Skip to content

Commit

Permalink
use POSIXct for datetime ranges
Browse files Browse the repository at this point in the history
ref #321 & #323

thanks, @pbchase
  • Loading branch information
wibeasley committed Mar 2, 2021
1 parent 45a023a commit 432d033
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 57 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Version 0.12 (Unreleased on CRAN)

* When writing records to the server, the functions `redcap_write()` and `redcap_write_oneshot()` have a new parameter that converts R's `logical`/boolean columns to integers. This meshes well with T/F and Y/N items that are coded as 1/0 underneath. The default will be FALSE, so it doesn't break existing code. (#305)
* When writing records to the server, the functions `redcap_write()` and `redcap_write_oneshot()` can toggle the ability to overwrite with blank/NA cells (suggested by @auricap, #315)
* The functions `redcap_read_oneshot()`, `redcap_read()`, & `redcap_read_oneshot_eav()` now support the parameters `datetime_range_begin` and `datetime_range_end`. The are passed to the REDCap parameters `dateRangeBegin` and `dateRangeEnd`, which restricts records returned, based on their last modified date in the server. (Thanks @pbchase, #321 & #323.)

### Stability Features

Expand Down
28 changes: 17 additions & 11 deletions R/redcap-read-oneshot-eav.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@
#' where the logic evaluates as TRUE. An blank/empty string returns all
#' records.
#' @param date_range_begin To return only records that have been created or
#' modified *after* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will assume no begin time.
#' modified *after* a given datetime, provide a
#' [POSIXct]
#' (https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no begin time.
#' @param date_range_end To return only records that have been created or
#' modified *before* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will use the current server time.
#' modified *before* a given datetime, provide a
#' [POSIXct]
#' (https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no end time.
#'
#' @param verbose A boolean value indicating if `message`s should be printed
#' to the R console during the operation. The verbose output might contain
Expand Down Expand Up @@ -146,8 +150,8 @@ redcap_read_oneshot_eav <- function(
# placeholder: export_survey_fields
export_data_access_groups = FALSE,
filter_logic = "",
date_range_begin = "",
date_range_end = "",
date_range_begin = as.POSIXct(NA),
date_range_end = as.POSIXct(NA),

# placeholder: guess_type
# placeholder: guess_max
Expand All @@ -174,9 +178,9 @@ redcap_read_oneshot_eav <- function(
# placeholder: export_survey_fields
checkmate::assert_logical( export_data_access_groups , any.missing=FALSE, len=1)
checkmate::assert_character(filter_logic , any.missing=FALSE, len=1, pattern="^.{0,}$")
checkmate::assert_character(date_range_begin , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
checkmate::assert_character(date_range_end , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
#
checkmate::assert_posixct( date_range_begin , any.missing=TRUE , len=1, null.ok=TRUE)
checkmate::assert_posixct( date_range_end , any.missing=TRUE , len=1, null.ok=TRUE)

# placeholder: checkmate::assert_logical( guess_type , any.missing=FALSE, len=1)
# placeholder: checkmate::assert_integerish(guess_max , any.missing=FALSE, len=1, lower=1)
checkmate::assert_logical( verbose , any.missing=FALSE, len=1, null.ok=TRUE)
Expand All @@ -191,6 +195,8 @@ redcap_read_oneshot_eav <- function(
events_collapsed <- collapse_vector(events , events_collapsed)
export_data_access_groups <- ifelse(export_data_access_groups, "true", "false")
filter_logic <- filter_logic_prepare(filter_logic)
date_range_begin <- dplyr::coalesce(strftime(date_range_begin, "%Y-%m-%d %H:%M:%S"), "")
date_range_end <- dplyr::coalesce(strftime(date_range_end , "%Y-%m-%d %H:%M:%S"), "")
verbose <- verbose_prepare(verbose)

if (1L <= nchar(fields_collapsed) )
Expand Down
26 changes: 15 additions & 11 deletions R/redcap-read-oneshot.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@
#' only return the records (or record-events, if a longitudinal project) where
#' the logic evaluates as TRUE. An blank/empty string returns all records.
#' @param date_range_begin To return only records that have been created or
#' modified *after* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will assume no begin time.
#' modified *after* a given datetime, provide a
#' [POSIXct](https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no begin time.
#' @param date_range_end To return only records that have been created or
#' modified *before* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will use the current server time.
#' modified *before* a given datetime, provide a
#' [POSIXct](https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no end time.
#' @param col_types A [readr::cols()] object passed internally to
#' [readr::read_csv()]. Optional.
#' @param guess_type A boolean value indicating if all columns should be
Expand Down Expand Up @@ -167,8 +169,8 @@ redcap_read_oneshot <- function(
export_survey_fields = FALSE,
export_data_access_groups = FALSE,
filter_logic = "",
date_range_begin = "",
date_range_end = "",
date_range_begin = as.POSIXct(NA),
date_range_end = as.POSIXct(NA),

col_types = NULL,
guess_type = TRUE,
Expand Down Expand Up @@ -196,9 +198,9 @@ redcap_read_oneshot <- function(
checkmate::assert_logical( export_survey_fields , any.missing=FALSE, len=1)
checkmate::assert_logical( export_data_access_groups , any.missing=FALSE, len=1)
checkmate::assert_character(filter_logic , any.missing=FALSE, len=1, pattern="^.{0,}$")
checkmate::assert_character(date_range_begin , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
checkmate::assert_character(date_range_end , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
#
checkmate::assert_posixct( date_range_begin , any.missing=TRUE , len=1, null.ok=TRUE)
checkmate::assert_posixct( date_range_end , any.missing=TRUE , len=1, null.ok=TRUE)

checkmate::assert_logical( guess_type , any.missing=FALSE, len=1)
checkmate::assert_integerish(guess_max , any.missing=FALSE, len=1, lower=1)
checkmate::assert_logical( verbose , any.missing=FALSE, len=1, null.ok=TRUE)
Expand All @@ -212,6 +214,8 @@ redcap_read_oneshot <- function(
forms_collapsed <- collapse_vector(forms , forms_collapsed)
events_collapsed <- collapse_vector(events , events_collapsed)
filter_logic <- filter_logic_prepare(filter_logic)
date_range_begin <- dplyr::coalesce(strftime(date_range_begin, "%Y-%m-%d %H:%M:%S"), "")
date_range_end <- dplyr::coalesce(strftime(date_range_end , "%Y-%m-%d %H:%M:%S"), "")
verbose <- verbose_prepare(verbose)

if (1L <= nchar(fields_collapsed) )
Expand Down
24 changes: 13 additions & 11 deletions R/redcap-read.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@
#' will only return the records (or record-events, if a longitudinal project)
#' where the logic evaluates as TRUE. An blank/empty string returns all records.
#' @param date_range_begin To return only records that have been created or
#' modified *after* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will assume no begin time.
#' modified *after* a given datetime, provide a
#' [POSIXct](https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no begin time.
#' @param date_range_end To return only records that have been created or
#' modified *before* a given date/time, provide a timestamp in the format
#' YYYY-MM-DD HH:MM:SS (e.g., '2017-01-01 00:00:00'). If not specified,
#' it will use the current server time.
#' modified *before* a given datetime, provide a
#' [POSIXct](https://stat.ethz.ch/R-manual/R-devel/library/base/html/as.POSIXlt.html)
#' value.
#' If not specified, REDCap will assume no end time.
#' @param col_types A [readr::cols()] object passed internally to
#' [readr::read_csv()]. Optional.
#' @param guess_type A boolean value indicating if all columns should be
Expand Down Expand Up @@ -180,8 +182,8 @@ redcap_read <- function(
export_survey_fields = FALSE,
export_data_access_groups = FALSE,
filter_logic = "",
date_range_begin = "",
date_range_end = "",
date_range_begin = as.POSIXct(NA),
date_range_end = as.POSIXct(NA),

col_types = NULL,
guess_type = TRUE,
Expand Down Expand Up @@ -209,9 +211,9 @@ redcap_read <- function(
# placeholder: returnFormat
checkmate::assert_logical( export_survey_fields , any.missing=FALSE, len=1)
checkmate::assert_logical( export_data_access_groups , any.missing=FALSE, len=1)
checkmate::assert_character(date_range_begin , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
checkmate::assert_character(date_range_end , any.missing=TRUE , len=1, pattern="^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|)$", null.ok=TRUE)
#
checkmate::assert_posixct( date_range_begin , any.missing=TRUE , len=1, null.ok=TRUE)
checkmate::assert_posixct( date_range_end , any.missing=TRUE , len=1, null.ok=TRUE)

checkmate::assert_logical( guess_type , any.missing=FALSE, len=1)

if (!is.null(guess_max)) warning("The `guess_max` parameter in `REDCapR::redcap_read()` is deprecated.")
Expand Down
18 changes: 10 additions & 8 deletions man/redcap_read.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 10 additions & 8 deletions man/redcap_read_oneshot.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 12 additions & 8 deletions man/redcap_read_oneshot_eav.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions tests/testthat/test-read-batch-simple.R
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,36 @@ test_that("filter-character", {
expect_true(returned_object$success)
})

test_that("date-range", {
testthat::skip_on_cran()
path_expected <- "test-data/specific-redcapr/read-batch-simple/default.R"
expected_outcome_message <- "\\d+ records and \\d+ columns were read from REDCap in \\d+(\\.\\d+\\W|\\W)seconds\\."

start <- as.POSIXct(strptime("2018-08-01 03:00", "%Y-%m-%d %H:%M"))
stop <- Sys.time()
expect_message(
regexp = expected_outcome_message,
returned_object <-
redcap_read(
redcap_uri = credential$redcap_uri,
token = credential$token,
date_range_begin = start,
date_range_end = stop
)
)

if (update_expectation) save_expected(returned_object$data, path_expected)
expected_data_frame <- retrieve_expected(path_expected)

expect_equal(returned_object$data, expected=expected_data_frame, label="The returned data.frame should be correct", ignore_attr = TRUE) # dput(returned_object$data)
expect_equal(returned_object$status_code, expected="200")
expect_true(returned_object$records_collapsed=="", "A subset of records was not requested.")
expect_true(returned_object$fields_collapsed=="", "A subset of fields was not requested.")
expect_equal(returned_object$filter_logic, "")
expect_match(returned_object$outcome_message, regexp=expected_outcome_message, perl=TRUE)
expect_true(returned_object$success)
})

test_that("error-bad-token", {
testthat::skip_on_cran()

Expand Down
31 changes: 31 additions & 0 deletions tests/testthat/test-read-oneshot-eav.R
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,37 @@ test_that("filter-character", {
expect_match(returned_object$outcome_message, regexp=expected_outcome_message, perl=TRUE)
expect_true(returned_object$success)
})

test_that("date-range", {
testthat::skip_on_cran()
path_expected <- "test-data/specific-redcapr/read-oneshot-eav/default.R"
expected_outcome_message <- "\\d+ records and \\d+ columns were read from REDCap in \\d+(\\.\\d+\\W|\\W)seconds\\."

start <- as.POSIXct(strptime("2018-08-01 03:00", "%Y-%m-%d %H:%M"))
stop <- Sys.time()
expect_message(
regexp = expected_outcome_message,
returned_object <-
REDCapR:::redcap_read_oneshot_eav(
redcap_uri = credential$redcap_uri,
token = credential$token,
date_range_begin = start,
date_range_end = stop
)
)

if (update_expectation) save_expected(returned_object$data, path_expected)
expected_data_frame <- retrieve_expected(path_expected)

expect_equal(returned_object$data, expected=expected_data_frame, label="The returned data.frame should be correct", ignore_attr = TRUE) # dput(returned_object$data)
expect_equal(returned_object$status_code, expected=200L)
expect_equal(returned_object$raw_text, expected="", ignore_attr = TRUE) # dput(returned_object$raw_text)
expect_true(returned_object$records_collapsed=="", "A subset of records was not requested.")
expect_true(returned_object$fields_collapsed=="", "A subset of fields was not requested.")
expect_equal(returned_object$filter_logic, "")
expect_match(returned_object$outcome_message, regexp=expected_outcome_message, perl=TRUE)
expect_true(returned_object$success)
})
test_that("bad token -Error", {
testthat::skip_on_cran()

Expand Down
31 changes: 31 additions & 0 deletions tests/testthat/test-read-oneshot.R
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,37 @@ test_that("filter-character", {
expect_true(returned_object$success)
})

test_that("date-range", {
testthat::skip_on_cran()
path_expected <- "test-data/specific-redcapr/read-oneshot/default.R"
expected_outcome_message <- "\\d+ records and \\d+ columns were read from REDCap in \\d+(\\.\\d+\\W|\\W)seconds\\."

start <- as.POSIXct(strptime("2018-08-01 03:00", "%Y-%m-%d %H:%M"))
stop <- Sys.time()
expect_message(
regexp = expected_outcome_message,
returned_object <-
redcap_read_oneshot(
redcap_uri = credential$redcap_uri,
token = credential$token,
date_range_begin = start,
date_range_end = stop
)
)

if (update_expectation) save_expected(returned_object$data, path_expected)
expected_data_frame <- retrieve_expected(path_expected)

expect_equal(returned_object$data, expected=expected_data_frame, label="The returned data.frame should be correct", ignore_attr = TRUE) # dput(returned_object$data)
expect_equal(returned_object$status_code, expected=200L)
expect_equal(returned_object$raw_text, expected="", ignore_attr = TRUE) # dput(returned_object$raw_text)
expect_true(returned_object$records_collapsed=="", "A subset of records was not requested.")
expect_true(returned_object$fields_collapsed=="", "A subset of fields was not requested.")
expect_equal(returned_object$filter_logic, "")
expect_match(returned_object$outcome_message, regexp=expected_outcome_message, perl=TRUE)
expect_true(returned_object$success)
})

test_that("bad token -Error", {
testthat::skip_on_cran()
expected_outcome_message <- "The REDCapR read/export operation was not successful\\."
Expand Down

0 comments on commit 432d033

Please sign in to comment.