Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
91b6473
unit tests for `parse_date_time()`
dragosmg Apr 1, 2022
f9c7a37
first pass at implementing `parse_date_time()`
dragosmg Apr 1, 2022
b63b4e7
added changes back in
dragosmg Apr 14, 2022
cc01f24
NEWS update
dragosmg Apr 14, 2022
35fb293
lints
dragosmg Apr 14, 2022
e7821c6
removed `insert_at_position()` and added a TODO
dragosmg Apr 14, 2022
3438344
trying out bypassing the windows custom logic
dragosmg Apr 14, 2022
0e9f63e
Merge branch 'master' into parse_date_time
dragosmg Apr 28, 2022
3cbbc9d
clean-up after merging master
dragosmg Apr 28, 2022
f97fc0c
add newline
dragosmg Apr 28, 2022
70204d1
separate locale-dependent unit tests on Windows
dragosmg Apr 28, 2022
9da29c0
add reference to locale Jira
dragosmg Apr 28, 2022
395594c
refer to the locale on Windows Jira
dragosmg Apr 29, 2022
f77246d
skip tests on windows when the R version is older than 4.0
dragosmg Apr 29, 2022
ffa667f
replace skipping on older R on windows with skip if RE2 not available
dragosmg Apr 29, 2022
1c97ca6
error early when the user supplies `orders` we don't yet support
dragosmg Apr 29, 2022
23adca4
simplify `build_formats()`
dragosmg Apr 29, 2022
94bef2d
simplify the `fast_strptime()` implementation
dragosmg Apr 29, 2022
a5e2b1a
trigger a CI failure on windows
dragosmg May 3, 2022
93f6944
remove binding and unit tests for `fast_strptime()`
dragosmg May 3, 2022
e223f51
move `HMS` failing (expected) tests to a separate block
dragosmg May 3, 2022
16c634d
test to figure out the CI `LC_TIME` locale
dragosmg May 3, 2022
e0a4db6
test
dragosmg May 3, 2022
c89cca8
set locale to `"en_US.UTF-8"`
dragosmg May 3, 2022
683f6ad
`LC_TIME="C"`
dragosmg May 3, 2022
203f0fa
final version with skip on Windows
dragosmg May 3, 2022
5774f10
move test data frames definition inside `compare_dplyr_binding()`
dragosmg May 3, 2022
ad2596b
added a Jira to update `parse_date_time` to accept strings without se…
dragosmg May 3, 2022
55e0f9b
Merge branch 'master' into parse_date_time
dragosmg May 3, 2022
8042844
Merge branch 'master' into parse_date_time
dragosmg May 4, 2022
9b6309a
moved `build_formats()` to helpers
dragosmg May 4, 2022
88beefb
register datetime parsers separately
dragosmg May 4, 2022
3717d49
style
dragosmg May 4, 2022
c6f9e00
a more elegant implementation (no hardcoding)
dragosmg May 5, 2022
98890c5
accept `orders` longer than 1
dragosmg May 5, 2022
5dd5247
updated NEWS with `parse_date_time()` limitations
dragosmg May 6, 2022
db274ce
switch back to 7.0.0.9000
dragosmg May 6, 2022
dc36fd0
one more try
dragosmg May 6, 2022
de52524
allow orders to have `"%"` (which get stripped out) and move checks o…
dragosmg May 6, 2022
0d4cc6a
stop processing formats passed via orders. assume they are correct as…
dragosmg May 6, 2022
a6b242b
return to processing `orders` with `"%"` into `formats`
dragosmg May 6, 2022
96220c4
add a test with combi formats and orders + combi strings to be parsed
dragosmg May 6, 2022
db777bf
typo
dragosmg May 6, 2022
9471e5f
skip when RE2 not available and add clarifying comments
dragosmg May 6, 2022
d1da8b2
hopefully made NEWS entry a bit clearer
dragosmg May 9, 2022
362681d
another minor NEWS update
dragosmg May 9, 2022
904a90e
Update r/NEWS.md
jonkeane May 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions r/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* date-time functionality:
* Added `difftime` and `as.difftime()`
* Added `as.Date()` to convert to date
* Added `parse_date_time()` & `fast_strptime()` datetime parsers
* `median()` and `quantile()` will warn once about approximate calculations regardless of interactivity.

