This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 30d26952b0 [arrow] Fix nanosecond timestamp conversion for pre-epoch
values (#9299)
30d26952b0 is described below
commit 30d26952b0e6c0e21850f300bde5dafa750306fd
Author: jackylee <[email protected]>
AuthorDate: Thu Aug 20 09:44:10 2026 +0800
[arrow] Fix nanosecond timestamp conversion for pre-epoch values (#9299)
---
.../converter/Arrow2PaimonVectorConverter.java | 3 +-
.../paimon/arrow/vector/ArrowFormatWriterTest.java | 39 ++++++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git
a/paimon-arrow/src/main/java/org/apache/paimon/arrow/converter/Arrow2PaimonVectorConverter.java
b/paimon-arrow/src/main/java/org/apache/paimon/arrow/converter/Arrow2PaimonVectorConverter.java
index e3a9a53d51..4ce6871809 100644
---
a/paimon-arrow/src/main/java/org/apache/paimon/arrow/converter/Arrow2PaimonVectorConverter.java
+++
b/paimon-arrow/src/main/java/org/apache/paimon/arrow/converter/Arrow2PaimonVectorConverter.java
@@ -462,7 +462,8 @@ public interface Arrow2PaimonVectorConverter {
} else if (precision >= 4 && precision <= 6) {
return Timestamp.fromMicros(value);
} else {
- return Timestamp.fromEpochMillis(value / 1_000_000, (int)
(value % 1_000_000));
+ return Timestamp.fromEpochMillis(
+ Math.floorDiv(value, 1_000_000L), (int)
Math.floorMod(value, 1_000_000L));
}
}
diff --git
a/paimon-arrow/src/test/java/org/apache/paimon/arrow/vector/ArrowFormatWriterTest.java
b/paimon-arrow/src/test/java/org/apache/paimon/arrow/vector/ArrowFormatWriterTest.java
index fd257450aa..d8cfd8fde9 100644
---
a/paimon-arrow/src/test/java/org/apache/paimon/arrow/vector/ArrowFormatWriterTest.java
+++
b/paimon-arrow/src/test/java/org/apache/paimon/arrow/vector/ArrowFormatWriterTest.java
@@ -53,6 +53,8 @@ import org.apache.arrow.vector.IntVector;
import org.apache.arrow.vector.TimeMicroVector;
import org.apache.arrow.vector.TimeNanoVector;
import org.apache.arrow.vector.TimeSecVector;
+import org.apache.arrow.vector.TimeStampNanoTZVector;
+import org.apache.arrow.vector.TimeStampNanoVector;
import org.apache.arrow.vector.VarBinaryVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
@@ -587,6 +589,43 @@ public class ArrowFormatWriterTest {
}
}
+ @Test
+ public void testArrowBundleRecordsWithPreEpochNanoTimestamps() {
+ RowType rowType =
+ RowType.of(
+ new DataField(0, "ts_nano", DataTypes.TIMESTAMP(9)),
+ new DataField(
+ 1, "ts_ltz_nano",
DataTypes.TIMESTAMP_WITH_LOCAL_TIME_ZONE(9)));
+
+ // 1969-12-31T23:59:59.999999999, i.e. one nanosecond before the epoch
+ long nanos = -1L;
+
+ try (RootAllocator allocator = new RootAllocator()) {
+ TimeStampNanoVector tsVector = new TimeStampNanoVector("ts_nano",
allocator);
+ tsVector.allocateNew(1);
+ tsVector.setSafe(0, nanos);
+ tsVector.setValueCount(1);
+
+ TimeStampNanoTZVector tsLtzVector =
+ new TimeStampNanoTZVector("ts_ltz_nano", allocator, "UTC");
+ tsLtzVector.allocateNew(1);
+ tsLtzVector.setSafe(0, nanos);
+ tsLtzVector.setValueCount(1);
+
+ List<FieldVector> vectors = Arrays.asList(tsVector, tsLtzVector);
+ try (VectorSchemaRoot vectorSchemaRoot = new
VectorSchemaRoot(vectors)) {
+ vectorSchemaRoot.setRowCount(1);
+
+ Iterator<InternalRow> iterator =
+ new ArrowBundleRecords(vectorSchemaRoot, rowType,
true).iterator();
+ InternalRow row = iterator.next();
+ Timestamp expected = Timestamp.fromEpochMillis(-1, 999_999);
+ assertThat(row.getTimestamp(0, 9)).isEqualTo(expected);
+ assertThat(row.getTimestamp(1, 9)).isEqualTo(expected);
+ }
+ }
+ }
+
@Test
public void testCWriter() {
try (ArrowFormatCWriter writer = new
ArrowFormatCWriter(PRIMITIVE_TYPE, 4096, true)) {