From f762612952f0ba128c35dbe2b752bf8a2350944f Mon Sep 17 00:00:00 2001 From: Andrew Gene Brown Date: Wed, 18 Sep 2024 14:53:48 -0700 Subject: [PATCH] Standardize timezone conversion for `datetime` column to `"US/Central"` (or user-specified via `tz` for #358) --- R/fetchSCAN.R | 29 +++++++++++++---------------- man/fetchSCAN.Rd | 5 ++++- tests/testthat/test-fetchSCAN.R | 11 +++-------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/R/fetchSCAN.R b/R/fetchSCAN.R index 9201bb54..3f379530 100644 --- a/R/fetchSCAN.R +++ b/R/fetchSCAN.R @@ -33,7 +33,7 @@ #' #' This function converts below-ground sensor depth from inches to cm. All temperature values are reported as degrees C. Precipitation, snow depth, and snow water content are reported as *inches*. #' -#' Times are converted to the time zone of the *first* station specified in `site.code`. +#' The `datetime` column in sensor data results is converted to the target time zone specified in `tz` argument, the default is `"US/Central"`. Use `tz = "UTC"` (or other `OlsonNames()` that do not use daylight savings, e.g. `"US/Arizona"`) to avoid having a mix of time offsets due to daylight savings time. #' #' ## SCAN Sensors #' @@ -81,6 +81,8 @@ #' #' @param timeseries either `'Daily'` or `'Hourly'` #' +#' @param tz Target timezone to convert `datetime` columns of results. Default: `"US/Central"`. +#' #' @param ... additional arguments. May include `intervalType`, `format`, `sitenum`, `interval`, `year`, `month`. Presence of additional arguments bypasses default batching functionality provided in the function and submits a 'raw' request to the API form. #' #' @return a `list` of `data.frame` objects, where each element name is a sensor type, plus a `metadata` table; different `report` types change the types of sensor data returned. `SCAN_sensor_metadata()` and `SCAN_site_metadata()` return a `data.frame`. `NULL` on bad request. @@ -109,7 +111,7 @@ #' } #' @rdname fetchSCAN #' @export -fetchSCAN <- function(site.code = NULL, year = NULL, report = 'SCAN', timeseries = c('Daily', 'Hourly'), ...) { +fetchSCAN <- function(site.code = NULL, year = NULL, report = 'SCAN', timeseries = c('Daily', 'Hourly'), tz = "US/Central", ...) { # check for required packages if (!requireNamespace('httr', quietly = TRUE)) @@ -199,7 +201,8 @@ fetchSCAN <- function(site.code = NULL, year = NULL, report = 'SCAN', timeseries d, code = sensor.i, meta = m.i, - hourlyFlag = (timeseries == 'Hourly') + hourlyFlag = (timeseries == 'Hourly'), + tz = tz ) } @@ -236,7 +239,7 @@ fetchSCAN <- function(site.code = NULL, year = NULL, report = 'SCAN', timeseries } # combine soil sensor suites into stackable format -.formatSCAN_soil_sensor_suites <- function(d, code, meta, hourlyFlag) { +.formatSCAN_soil_sensor_suites <- function(d, code, meta, hourlyFlag, tz) { value <- NULL @@ -324,19 +327,13 @@ fetchSCAN <- function(site.code = NULL, year = NULL, report = 'SCAN', timeseries } } - ## create datetime stamp + timezone if hourly data - # GMT offset from current station metadata.. maybe faster to do this in bulk - - # setup timezone code - # odd, but we have to negate the offset from GMT here - # trust me it works - .tz <- sprintf('etc/gmt+%s', -meta$dataTimeZone) - - # create datetime stamp + timezone - res$datetime <- as.POSIXct(strptime(paste(res$Date, res$Time), "%Y-%m-%d %H:%M"), tz = .tz) + # setup signed offset in hours and minutes from UTC, e.g. -0800 is 8 hours behind + .so <- formatC(meta$dataTimeZone * 100, 4, flag = 0) - ## TODO: implement user-supplied TZ - # format(res$datetime, tz = .userTZ, usetz = TRUE) + # create datetime stamp standardized to user-specified timezone + res$datetime <- as.POSIXct(strftime(paste(res$Date, res$Time, .so), "%Y-%m-%d %H:%M %z"), + format = "%Y-%m-%d %H:%M %z", + tz = tz) return(res) } diff --git a/man/fetchSCAN.Rd b/man/fetchSCAN.Rd index 6fa096ff..4455c750 100644 --- a/man/fetchSCAN.Rd +++ b/man/fetchSCAN.Rd @@ -11,6 +11,7 @@ fetchSCAN( year = NULL, report = "SCAN", timeseries = c("Daily", "Hourly"), + tz = "US/Central", ... ) @@ -27,6 +28,8 @@ SCAN_site_metadata(site.code = NULL) \item{timeseries}{either \code{'Daily'} or \code{'Hourly'}} +\item{tz}{Target timezone to convert \code{datetime} columns of results. Default: \code{"US/Central"}.} + \item{...}{additional arguments. May include \code{intervalType}, \code{format}, \code{sitenum}, \code{interval}, \code{year}, \code{month}. Presence of additional arguments bypasses default batching functionality provided in the function and submits a 'raw' request to the API form.} } \value{ @@ -40,7 +43,7 @@ Possible above and below ground sensor types include: 'SMS' (soil moisture), 'ST This function converts below-ground sensor depth from inches to cm. All temperature values are reported as degrees C. Precipitation, snow depth, and snow water content are reported as \emph{inches}. -Times are converted to the time zone of the \emph{first} station specified in \code{site.code}. +The \code{datetime} column in sensor data results is converted to the target time zone specified in \code{tz} argument, the default is \code{"US/Central"}. Use \code{tz = "UTC"} (or other \code{OlsonNames()} that do not use daylight savings, e.g. \code{"US/Arizona"}) to avoid having a mix of time offsets due to daylight savings time. \subsection{SCAN Sensors}{ All Soil Climate Analysis Network (SCAN) sensor measurements are reported hourly.\tabular{lll}{ diff --git a/tests/testthat/test-fetchSCAN.R b/tests/testthat/test-fetchSCAN.R index 6e101dbe..78f713dc 100644 --- a/tests/testthat/test-fetchSCAN.R +++ b/tests/testthat/test-fetchSCAN.R @@ -60,16 +60,11 @@ test_that("timezone check", { skip_on_cran() - # skip on error # skip on error skip_if(inherits(z, 'try-error') || is.null(z)) - # should be GMT-8, that of the first station (2218) - .tz <- table(format(z$SMS$datetime, format = '%Z')) - # windows and macos should return '-08' - # linux returns c('etc', '-08') + # default target timezone is US/Central, including CDT (-0500) and CST (-0600) + .tz <- table(format(z$SMS$datetime, format = '%z')) - # platform agnostic test - .test <- any(grepl('-08', names(.tz))) - expect_true(.test) + expect_equal(names(.tz), c("-0500", "-0600")) })