# arrow 7.0.0
Expand Down
80 changes: 80 additions & 0 deletions r/R/dplyr-funcs-datetime.R
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,62 @@ register_bindings_duration <- function() {
delta <- delta$cast(int64())
start + delta$cast(duration("s"))
})
register_binding("fast_strptime", function(x,
format,
tz = "UTC",
lt = TRUE,
cutoff_2000 = 68L,
unit = "s") {
# TODO support multiple formats once
# https://issues.apache.org/jira/browse/ARROW-15665 is done
Comment thread
dragosmg marked this conversation as resolved.
Outdated
if (length(format) > 1) {
arrow_not_supported("multiple values for `format`")
}

if (!missing(tz)) {
arrow_not_supported("Time zone argument")
}
Comment thread
dragosmg marked this conversation as resolved.
Outdated
# `lt` controls the output `lt = TRUE` returns a POSIXlt (which doesn't play
# well with mutate, for example)
if (lt) {
arrow_not_supported("`lt = TRUE` argument")
}

if (cutoff_2000 != 68L) {
arrow_not_supported("`cutoff_2000` != 68L argument")
}

unit <- make_valid_time_unit(unit, c(valid_time64_units, valid_time32_units))

build_expr("strptime", x, options = list(format = format, unit = unit))
Comment thread
dragosmg marked this conversation as resolved.
Outdated
})
register_binding("parse_date_time", function(x,
orders,
tz = "UTC") {

# make all separators (non-letters and non-numbers) into "-"
x <- call_binding("gsub", "[^A-Za-z0-9]", "-", x)
# collapse multiple separators into a single one
x <- call_binding("gsub", "-{2,}", "-", x)

# TODO figure out how to parse strings that have no separators)
# we could insert separators at the "likely" positions, but it might be
# tricky given the possible combinations between dmy formats + locale

# each order is translated into 6 possible formats
formats <- build_formats(orders)
coalesce_output <- build_expr(
"coalesce",
build_expr("strptime", x, options = list(format = formats[1], unit = 0L, error_is_null = TRUE)),
build_expr("strptime", x, options = list(format = formats[2], unit = 0L, error_is_null = TRUE)),
build_expr("strptime", x, options = list(format = formats[3], unit = 0L, error_is_null = TRUE)),
build_expr("strptime", x, options = list(format = formats[4], unit = 0L, error_is_null = TRUE)),
build_expr("strptime", x, options = list(format = formats[5], unit = 0L, error_is_null = TRUE)),
build_expr("strptime", x, options = list(format = formats[6], unit = 0L, error_is_null = TRUE))
)

build_expr("assume_timezone", coalesce_output, options = list(timezone = tz))
Comment thread
dragosmg marked this conversation as resolved.
Outdated
})
}

binding_format_datetime <- function(x, format = "", tz = "", usetz = FALSE) {
Expand All @@ -372,3 +428,27 @@ binding_format_datetime <- function(x, format = "", tz = "", usetz = FALSE) {

build_expr("strftime", x, options = list(format = format, locale = Sys.getlocale("LC_TIME")))
}

build_formats <- function(orders) {
year_chars <- sprintf("%%%s", c("y", "Y"))
month_chars <- sprintf("%%%s", c("m", "B", "b"))
day_chars <- sprintf("%%%s", c("d"))

outcome <- switch(
orders,
"ymd" = expand.grid(year_chars, month_chars, day_chars),
"ydm" = expand.grid(year_chars, day_chars, month_chars),
"mdy" = expand.grid(month_chars, day_chars, year_chars),
"myd" = expand.grid(month_chars, year_chars, day_chars),
"dmy" = expand.grid(day_chars, month_chars, year_chars),
"dym" = expand.grid(day_chars, year_chars, month_chars)
)
outcome$format <- paste(outcome$Var1, outcome$Var2, outcome$Var3, sep = "-")
outcome$format
}

instert_at_position <- function(string, positions, replacement) {
Comment thread
dragosmg marked this conversation as resolved.
Outdated
if (positions < 0) {
pattern <- paste0("^(.{", nchar(string) - positions, "})(.*)$")
}
}
146 changes: 146 additions & 0 deletions r/tests/testthat/test-dplyr-funcs-datetime.R
Original file line number Diff line number Diff line change
Expand Up @@ -1253,3 +1253,149 @@ test_that("`decimal_date()` and `date_decimal()`", {
ignore_attr = "tzone"
)
})

