Repository: single-cell-data/TileDB-SOMA (language: python)

Issue:
[r] Expose timestamp ranges for libtiledbsoma
Improved handling of timestamp and timestamp ranges for libtiledbsoma. When users provide a timestamp to `SOMA*Open()`, it is expected to be a scalar, defined as "read up to this point"; as libtiledbsoma expects a vector of timestamps in the form of `<t0, t1>`, add a new `tiledb_timestamp_range` active binding to automagically convert the scalar `tiledb_timestamp` to a vector, or `NULL` if no timestamp was specified

New SOMA properites:
 - `TileDBObject$tiledb_timestamp_range`: new active binding to return the timestamp as a range `<t0, t1>` for libtiledbsoma; exposed as an active binding for use outside of the R6 objects

Modified SOMA methods:
 - `TileDBObject$initialize(tiledb_timestamp = )`: now requires `tiledb_timestamp` to be either `NULL` or a scalar, finite `POSIXct` object
 - `TileDBOBject$reopen(tiledb_timestamp = )`: same as `$initialize()`
[r] Timestamp write and read of data frame and array objects
**Issue and/or context:**

This PR adds the ability to write, and read, at given time stamps covering the key SOMA object data frame as well as sparse and dense arrays.

There is a corresponding follow-up PR #2925 which exposes time points (as before) as well as range (begin and end) as taken by the C++ API.

**Changes:**

