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

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


The following commit(s) were added to refs/heads/main by this push:
     new cb58fab6 fix(arrow/scalar): apply timestamp zones when casting to 
dates (#1110)
cb58fab6 is described below

commit cb58fab6c34f836a4b4c53a51f086616a5432d98
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 10 18:52:38 2026 +0200

    fix(arrow/scalar): apply timestamp zones when casting to dates (#1110)
    
    ### Rationale for this change
    
    Timestamp-to-Date32 and timestamp-to-Date64 casts use integer arithmetic
    that truncates negative values and ignores the timestamp timezone.
    
    ### What changes are included in this PR?
    
    Convert timestamps through the timestamp type's time semantics and reuse
    the date normalization used by the compute cast path. Add regression
    coverage around the Unix epoch and a named timezone.
    
    ### Are these changes tested?
    
    - `go test ./arrow/scalar`
    
    ### Are there any user-facing changes?
    
    No API changes. This corrects the reported behavior while preserving the
    existing ownership and compatibility contracts.
---
 arrow/scalar/scalar_test.go | 17 +++++++++++++++++
 arrow/scalar/temporal.go    | 26 ++++++++++++++++++++++----
 2 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/arrow/scalar/scalar_test.go b/arrow/scalar/scalar_test.go
index 1627aef9..36557f2f 100644
--- a/arrow/scalar/scalar_test.go
+++ b/arrow/scalar/scalar_test.go
@@ -556,6 +556,23 @@ func TestTimestampScalarsCasting(t *testing.T) {
        tms, err = 
scalar.NewDate32Scalar(arrow.Date32(1024)).CastTo(arrow.FixedWidthTypes.Timestamp_ms)
        assert.NoError(t, err)
        assert.True(t, scalar.Equals(tms, 
scalar.NewTimestampScalar(arrow.Timestamp(1024*millisInDay), 
arrow.FixedWidthTypes.Timestamp_ms)))
+
+       negativeDate32, err := scalar.NewTimestampScalar(-1, 
arrow.FixedWidthTypes.Timestamp_ms).CastTo(arrow.FixedWidthTypes.Date32)
+       assert.NoError(t, err)
+       assert.Equal(t, arrow.Date32(-1), negativeDate32.(*scalar.Date32).Value)
+
+       negativeDate64, err := scalar.NewTimestampScalar(-1, 
arrow.FixedWidthTypes.Timestamp_ms).CastTo(arrow.FixedWidthTypes.Date64)
+       assert.NoError(t, err)
+       assert.Equal(t, arrow.Date64(-millisInDay), 
negativeDate64.(*scalar.Date64).Value)
+
+       localTimestamp := &arrow.TimestampType{Unit: arrow.Millisecond, 
TimeZone: "America/Los_Angeles"}
+       localDate, err := scalar.NewTimestampScalar(0, 
localTimestamp).CastTo(arrow.FixedWidthTypes.Date32)
+       assert.NoError(t, err)
+       assert.Equal(t, arrow.Date32(-1), localDate.(*scalar.Date32).Value)
+
+       localDate64, err := scalar.NewTimestampScalar(0, 
localTimestamp).CastTo(arrow.FixedWidthTypes.Date64)
+       assert.NoError(t, err)
+       assert.Equal(t, arrow.Date64(-millisInDay), 
localDate64.(*scalar.Date64).Value)
 }
 
 func TestDurationScalarBasics(t *testing.T) {
diff --git a/arrow/scalar/temporal.go b/arrow/scalar/temporal.go
index c52ad612..d0f35011 100644
--- a/arrow/scalar/temporal.go
+++ b/arrow/scalar/temporal.go
@@ -104,6 +104,18 @@ type IntervalScalar interface {
 
 const millisecondsInDay = (time.Hour * 24) / time.Millisecond
 
+func timestampDate(s *Timestamp) (time.Time, error) {
+       timestampType := s.DataType().(*arrow.TimestampType)
+       toTime, err := timestampType.GetToTimeFunc()
+       if err != nil {
+               return time.Time{}, err
+       }
+
+       tm := toTime(s.Value)
+       year, month, day := tm.Date()
+       return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil
+}
+
 func castTemporal(from TemporalScalar, to arrow.DataType) (Scalar, error) {
        if arrow.TypeEqual(from.DataType(), to) {
                return from, nil
@@ -149,11 +161,17 @@ func castTemporal(from TemporalScalar, to arrow.DataType) 
(Scalar, error) {
                case *arrow.TimestampType:
                        return 
NewTimestampScalar(arrow.Timestamp(arrow.ConvertTimestampValue(s.Unit(), 
to.Unit, int64(s.Value))), to), nil
                case *arrow.Date32Type:
-                       millis := arrow.ConvertTimestampValue(s.Unit(), 
arrow.Millisecond, int64(s.Value))
-                       return NewDate32Scalar(arrow.Date32(millis / 
int64(millisecondsInDay))), nil
+                       tm, err := timestampDate(s)
+                       if err != nil {
+                               return nil, err
+                       }
+                       return NewDate32Scalar(arrow.Date32FromTime(tm)), nil
                case *arrow.Date64Type:
-                       millis := arrow.ConvertTimestampValue(s.Unit(), 
arrow.Millisecond, int64(s.Value))
-                       return NewDate64Scalar(arrow.Date64(millis - 
millis%int64(millisecondsInDay))), nil
+                       tm, err := timestampDate(s)
+                       if err != nil {
+                               return nil, err
+                       }
+                       return NewDate64Scalar(arrow.Date64FromTime(tm)), nil
                }
        case TimeScalar:
                var value int64

Reply via email to