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