Code changes as well as a new test file.  It also contains two additional smaller PRs (#2866, #2871).

**Notes for Reviewer:**

[SC 52516](https://app.shortcut.com/tiledb-inc/story/52516/r-support-timestamp-range)  

The branch had been open for two-plus weeks and has been rebased a few times so the final commit timestamps are not from the initial commits.


Discussion / hints:

## [Codecov](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 89.88%. Comparing base [(`331a1b6`)](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/commit/331a1b62a0c72fd6f58cfebf97a80a02fd4aa74f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) to head [(`af225dc`)](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/commit/af225dcf534a67c279bc7573746d9ed61df70517?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data).
> Report is 2 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #2926   +/-   ##
=======================================
  Coverage   89.88%   89.88%           
=======================================
  Files          37       37           
  Lines        3925     3925           
=======================================
  Hits         3528     3528           
  Misses        397      397           
```

| [Flag](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) | Coverage Δ | |
|---|---|---|
| [python](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) | `89.88% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Components](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) | Coverage Δ | |
|---|---|---|
| [python_api](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) | `89.88% <ø> (ø)` | |
| [libtiledbsoma](https://app.codecov.io/gh/single-cell-data/TileDB-SOMA/pull/2926/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=single-cell-data) | `∅ <ø> (∅)` | |

</details>
I put one microfix in following review but as discussed in parallel it maybe beneficial to attach this in parallel.

Fix patch:
diff --git a/apis/r/DESCRIPTION b/apis/r/DESCRIPTION
index 34fdd30b0f..5ada792d2b 100644
--- a/apis/r/DESCRIPTION
+++ b/apis/r/DESCRIPTION
@@ -6,7 +6,7 @@ Description: Interface for working with 'TileDB'-based Stack of Matrices,
     like those commonly used for single cell data analysis. It is documented at
     <https://github.com/single-cell-data>; a formal specification available is at
     <https://github.com/single-cell-data/SOMA/blob/main/abstract_specification.md>.
-Version: 1.13.99.3
+Version: 1.13.99.4
 Authors@R: c(
     person(given = "Aaron", family = "Wolen",
            role = c("cre", "aut"), email = "aaron@tiledb.com",
diff --git a/apis/r/NEWS.md b/apis/r/NEWS.md
index d2db784ac7..aa61d10905 100644
--- a/apis/r/NEWS.md
+++ b/apis/r/NEWS.md
@@ -3,6 +3,7 @@
 ## Changes
 
 * Make use of timestamp ranges in libtiledbsoma
+* Simplify timestamp ranges; strengthen assumptions about `tiledb_timestamp`
 
 # tiledbsoma 1.13.0
 
diff --git a/apis/r/R/SOMAArrayBase.R b/apis/r/R/SOMAArrayBase.R
index 237db7784f..06b7a2e8ff 100644
--- a/apis/r/R/SOMAArrayBase.R
+++ b/apis/r/R/SOMAArrayBase.R
@@ -36,7 +36,7 @@ SOMAArrayBase <- R6::R6Class(
       meta[[SOMA_OBJECT_TYPE_METADATA_KEY]] <- self$class()
       meta[[SOMA_ENCODING_VERSION_METADATA_KEY]] <- SOMA_ENCODING_VERSION
       spdl::debug("[SOMAArrayBase::write_object_metadata] calling set metadata")
-      self$set_metadata(meta, c(self$tiledb_timestamp,self$tiledb_timestamp))
+      self$set_metadata(meta, self$.tiledb_timestamp_range)
     }
   )
 )
diff --git a/apis/r/R/SOMADataFrame.R b/apis/r/R/SOMADataFrame.R
index b4d81fff0c..b27c852e79 100644
--- a/apis/r/R/SOMADataFrame.R
+++ b/apis/r/R/SOMADataFrame.R
@@ -165,7 +165,7 @@ SOMADataFrame <- R6::R6Class(
                      colnames = column_names,
                      qc = value_filter,
                      dim_points = coords,
-                     timestamprange = self$tiledb_timestamp,  # NULL or two-elem vector
+                     timestamprange = self$.tiledb_timestamp_range,  # NULL or two-elem vector
                      loglevel = log_level)
       private$ctx_ptr <- rl$ctx
       TableReadIter$new(rl$sr)
diff --git a/apis/r/R/SOMADenseNDArray.R b/apis/r/R/SOMADenseNDArray.R
index 884da79b7c..11a24677ff 100644
--- a/apis/r/R/SOMADenseNDArray.R
+++ b/apis/r/R/SOMADenseNDArray.R
@@ -32,8 +32,6 @@ SOMADenseNDArray <- R6::R6Class(
     #' read. List elements can be named when specifying a subset of dimensions.
     #' @template param-result-order
     #' @param log_level Optional logging level with default value of `"warn"`.
-    #' @param timestamprange Optional POSIXct (i.e. Datetime) vector with start and end of
-    #' interval for which data is considered.
     #' @return An [`arrow::Table`].
     read_arrow_table = function(
       coords = NULL,
@@ -54,14 +52,15 @@ SOMADenseNDArray <- R6::R6Class(
       coords <- private$.convert_coords(coords)
 
       cfg <- as.character(tiledb::config(self$tiledbsoma_ctx$context()))
-      spdl::debug("[SOMADenseNDArray$read_arrow_table] timestamp ({},{})",
-                  self$tiledb_timestamp[1], self$tiledb_timestamp[2])
+      spdl::debug(
+        "[SOMADenseNDArray$read_arrow_table] timestamp ({})",
+        self$tiledb_timestamp %||% "now"
+      )
 
       rl <- soma_array_reader(uri = uri,
                               dim_points = coords,
                               result_order = result_order,
-                              timestamprange = if (is.null(self$tiledb_timestamp)) self$tiledb_timestamp
-                                               else c(0, self$tiledb_timestamp[2]),
+                              timestamprange = self$.tiledb_timestamp_range,
                               loglevel = log_level,
                               config = cfg)
 
@@ -144,8 +143,14 @@ SOMADenseNDArray <- R6::R6Class(
       nasp <- nanoarrow::nanoarrow_allocate_schema()
       arrow::as_record_batch(tbl)$export_to_c(naap, nasp)
       #arr[] <- values
-      writeArrayFromArrow(self$uri, naap, nasp, "SOMADenseNDArray", NULL,
-                          c(self$tiledb_timestamp[1], self$tiledb_timestamp[1]))
+      writeArrayFromArrow(
+        uri = self$uri,
+        naap = naap,
+        nasp = nasp,
+        arraytype = "SOMADenseNDArray",
+        config = NULL,
+        tsvec = self$.tiledb_timestamp_range
+      )
       spdl::debug("[SOMADenseNDArray::write] written")
 
       # tiledb-r always closes the array after a write operation so we need to
diff --git a/apis/r/R/SOMASparseNDArray.R b/apis/r/R/SOMASparseNDArray.R
index a6e412c2b2..3e91ea32f1 100644
--- a/apis/r/R/SOMASparseNDArray.R
+++ b/apis/r/R/SOMASparseNDArray.R
@@ -51,8 +51,7 @@ SOMASparseNDArray <- R6::R6Class(
                      config = cfg,
                      dim_points = coords,
                      result_order = result_order,
-                     timestamprange = if (is.null(self$tiledb_timestamp)) self$tiledb_timestamp
-                                      else c(0, self$tiledb_timestamp[2]),
+                     timestamprange = self$.tiledb_timestamp_range,
                      loglevel = log_level)
       private$ctx_ptr <- rl$ctx
       SOMASparseNDArrayRead$new(rl$sr, self, coords)
@@ -175,9 +174,11 @@ SOMASparseNDArray <- R6::R6Class(
         names(bbox_flat)[index:(index + 1L)] <- paste0(names(bbox)[i], c('_lower', '_upper'))
         index <- index + 2L
       }
-      self$set_metadata(bbox_flat, c(self$tiledb_timestamp[1],self$tiledb_timestamp[1]))
-      spdl::debug("[SOMASparseNDArray$write] Calling .write_coo_df ({},{})",
-                  self$tiledb_timestamp[1],self$tiledb_timestamp[2])
+      self$set_metadata(bbox_flat, self$.tiledb_timestamp_range)
+      spdl::debug(
+        "[SOMASparseNDArray$write] Calling .write_coo_df ({})",
+        self$tiledb_timestamp %||% "now"
+      )
 
       private$.write_coo_dataframe(coo)
 
@@ -244,14 +245,22 @@ SOMASparseNDArray <- R6::R6Class(
                               arrow::field(nms[3], private$.type))
 
       tbl <- arrow::arrow_table(values, schema = arrsch)
-      spdl::debug("[SOMASparseNDArray$write] array created, writing to {} at ({},{})", self$uri,
-                  self$tiledb_timestamp[1],self$tiledb_timestamp[2])
+      spdl::debug(
+        "[SOMASparseNDArray$write] array created, writing to {} at ({})",
+        self$uri,
+        self$tiledb_timestamp %||% "now"
+      )
       naap <- nanoarrow::nanoarrow_allocate_array()
       nasp <- nanoarrow::nanoarrow_allocate_schema()
       arrow::as_record_batch(tbl)$export_to_c(naap, nasp)
-      writeArrayFromArrow(self$uri, naap, nasp, "SOMASparseNDArray", NULL,
-                          if (is.null(self$tiledb_timestamp[1])) NULL
-                          else c(self$tiledb_timestamp[1], self$tiledb_timestamp[1]))
+      writeArrayFromArrow(
+        uri = self$uri,
+        naap = naap,
+        nasp = nasp,
+        arraytype = "SOMASparseNDArray",
+        config = NULL,
+        tsvec = self$.tiledb_timestamp_range
+      )
     },
 
     # Internal marking of one or zero based matrices for iterated reads
diff --git a/apis/r/R/TileDBArray.R b/apis/r/R/TileDBArray.R
index 67aa8d7b4a..22d9a01d54 100644
--- a/apis/r/R/TileDBArray.R
+++ b/apis/r/R/TileDBArray.R
@@ -31,9 +31,13 @@ TileDBArray <- R6::R6Class(
         private$.tiledb_array <- tiledb::tiledb_array_open(self$object, type = mode)
       } else {
         if (is.null(internal_use_only)) stopifnot("tiledb_timestamp not yet supported for WRITE mode" = mode == "READ")
-        spdl::debug("[TileDBArray$open] Opening {} '{}' in {} mode at ({},{})",
-                    self$class(), self$uri, mode, self$tiledb_timestamp[1],
-                    self$tiledb_timestamp[2])
+        spdl::debug(
+          "[TileDBArray$open] Opening {} '{}' in {} mode at ({})",
+          self$class(),
+          self$uri,
+          mode,
+          self$tiledb_timestamp %||% "now"
+        )
         #private$.tiledb_array <- tiledb::tiledb_array_open_at(self$object, type = mode,
         #                                                      timestamp = self$tiledb_timestamp)
       }
diff --git a/apis/r/R/TileDBObject.R b/apis/r/R/TileDBObject.R
index 0b80e14251..e23d8be3ea 100644
--- a/apis/r/R/TileDBObject.R
+++ b/apis/r/R/TileDBObject.R
@@ -39,12 +39,16 @@ TileDBObject <- R6::R6Class(
       private$.tiledb_ctx <- self$tiledbsoma_ctx$context()
 
       if (!is.null(tiledb_timestamp)) {
-        stopifnot("'tiledb_timestamp' must be a POSIXct datetime object" = inherits(tiledb_timestamp, "POSIXct"))
+        stopifnot(
+            "'tiledb_timestamp' must be a single POSIXct datetime object" = inherits(tiledb_timestamp, "POSIXct") &&
+              length(tiledb_timestamp) == 1L &&
+              !is.na(tiledb_timestamp)
+        )
         private$.tiledb_timestamp <- tiledb_timestamp
       }
 
-      spdl::debug("[TileDBObject] initialize {} with '{}' at ({},{})", self$class(), self$uri,
-                  private$.tiledb_timestamp[1],private$.tiledb_timestamp[2])
+      spdl::debug("[TileDBObject] initialize {} with '{}' at ({})", self$class(), self$uri,
+                  self$tiledb_timestamp %||% "now")
     },
 
     #' @description Print the name of the R6 class.
@@ -91,7 +95,7 @@ TileDBObject <- R6::R6Class(
       mode <- match.arg(mode, choices = c('READ', 'WRITE'))
       stopifnot(
         "'tiledb_timestamp' must be a POSIXct datetime object" = is.null(tiledb_timestamp) ||
-          inherits(tiledb_timestamp, what = "POSIXct")
+          (inherits(tiledb_timestamp, what = "POSIXct") && length(tiledb_timestamp) == 1L && !is.na(tiledb_timestamp))
       )
       self$close()
       private$.tiledb_timestamp <- tiledb_timestamp
@@ -149,6 +153,20 @@ TileDBObject <- R6::R6Class(
     uri = function(value) {
       if (missing(value)) return(private$tiledb_uri$uri)
       stop(sprintf("'%s' is a read-only field.", "uri"), call. = FALSE)
+    },
+    #' @field .tiledb_timestamp_range Time range for libtiledbsoma
+    #'
+    .tiledb_timestamp_range = function(value) {
+      if (!missing(value)) {
+        private$.read_only_error("tiledb_timestamp_range")
+      }
+      if (is.null(self$tiledb_timestamp)) {
+        return(NULL)
+      }
+      return(c(
+        as.POSIXct(0, tz = 'UTC', origin = '1970-01-01'),
+        self$tiledb_timestamp
+      ))
     }
   ),
 
diff --git a/apis/r/R/utils.R b/apis/r/R/utils.R
index 2b78da7f2c..1e5930c9f4 100644
--- a/apis/r/R/utils.R
+++ b/apis/r/R/utils.R
@@ -169,7 +169,7 @@ uns_hint <- function(type = c('1d', '2d')) {
     uri = x$uri,
     config = as.character(tiledb::config(x$tiledbsoma_ctx$context())),
     colnames = dimname,
-    timestamprange = c(0, x$tiledb_timestamp)
+    timestamprange = x$.tiledb_timestamp_range
   )
   return(TableReadIter$new(rl$sr)$concat()$GetColumnByName(dimname)$as_vector())
 }
diff --git a/apis/r/man/TileDBObject.Rd b/apis/r/man/TileDBObject.Rd
index 82c7daf2b3..ae17275794 100644
--- a/apis/r/man/TileDBObject.Rd
+++ b/apis/r/man/TileDBObject.Rd
@@ -17,6 +17,8 @@ TileDBGroup classes. (lifecycle: maturing)
 \item{\code{tiledb_timestamp}}{Time that object was opened at}
 
 \item{\code{uri}}{The URI of the TileDB object.}
+
+\item{\code{.tiledb_timestamp_range}}{Time range for libtiledbsoma}
 }
 \if{html}{\out{</div>}}
 }


Test patch:
diff --git a/apis/r/tests/testthat/test-SOMASparseNDArray.R b/apis/r/tests/testthat/test-SOMASparseNDArray.R
index c2b64b494b..a2d2881e74 100644
--- a/apis/r/tests/testthat/test-SOMASparseNDArray.R
+++ b/apis/r/tests/testthat/test-SOMASparseNDArray.R
@@ -324,13 +324,13 @@ test_that("SOMASparseNDArray timestamped ops", {
   uri <- tempfile(pattern="soma-sparse-nd-array-timestamps")
 
   # t=10: create 2x2 array and write 1 into top-left entry
-  t10 <- as.POSIXct(10, tz="UTC")
+  t10 <- as.POSIXct(10, tz = "UTC", origin = "1970-01-01")
   snda <- SOMASparseNDArrayCreate(uri=uri, type=arrow::int16(), shape=c(2,2), tiledb_timestamp=t10)
   snda$write(Matrix::sparseMatrix(i = 1, j = 1, x = 1, dims = c(2, 2)))
   snda$close()
 
   # t=20: write 1 into bottom-right entry
-  t20 <- as.POSIXct(20, tz="UTC")
+  t20 <- as.POSIXct(20, tz = "UTC", origin = "1970-01-01")
   snda <- SOMASparseNDArrayOpen(uri=uri, mode="WRITE", tiledb_timestamp=t20)
   snda$write(Matrix::sparseMatrix(i = 2, j = 2, x = 1, dims = c(2, 2)))
   snda$close()
@@ -341,7 +341,10 @@ test_that("SOMASparseNDArray timestamped ops", {
   snda$close()
 
   # read @ t=15 and see only the first write
-  snda <- SOMASparseNDArrayOpen(uri=uri, tiledb_timestamp = rep(t10 + 0.5*as.numeric(t20 - t10),2))
+  snda <- SOMASparseNDArrayOpen(
+    uri = uri,
+    tiledb_timestamp = t10 + 0.5 * as.numeric(t20 - t10)
+  )
   expect_equal(sum(snda$read()$sparse_matrix()$concat()), 1)
   snda$close()
 })
diff --git a/apis/r/tests/testthat/test-Timestamps.R b/apis/r/tests/testthat/test-Timestamps.R
index 4d29a6df50..3e85b4b636 100644
--- a/apis/r/tests/testthat/test-Timestamps.R
+++ b/apis/r/tests/testthat/test-Timestamps.R
@@ -7,21 +7,21 @@ test_that("SOMADataFrame", {
                                                                value_type = arrow::utf8())))
 
     ## create at t = 1
-    ts1 <- rep(as.POSIXct(1, tz="UTC"), 2)
-    sdf <- tiledbsoma::SOMADataFrameCreate(uri, sch, tiledb_timestamp=ts1[1])
+    ts1 <- as.POSIXct(1, tz = "UTC", origin = "1970-01-01")
+    sdf <- tiledbsoma::SOMADataFrameCreate(uri, sch, tiledb_timestamp = ts1)
 
     ## write part1 at t = 2
     dat2 <- arrow::arrow_table(soma_joinid = bit64::as.integer64(1L:5L),
                                int = 101:105L,
                                str = factor(c('a','b','b','a','b')))
-    ts2 <- rep(as.POSIXct(2, tz="UTC"), 2)
+    ts2 <- as.POSIXct(2, tz = "UTC", origin = "1970-01-01")
     expect_silent(sdf$write(dat2, ts2))
 
     ## write part2 at t = 3
     dat3 <- arrow::arrow_table(soma_joinid = bit64::as.integer64(6L:10L),
                                int = 106:110L,
                                str = factor(c('c','b','c','c','b')))
-    ts3 <- rep(as.POSIXct(3, tz="UTC"), 2)
+    ts3 <- as.POSIXct(3, tz = "UTC", origin = "1970-01-01")
     expect_silent(sdf$write(dat3, ts3))
     sdf$close()
 
@@ -31,50 +31,55 @@ test_that("SOMADataFrame", {
     expect_equal(dim(res), c(10, 3)) 					# two writes lead to 10x3 data
     expect_equal(levels(res$str), c("a", "b", "c"))     # string variable has three values
 
-    ## read before data is written
-    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp=ts1)
+    ## read before data is written (tiledb_timestamp_range = <origin, ts1>)
+    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp = ts1)
     res <- sdf$read()$concat()
     expect_equal(dim(res), c(0, 3))
 
-    ## read at ts2
-    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp=ts2)
+    ## read at ts2 (tiledb_timestamp_range = <origin, ts2>)
+    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp = ts2)
     res <- tibble::as_tibble(sdf$read()$concat())
     expect_equal(dim(res), c(5, 3))
     expect_equal(max(res$int), 105L)
     expect_equal(range(res$int), c(101L,105L))
 
-    ## read at ts3
-    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp=ts3)
+    ## read at ts3 (tiledb_timestamp_range = <origin, ts3>)
+    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp = ts3)
     res <- tibble::as_tibble(sdf$read()$concat())
