You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by th...@apache.org on 2025/08/22 09:11:07 UTC

(arrow) branch main updated: GH-47257: [R] Fix truncation of time variables to work with numeric subseconds time with hms bindings (#47278)

This is an automated email from the ASF dual-hosted git repository.

thisisnic pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 6faea76779 GH-47257: [R] Fix truncation of time variables to work with numeric subseconds time with hms bindings (#47278)
6faea76779 is described below

commit 6faea7677913812779e94ffb1e46aa2487e024e4
Author: Nic Crane <th...@gmail.com>
AuthorDate: Fri Aug 22 10:11:01 2025 +0100

    GH-47257: [R] Fix truncation of time variables to work with numeric subseconds time with hms bindings (#47278)
    
    ### Rationale for this change
    
    Subsecond times truncated
    
    ### What changes are included in this PR?
    
    Work with times as milliseconds
    
    ### Are these changes tested?
    
    Yes
    
    ### Are there any user-facing changes?
    
    Yes
    * GitHub Issue: #47257
    
    Authored-by: Nic Crane <th...@gmail.com>
    Signed-off-by: Nic Crane <th...@gmail.com>
---
 r/R/dplyr-funcs-datetime.R                   | 32 +++++++++++++++-------------
 r/tests/testthat/test-dplyr-funcs-datetime.R | 18 ++++++++++++----
 2 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/r/R/dplyr-funcs-datetime.R b/r/R/dplyr-funcs-datetime.R
index 77c6c9821e..74f83454b2 100644
--- a/r/R/dplyr-funcs-datetime.R
+++ b/r/R/dplyr-funcs-datetime.R
@@ -831,7 +831,7 @@ register_bindings_datetime_rounding <- function() {
 register_bindings_hms <- function() {
   numeric_to_time32 <- function(x) {
     # The only numeric which can be cast to time32 is int32 so double cast to make sure
-    cast(cast(x, int32()), time32(unit = "s"))
+    cast(cast(x, int32()), time32(unit = "ms"))
   }
 
   datetime_to_time32 <- function(datetime) {
@@ -850,14 +850,14 @@ register_bindings_hms <- function() {
         abort("All arguments must be numeric or NA_real_")
       }
 
-      total_secs <- seconds +
-        Expression$create("multiply_checked", minutes, 60) +
-        Expression$create("multiply_checked", hours, 3600) +
-        Expression$create("multiply_checked", days, 86400)
+      total_ms <- Expression$create("multiply_checked", seconds, 1000) +
+        Expression$create("multiply_checked", minutes, 60000) +
+        Expression$create("multiply_checked", hours, 3600000) +
+        Expression$create("multiply_checked", days, 86400000)
 
-      return(numeric_to_time32(total_secs))
+      return(numeric_to_time32(total_ms))
     },
-    notes = "subsecond times not supported"
+    notes = "nanosecond times not supported"
   )
 
   register_binding(
@@ -868,20 +868,22 @@ register_bindings_hms <- function() {
       }
 
       if (call_binding("is.numeric", x)) {
-        return(numeric_to_time32(x))
+        return(numeric_to_time32(Expression$create("multiply_checked", x, 1000)))
       }
 
       if (call_binding("is.character", x)) {
-        dash <- call_binding("gsub", ":", "-", x)
-        as_date_time_string <- call_binding("str_c", "1970-01-01", dash, sep = "-")
-        as_date_time <- Expression$create(
+        # Parse time strings using strptime with a time format
+        # Since strptime expects full datetime strings, we need to prefix with a dummy date
+        # Note: Arrow's strptime doesn't support subsecond precision for time parsing
+        prefixed_string <- call_binding("str_c", "1970-01-01 ", x)
+        parsed_datetime <- Expression$create(
           "strptime",
-          as_date_time_string,
-          options = list(format = "%Y-%m-%d-%H-%M-%S", unit = 0L)
+          prefixed_string,
+          options = list(format = "%Y-%m-%d %H:%M:%S", unit = 3L)
         )
-        return(datetime_to_time32(as_date_time))
+        return(datetime_to_time32(parsed_datetime))
       }
     },
-    notes = "subsecond times not supported"
+    notes = "subsecond precision not supported for character input"
   )
 }
diff --git a/r/tests/testthat/test-dplyr-funcs-datetime.R b/r/tests/testthat/test-dplyr-funcs-datetime.R
index 66ae066623..27bfdcde9e 100644
--- a/r/tests/testthat/test-dplyr-funcs-datetime.R
+++ b/r/tests/testthat/test-dplyr-funcs-datetime.R
@@ -3769,28 +3769,38 @@ test_that("hms::hms", {
 test_that("hms::as_hms", {
   test_df <- tibble(
     hms_string = c("0:7:45", "12:34:56"),
+    hms_subsec_string = c("0:7:45.1234", "12:34:56.12345"),
     int = c(30L, 75L),
     integerish_dbl = c(31, 76),
     dbl = c(31.2, 76.4),
-    datetime = as.POSIXct(c(1645243500, 1745243500), tz = "UTC", origin = "1970-01-01")
+    nanosecs = c(31.21234564, 76.412345),
+    datetime = as.POSIXct(c(1645243500.123, 1745243500.123), tz = "UTC", origin = "1970-01-01")
   )
 
   compare_dplyr_binding(
     .input %>%
       mutate(
-        x2 = hms::as_hms(int),
-        x3 = hms::as_hms(integerish_dbl),
+        x1 = hms::as_hms(int),
+        x2 = hms::as_hms(integerish_dbl),
+        x3 = hms::as_hms(dbl),
         x4 = hms::as_hms(datetime)
       ) %>%
       collect(),
     test_df
   )
 
+  # can only do millisecond precision
   expect_error(
-    arrow_table(test_df) %>% mutate(y = hms::as_hms(dbl)) %>% collect(),
+    arrow_table(test_df) %>% mutate(y = hms::as_hms(nanosecs)) %>% collect(),
     "was truncated converting to int32"
   )
 
+  # no subsecond precision with character strings
+  expect_error(
+    arrow_table(test_df) %>% mutate(y = hms::as_hms(hms_subsec_string)) %>% collect(),
+    "Failed to parse string"
+  )
+
   skip_if_not_available("utf8proc")
 
   compare_dplyr_binding(