diff --git a/arrow/array/timestamp.go b/arrow/array/timestamp.go index 5ac0ee6c..cf17b31f 100644 --- a/arrow/array/timestamp.go +++ b/arrow/array/timestamp.go @@ -336,6 +336,11 @@ func (b *TimestampBuilder) AppendValueFromString(s string) error { } func (b *TimestampBuilder) UnmarshalOne(dec *json.Decoder) error { + loc, err := b.dtype.GetZone() + if err != nil { + return err + } + t, err := dec.Token() if err != nil { return err @@ -349,7 +354,6 @@ func (b *TimestampBuilder) UnmarshalOne(dec *json.Decoder) error { b.Append(arrow.Timestamp(i)) break } - loc, _ := b.dtype.GetZone() tm, _, err := arrow.TimestampFromStringInLocation(v, b.dtype.Unit, loc) if err != nil { return &json.UnmarshalTypeError{ diff --git a/arrow/array/timestamp_test.go b/arrow/array/timestamp_test.go index 8242bf7a..6dcdeba9 100644 --- a/arrow/array/timestamp_test.go +++ b/arrow/array/timestamp_test.go @@ -17,6 +17,7 @@ package array_test import ( + "strings" "testing" "time" @@ -260,6 +261,36 @@ func TestTimestampValueStr(t *testing.T) { assert.Equal(t, "2016-02-29T10:42:23-07:00", arr.ValueStr(1)) } +func TestTimestampBuilderUnmarshalRejectsInvalidTimezone(t *testing.T) { + for _, tc := range []struct { + name string + value string + useNumber bool + }{ + {name: "timestamp string", value: `"2024-01-01T00:00:00"`}, + {name: "integer string", value: `"0"`}, + {name: "json number", value: `0`, useNumber: true}, + {name: "float64", value: `0`}, + } { + t.Run(tc.name, func(t *testing.T) { + mem := memory.NewCheckedAllocator(memory.NewGoAllocator()) + defer mem.AssertSize(t, 0) + + dt := &arrow.TimestampType{Unit: arrow.Second, TimeZone: "not/a_timezone"} + b := array.NewTimestampBuilder(mem, dt) + defer b.Release() + + dec := json.NewDecoder(strings.NewReader(tc.value)) + if tc.useNumber { + dec.UseNumber() + } + err := b.UnmarshalOne(dec) + assert.ErrorContains(t, err, "could not find timezone location") + assert.Zero(t, b.Len()) + }) + } +} + func TestTimestampValueStrWithDeprecatedLayout(t *testing.T) { mem := memory.NewCheckedAllocator(memory.NewGoAllocator()) defer mem.AssertSize(t, 0) diff --git a/arrow/compute/cast_test.go b/arrow/compute/cast_test.go index 74d66541..8cb495cb 100644 --- a/arrow/compute/cast_test.go +++ b/arrow/compute/cast_test.go @@ -410,12 +410,23 @@ func (c *CastSuite) TestCanCast() { } func (c *CastSuite) checkCastFails(dt arrow.DataType, input string, opts *compute.CastOptions) { - inArr, _, _ := array.FromJSON(c.mem, dt, strings.NewReader(input)) + inArr, _, err := array.FromJSON(c.mem, dt, strings.NewReader(input)) + require.NoError(c.T(), err) defer inArr.Release() checkCastFails(c.T(), inArr, *opts) } +func (c *CastSuite) checkInvalidTimezoneCastFails(dt *arrow.TimestampType, opts *compute.CastOptions) { + bldr := array.NewTimestampBuilder(c.mem, dt) + defer bldr.Release() + bldr.Append(0) + + inArr := bldr.NewArray() + defer inArr.Release() + checkCastFails(c.T(), inArr, *opts) +} + func (c *CastSuite) checkCastOpts(dtIn, dtOut arrow.DataType, inJSON, outJSON string, opts compute.CastOptions) { inArr, _, _ := array.FromJSON(c.mem, dtIn, strings.NewReader(inJSON)) outArr, _, _ := array.FromJSON(c.mem, dtOut, strings.NewReader(outJSON)) @@ -2750,8 +2761,8 @@ func (c *CastSuite) TestZonedTimestampToDate() { // invalid timezones for _, u := range []arrow.TimeUnit{arrow.Second, arrow.Millisecond, arrow.Microsecond, arrow.Nanosecond} { dt := &arrow.TimestampType{Unit: u, TimeZone: "Mars/Mariner_Valley"} - c.checkCastFails(dt, timestampSecondsJSON, compute.NewCastOptions(arrow.FixedWidthTypes.Date32, false)) - c.checkCastFails(dt, timestampSecondsJSON, compute.NewCastOptions(arrow.FixedWidthTypes.Date64, false)) + c.checkInvalidTimezoneCastFails(dt, compute.NewCastOptions(arrow.FixedWidthTypes.Date32, false)) + c.checkInvalidTimezoneCastFails(dt, compute.NewCastOptions(arrow.FixedWidthTypes.Date64, false)) } } @@ -2873,9 +2884,9 @@ func (c *CastSuite) TestTimestampToTime() { dt := &arrow.TimestampType{Unit: u, TimeZone: "Mars/Mariner_Valley"} switch u { case arrow.Second, arrow.Millisecond: - c.checkCastFails(dt, timestampSecondsJSON, compute.NewCastOptions(&arrow.Time32Type{Unit: u}, false)) + c.checkInvalidTimezoneCastFails(dt, compute.NewCastOptions(&arrow.Time32Type{Unit: u}, false)) default: - c.checkCastFails(dt, timestampSecondsJSON, compute.NewCastOptions(&arrow.Time64Type{Unit: u}, false)) + c.checkInvalidTimezoneCastFails(dt, compute.NewCastOptions(&arrow.Time64Type{Unit: u}, false)) } } }