test_that("parse_date_time()", {
test_df <- tibble(
dates = c("09-01-17", "02-Sep-17")
)

test_dates <- tibble::tibble(
string_ymd = c(
"2021-09-1", "2021/09///2", "2021.09.03", "2021,09,4", "2021:09::5",
"2021 09 6", "21-09-07", "21/09/08", "21.09.9", "21,09,10",
"21:09:11", "2021 Sep 12", "2021 September 13", "21 Sep 14",
"21 September 15",
# not yet working for strings with no separators, like "20210917", "210918" or "2021Sep19
# no separators and %b or %B are even more complicated (and they work in
# lubridate). not to mention locale
NA
),
string_dmy = c(
"1-09-2021", "2/09//2021", "03.09.2021", "04,09,2021", "5:::09:2021",
"6 09 2021", "07-09-21", "08/09/21", "9.09.21", "10,09,21", "11:09:21",
"12 Sep 2021", "13 September 2021", "14 Sep 21", "15 September 21",
# not yet working for strings with no separators, like "10092021", "100921",
NA
),
string_mdy = c(
"09-01-2021", "09/2/2021", "09.3.2021", "09,04,2021", "09:05:2021",
"09 6 2021", "09-7-21", "09/08/21", "09.9.21", "09,10,21", "09:11:21",
"Sep 12 2021", "September 13 2021", "Sep 14 21", "September 15 21",
# not yet working for strings with no separators, like "09102021", "091021",
NA
)
)

compare_dplyr_binding(
.input %>%
mutate(
parsed_date_ymd = parse_date_time(string_ymd, orders = "ymd"),
parsed_date_dmy = parse_date_time(string_dmy, orders = "dmy"),
parsed_date_mdy = parse_date_time(string_mdy, orders = "mdy")
) %>%
collect(),
test_dates
)
})

test_that("lubridate's fast_strptime", {
t_string <- tibble(x = c("2018-10-07 19:04:05", NA))
t_string_y <- tibble(x = c("68-10-07 19:04:05", "69-10-07 19:04:05", NA))
t_string_2formats <- tibble(x = c("2018-10-07 19:04:05", "68-10-07 19:04:05"))
t_stamp <- tibble(x = c(lubridate::ymd_hms("2018-10-07 19:04:05"), NA))

compare_dplyr_binding(
.input %>%
mutate(y = fast_strptime(x, format = "%Y-%m-%d %H:%M:%S", lt = FALSE)) %>%
collect(),
t_string,
# arrow does not preserve the `tzone` attribute
ignore_attr = TRUE
)

compare_dplyr_binding(
.input %>%
mutate(y =
fast_strptime("68-10-07 19:04:05", format = "%y-%m-%d %H:%M:%S", lt = FALSE)
) %>%
collect(),
t_string,
ignore_attr = TRUE
)

# fast_strptime()'s `cutoff_2000` argument is not supported, but its value is
# implicitly set to 68L both in base R and in Arrow
compare_dplyr_binding(
.input %>%
mutate(date_short_year = fast_strptime(x, format = "%y-%m-%d %H:%M:%S", lt = FALSE)) %>%
collect(),
t_string_y,
# arrow does not preserve the `tzone` attribute
ignore_attr = TRUE
)

compare_dplyr_binding(
.input %>%
mutate(
date_short_year =
fast_strptime(x, format = "%y-%m-%d %H:%M:%S", lt = FALSE, cutoff_2000 = 69L)
) %>%
collect(),
t_string_y,
warning = TRUE
)

formats <- c("%Y-%m-%d %H:%M:%S", "%y-%m-%d %H:%M:%S")
compare_dplyr_binding(
.input %>%
mutate(date_multi_formats =
fast_strptime(x, format = formats, lt = FALSE)) %>%
collect(),
t_string_2formats,
warning = TRUE
)
})

test_that("parse_date_time()", {
test_df <- tibble(
dates = c("09-01-17", "02-Sep-17")
)

test_dates <- tibble::tibble(
string_ymd = c(
"2021-09-1", "2021/09///2", "2021.09.03", "2021,09,4", "2021:09::5",
"2021 09 6", "21-09-07", "21/09/08", "21.09.9", "21,09,10",
"21:09:11", "2021 Sep 12", "2021 September 13", "21 Sep 14",
"21 September 15",
# not yet working for strings with no separators, like "20210917", "210918" or "2021Sep19
# no separators and %b or %B are even more complicated (and they work in
# lubridate). not to mention locale
NA
),
string_dmy = c(
"1-09-2021", "2/09//2021", "03.09.2021", "04,09,2021", "5:::09:2021",
"6 09 2021", "07-09-21", "08/09/21", "9.09.21", "10,09,21", "11:09:21",
"12 Sep 2021", "13 September 2021", "14 Sep 21", "15 September 21",
# not yet working for strings with no separators, like "10092021", "100921",
NA
),
string_mdy = c(
"09-01-2021", "09/2/2021", "09.3.2021", "09,04,2021", "09:05:2021",
"09 6 2021", "09-7-21", "09/08/21", "09.9.21", "09,10,21", "09:11:21",
"Sep 12 2021", "September 13 2021", "Sep 14 21", "September 15 21",
# not yet working for strings with no separators, like "09102021", "091021",
NA
)
)

compare_dplyr_binding(
.input %>%
mutate(
parsed_date_ymd = parse_date_time(string_ymd, orders = "ymd"),
parsed_date_dmy = parse_date_time(string_dmy, orders = "dmy"),
parsed_date_mdy = parse_date_time(string_mdy, orders = "mdy")
) %>%
collect(),
test_dates
)
})