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 2900ed6  fix(arrow/array): update timestamp json format (#450)
2900ed6 is described below

commit 2900ed6f8ae27398d825b2fde2f67d77dcf780b4
Author: Matt Topol <[email protected]>
AuthorDate: Sun Jul 27 16:42:20 2025 -0400

    fix(arrow/array): update timestamp json format (#450)
    
    ### Rationale for this change
    resolves #445
    
    ### What changes are included in this PR?
    Use `time.RFC3339Nano` for the format to output the time value
    
    ### Are these changes tested?
    Yes
    
    ### Are there any user-facing changes?
    Changes the format of timestamp strings for `ValueStr` and marshalling
    to JSON to match JSON and Go conventions.
---
 arrow/array/timestamp.go        |  2 +-
 arrow/array/timestamp_test.go   | 56 +++++++++++++++++++++++++++++++++++------
 arrow/avro/testdata/testdata.go |  8 +++---
 3 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/arrow/array/timestamp.go b/arrow/array/timestamp.go
index ff9ff4b..3f11c68 100644
--- a/arrow/array/timestamp.go
+++ b/arrow/array/timestamp.go
@@ -93,7 +93,7 @@ func (a *Timestamp) ValueStr(i int) string {
        }
 
        toTime, _ := a.DataType().(*arrow.TimestampType).GetToTimeFunc()
-       return toTime(a.values[i]).Format("2006-01-02 15:04:05.999999999Z0700")
+       return toTime(a.values[i]).Format(time.RFC3339Nano)
 }
 
 func (a *Timestamp) GetOneForMarshal(i int) interface{} {
diff --git a/arrow/array/timestamp_test.go b/arrow/array/timestamp_test.go
index 2dba3cf..f0dd838 100644
--- a/arrow/array/timestamp_test.go
+++ b/arrow/array/timestamp_test.go
@@ -23,7 +23,9 @@ import (
        "github.com/apache/arrow-go/v18/arrow"
        "github.com/apache/arrow-go/v18/arrow/array"
        "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/apache/arrow-go/v18/internal/json"
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 )
 
 func TestTimestampStringRoundTrip(t *testing.T) {
@@ -248,8 +250,8 @@ func TestTimestampValueStr(t *testing.T) {
        arr := b.NewArray()
        defer arr.Release()
 
-       assert.Equal(t, "1968-11-30 13:30:45-0700", arr.ValueStr(0))
-       assert.Equal(t, "2016-02-29 10:42:23-0700", arr.ValueStr(1))
+       assert.Equal(t, "1968-11-30T13:30:45-07:00", arr.ValueStr(0))
+       assert.Equal(t, "2016-02-29T10:42:23-07:00", arr.ValueStr(1))
 }
 
 func TestTimestampEquality(t *testing.T) {
@@ -278,16 +280,16 @@ func TestTimestampEquality(t *testing.T) {
 
        // No timezone, "wall clock" semantics
        // These timestamps have no actual timezone, but we still represent as 
UTC per Go conventions
-       assert.Equal(t, "1968-11-30 20:30:45Z", arrs[0].ValueStr(0))
-       assert.Equal(t, "2016-02-29 17:42:23Z", arrs[0].ValueStr(1))
+       assert.Equal(t, "1968-11-30T20:30:45Z", arrs[0].ValueStr(0))
+       assert.Equal(t, "2016-02-29T17:42:23Z", arrs[0].ValueStr(1))
 
        // UTC timezone, "instant" semantics
-       assert.Equal(t, "1968-11-30 20:30:45Z", arrs[1].ValueStr(0))
-       assert.Equal(t, "2016-02-29 17:42:23Z", arrs[1].ValueStr(1))
+       assert.Equal(t, "1968-11-30T20:30:45Z", arrs[1].ValueStr(0))
+       assert.Equal(t, "2016-02-29T17:42:23Z", arrs[1].ValueStr(1))
 
        // America/Phoenix timezone, "instant" semantics
-       assert.Equal(t, "1968-11-30 13:30:45-0700", arrs[2].ValueStr(0))
-       assert.Equal(t, "2016-02-29 10:42:23-0700", arrs[2].ValueStr(1))
+       assert.Equal(t, "1968-11-30T13:30:45-07:00", arrs[2].ValueStr(0))
+       assert.Equal(t, "2016-02-29T10:42:23-07:00", arrs[2].ValueStr(1))
 
        // Despite timezone and semantics, the physical values are equivalent
        assert.Equal(t, arrs[0].Value(0), arrs[1].Value(0))
@@ -298,3 +300,41 @@ func TestTimestampEquality(t *testing.T) {
        assert.Equal(t, arrs[0].Value(1), arrs[2].Value(1))
        assert.Equal(t, arrs[1].Value(1), arrs[2].Value(1))
 }
+
+func TestTimestampArrayJSONRoundTrip(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+       defer mem.AssertSize(t, 0)
+
+       tz, _ := time.LoadLocation("America/Phoenix")
+       dt := &arrow.TimestampType{Unit: arrow.Second, TimeZone: tz.String()}
+       b := array.NewTimestampBuilder(mem, dt)
+       defer b.Release()
+
+       b.Append(-34226955)
+       b.Append(1456767743)
+
+       arr := b.NewArray()
+       defer arr.Release()
+
+       assert.Equal(t, "1968-11-30T13:30:45-07:00", arr.ValueStr(0))
+       assert.Equal(t, "2016-02-29T10:42:23-07:00", arr.ValueStr(1))
+       assert.Equal(t, 2, arr.Len())
+       assert.Equal(t, 0, arr.NullN())
+
+       json_bytes, err := arr.MarshalJSON()
+       require.NoError(t, err)
+
+       expectedJSON := 
`["1968-11-30T13:30:45-07:00","2016-02-29T10:42:23-07:00"]`
+       require.Equal(t, expectedJSON, string(json_bytes))
+
+       var timestamps []time.Time
+       err = json.Unmarshal(json_bytes, &timestamps)
+       require.NoError(t, err)
+       require.Len(t, timestamps, 2)
+
+       expectedTime1 := time.Date(1968, time.November, 30, 13, 30, 45, 0, tz)
+       expectedTime2 := time.Date(2016, time.February, 29, 10, 42, 23, 0, tz)
+
+       assert.Equal(t, expectedTime1.Unix(), timestamps[0].Unix())
+       assert.Equal(t, expectedTime2.Unix(), timestamps[1].Unix())
+}
diff --git a/arrow/avro/testdata/testdata.go b/arrow/avro/testdata/testdata.go
index 926c054..cb9c37e 100644
--- a/arrow/avro/testdata/testdata.go
+++ b/arrow/avro/testdata/testdata.go
@@ -50,16 +50,16 @@ func (b ByteArray) MarshalJSON() ([]byte, error) {
 type TimestampMicros int64
 
 func (t TimestampMicros) MarshalJSON() ([]byte, error) {
-       ts := time.Unix(0, 
int64(t)*int64(time.Microsecond)).UTC().Format("2006-01-02 15:04:05.000000")
+       ts := time.Unix(0, 
int64(t)*int64(time.Microsecond)).UTC().Format(time.RFC3339Nano)
        // arrow record marshaller trims trailing zero digits from timestamp so 
we do the same
-       return json.Marshal(fmt.Sprintf("%sZ", strings.TrimRight(ts, "0.")))
+       return json.Marshal(ts)
 }
 
 type TimestampMillis int64
 
 func (t TimestampMillis) MarshalJSON() ([]byte, error) {
-       ts := time.Unix(0, 
int64(t)*int64(time.Millisecond)).UTC().Format("2006-01-02 15:04:05.000")
-       return json.Marshal(fmt.Sprintf("%sZ", strings.TrimRight(ts, "0.")))
+       ts := time.Unix(0, 
int64(t)*int64(time.Millisecond)).UTC().Format(time.RFC3339Nano)
+       return json.Marshal(ts)
 }
 
 type TimeMillis time.Duration

Reply via email to