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 d035788bd5 GH-48832: [R] Fix crash with zero-length POSIXct tzone
attribute (#49619)
d035788bd5 is described below
commit d035788bd51e829cf55f3658dd0dd05fefc3080e
Author: Nic Crane <[email protected]>
AuthorDate: Thu Apr 2 13:42:27 2026 +0100
GH-48832: [R] Fix crash with zero-length POSIXct tzone attribute (#49619)
### Rationale for this change
In R 4.5.2+, `as.POSIXct(x = NULL)` creates a zero-length `POSIXct` with
`INTSXP` (integer) type, not `REALSXP` (double). The `GetVectorType()` function
in `r_to_arrow.cpp` only checked for `POSIXct` in the `REALSXP` branch, so it
misclassified zero-length `POSIXct` as `INT32`.
### What changes are included in this PR?
Map any `POSIXct` object to correct class by adding the check to the
`INTSXP` branch of `GetVectorType()`
### Are these changes tested?
Yes
### Are there any user-facing changes?
No
* GitHub Issue: #48832
Authored-by: Nic Crane <[email protected]>
Signed-off-by: Nic Crane <[email protected]>
---
r/src/arrowExports.cpp | 4 ++--
r/src/r_to_arrow.cpp | 2 ++
r/tests/testthat/test-Array.R | 11 +++++++++++
r/tests/testthat/test-parquet.R | 10 ++++++++++
4 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/r/src/arrowExports.cpp b/r/src/arrowExports.cpp
index 5edf62d772..560466495e 100644
--- a/r/src/arrowExports.cpp
+++ b/r/src/arrowExports.cpp
@@ -5835,8 +5835,8 @@ static const R_CallMethodDef CallEntries[] = {
{ "_arrow_compute__GetFunctionNames", (DL_FUNC)
&_arrow_compute__GetFunctionNames, 0},
{ "_arrow_compute__Initialize", (DL_FUNC)
&_arrow_compute__Initialize, 0},
{ "_arrow_RegisterScalarUDF", (DL_FUNC)
&_arrow_RegisterScalarUDF, 2},
- { "_arrow_build_info", (DL_FUNC) &_arrow_build_info, 0},
- { "_arrow_runtime_info", (DL_FUNC) &_arrow_runtime_info, 0},
+ { "_arrow_build_info", (DL_FUNC) &_arrow_build_info, 0},
+ { "_arrow_runtime_info", (DL_FUNC) &_arrow_runtime_info, 0},
{ "_arrow_csv___WriteOptions__initialize", (DL_FUNC)
&_arrow_csv___WriteOptions__initialize, 1},
{ "_arrow_csv___ReadOptions__initialize", (DL_FUNC)
&_arrow_csv___ReadOptions__initialize, 1},
{ "_arrow_csv___ParseOptions__initialize", (DL_FUNC)
&_arrow_csv___ParseOptions__initialize, 1},
diff --git a/r/src/r_to_arrow.cpp b/r/src/r_to_arrow.cpp
index 4bee86ef68..45d68043af 100644
--- a/r/src/r_to_arrow.cpp
+++ b/r/src/r_to_arrow.cpp
@@ -93,6 +93,8 @@ RVectorType GetVectorType(SEXP x) {
return FACTOR;
} else if (Rf_inherits(x, "Date")) {
return DATE_INT;
+ } else if (Rf_inherits(x, "POSIXct")) {
+ return POSIXCT;
}
return INT32;
case STRSXP:
diff --git a/r/tests/testthat/test-Array.R b/r/tests/testthat/test-Array.R
index 03387f506f..8520160d12 100644
--- a/r/tests/testthat/test-Array.R
+++ b/r/tests/testthat/test-Array.R
@@ -324,6 +324,17 @@ test_that("array uses local timezone for POSIXct without
timezone", {
})
})
+test_that("zero-length POSIXct can be converted (GH-48832)", {
+ # In R 4.5.2+, zero-length POSIXct vectors are integer type, not double
+ x <- as.POSIXct(x = NULL)
+
+ # Should behave the same as non-empty POSIXct with empty tzone
+ expect_type_equal(infer_type(x), timestamp("us"))
+ arr <- Array$create(x)
+ expect_equal(arr$length(), 0L)
+ expect_type_equal(arr, timestamp("us"))
+})
+
test_that("Timezone handling in Arrow roundtrip (ARROW-3543)", {
# Write a feather file as that's what the initial bug report used
df <- tibble::tibble(
diff --git a/r/tests/testthat/test-parquet.R b/r/tests/testthat/test-parquet.R
index 47d73fb1e2..faa8d41e23 100644
--- a/r/tests/testthat/test-parquet.R
+++ b/r/tests/testthat/test-parquet.R
@@ -129,6 +129,16 @@ test_that("write_parquet() can truncate timestamps", {
expect_equal(as.data.frame(tab), as.data.frame(new))
})
+test_that("write_parquet() works with zero-length POSIXct (GH-48832)", {
+ # In R 4.5.2+, zero-length POSIXct vectors are integer type, not double
+ tf <- tempfile()
+ on.exit(unlink(tf))
+
+ expect_no_error(write_parquet(data.frame(x = as.POSIXct(x = NULL)), tf))
+ result <- read_parquet(tf)
+ expect_equal(nrow(result), 0)
+})
+
test_that("make_valid_parquet_version()", {
expect_equal(
make_valid_parquet_version("1.0"),