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

laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 9cc9211c9 fix(types): floor negative nanosecond timestamps (#1586)
9cc9211c9 is described below

commit 9cc9211c9a8497572e9e4b4b7e1ec5ff0144011e
Author: Minh Vu <[email protected]>
AuthorDate: Thu Jul 30 09:55:54 2026 +0200

    fix(types): floor negative nanosecond timestamps (#1586)
    
    ## What changed
    
    Use signed floor division when converting nanosecond timestamps to
    microseconds. Add boundary coverage around zero and verify casts to both
    timestamp variants.
    
    ## Why
    
    Go integer division truncates toward zero. For pre-epoch timestamps with
    sub-microsecond precision, that could move the converted value forward
    by one microsecond and even cross the Unix epoch.
    
    ## Testing
    
    - `go test .`
    
    Signed-off-by: Minh Vu <[email protected]>
---
 literals_test.go | 34 ++++++++++++++++++++++++++++++++--
 types.go         |  2 +-
 2 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/literals_test.go b/literals_test.go
index 42875df10..e0030092f 100644
--- a/literals_test.go
+++ b/literals_test.go
@@ -201,8 +201,8 @@ func TestTimestampNanoLiteralConversions(t *testing.T) {
                {
                        name:              "negative timestamp",
                        nanoLit:           
iceberg.TimestampNsLiteral(-1234567890123456789),
-                       expectedMicro:     iceberg.Timestamp(-1234567890123456),
-                       expectedRoundTrip: 
iceberg.TimestampNano(-1234567890123456000),
+                       expectedMicro:     iceberg.Timestamp(-1234567890123457),
+                       expectedRoundTrip: 
iceberg.TimestampNano(-1234567890123457000),
                },
                {
                        name:              "maximum precision truncation",
@@ -235,6 +235,36 @@ func TestTimestampNanoLiteralConversions(t *testing.T) {
        }
 }
 
+func TestTimestampNanoToMicrosFloorsNegativeValues(t *testing.T) {
+       tests := []struct {
+               nanos  iceberg.TimestampNano
+               micros iceberg.Timestamp
+       }{
+               {nanos: -1001, micros: -2},
+               {nanos: -1000, micros: -1},
+               {nanos: -999, micros: -1},
+               {nanos: -1, micros: -1},
+               {nanos: 0, micros: 0},
+               {nanos: 1, micros: 0},
+               {nanos: 999, micros: 0},
+               {nanos: 1000, micros: 1},
+               {nanos: 1001, micros: 1},
+       }
+
+       for _, tt := range tests {
+               assert.Equal(t, tt.micros, tt.nanos.ToMicros())
+
+               for _, target := range []iceberg.Type{
+                       iceberg.PrimitiveTypes.Timestamp,
+                       iceberg.PrimitiveTypes.TimestampTz,
+               } {
+                       converted, err := 
iceberg.TimestampNsLiteral(tt.nanos).To(target)
+                       require.NoError(t, err)
+                       assert.Equal(t, iceberg.TimestampLiteral(tt.micros), 
converted)
+               }
+       }
+}
+
 func TestTimestampLiteralToNanosRejectsOverflow(t *testing.T) {
        targets := []iceberg.Type{
                iceberg.PrimitiveTypes.TimestampNs,
diff --git a/types.go b/types.go
index 3dad8a2a3..f19bd1003 100644
--- a/types.go
+++ b/types.go
@@ -799,7 +799,7 @@ func (t TimestampNano) ToTime() time.Time {
 }
 
 func (t TimestampNano) ToMicros() Timestamp {
-       return Timestamp(int64(t) / 1000)
+       return Timestamp(internal.FloorDiv(int64(t), 1000))
 }
 
 func (t TimestampNano) ToDate() Date {

Reply via email to