-    expect_equal(dim(res), c(5, 3))
+    expect_equal(dim(res), c(10L, 3L))
     expect_equal(max(res$int), 110L)
-    expect_equal(range(res$int), c(106L,110L))
+    expect_equal(range(res$int), c(101L, 110L))
 
-    ## read after ts3
-    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp=ts3 + c(1,1))
+    ## read after ts3 (tiledb_timestamp_range = <origin, ts3 + 1>)
+    sdf <- tiledbsoma::SOMADataFrameOpen(uri, tiledb_timestamp = ts3 + 1)
     res <- tibble::as_tibble(sdf$read()$concat())
     res <- sdf$read()$concat()
-    expect_equal(dim(res), c(0, 3))
+    expect_equal(dim(res), c(10L, 3L))
 })
 
 test_that("SOMANDSparseArray", {
     uri <- tempfile()
 
     ## create at t = 2, also writes at t = 2 as timestamp is cached
-    ts1 <- rep(as.POSIXct(2, tz="UTC"), 2)
-    snda <- SOMASparseNDArrayCreate(uri, arrow::int32(), shape = c(10, 10), tiledb_timestamp=ts1[1])
+    ts1 <- as.POSIXct(2, tz = "UTC", origin = "1970-01-01")
+    snda <- SOMASparseNDArrayCreate(
+        uri,
+        arrow::int32(),
+        shape = c(10, 10),
+        tiledb_timestamp = ts1
+    )
     mat <- create_sparse_matrix_with_int_dims(10, 10)
     snda$write(mat) 		# write happens at create time
 
     ## read at t = 3, expect all rows as read is from (0, 3)
-    ts2 <- rep(as.POSIXct(3, tz="UTC"), 2)
-    snda <- tiledbsoma::SOMASparseNDArrayOpen(uri, tiledb_timestamp=ts2)
+    ts2 <- as.POSIXct(3, tz = "UTC", origin = "1970-01-01")
+    snda <- tiledbsoma::SOMASparseNDArrayOpen(uri, tiledb_timestamp = ts2)
     res <- snda$read()$tables()$concat()
     expect_equal(dim(res), c(60,3))
 
     ## read at t = 1, expect zero rows as read is from (0, 1)
-    ts3 <- rep(as.POSIXct(1, tz="UTC"), 2)
-    snda <- tiledbsoma::SOMASparseNDArrayOpen(uri, tiledb_timestamp=ts3)
+    ts3 <- as.POSIXct(1, tz = "UTC", origin = "1970-01-01")
+    snda <- tiledbsoma::SOMASparseNDArrayOpen(uri, tiledb_timestamp = ts3)
     res <- snda$read()$tables()$concat()
     expect_equal(dim(res), c(0,3))
 
@@ -84,22 +89,27 @@ test_that("SOMANDDenseArray", {
     uri <- tempfile()
 
     ## create at t = 2, also writes at t = 2 as timestamp is cached
-    ts1 <- rep(as.POSIXct(2, tz="UTC"), 2)
-    dnda <- SOMADenseNDArrayCreate(uri, arrow::int32(), shape = c(5, 2), tiledb_timestamp=ts1[1])
+    ts1 <- as.POSIXct(2, tz = "UTC", origin = "1970-01-01")
+    dnda <- SOMADenseNDArrayCreate(
+        uri,
+        type = arrow::int32(),
+        shape = c(5, 2),
+        tiledb_timestamp = ts1
+    )
     mat <- create_dense_matrix_with_int_dims(5, 2)
     expect_silent(dnda$write(mat)) 		# write happens at create time
 
     ## read at t = 3, expect all rows as read is from (0, 3)
-    ts2 <- rep(as.POSIXct(3, tz="UTC"), 2)
-    dnda <- tiledbsoma::SOMADenseNDArrayOpen(uri, tiledb_timestamp=ts2)
+    ts2 <- as.POSIXct(3, tz = "UTC", origin = "1970-01-01")
+    dnda <- tiledbsoma::SOMADenseNDArrayOpen(uri, tiledb_timestamp = ts2)
     res <- dnda$read_arrow_table()
     expect_equal(dim(res), c(10,1))
 
     ## read at t = 1, expect zero rows as read is from (0, 1)
     ## NB that this requires a) nullable arrow schema and b) na.omit() on result
     ## It also works without a) as the fill value is also NA
-    ts3 <- rep(as.POSIXct(1, tz="UTC"), 2)
-    dnda <- tiledbsoma::SOMADenseNDArrayOpen(uri, tiledb_timestamp=ts3)
+    ts3 <- as.POSIXct(1, tz = "UTC", origin = "1970-01-01")
+    dnda <- tiledbsoma::SOMADenseNDArrayOpen(uri, tiledb_timestamp = ts3)
     res <- dnda$read_arrow_table()
     #print(res$soma_data)
     vec <- tibble::as_tibble(res)