diff --git a/internal/decoder.go b/internal/decoder.go index aa9aef1..5568894 100644 --- a/internal/decoder.go +++ b/internal/decoder.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" "strconv" + "time" "github.com/goccy/go-json" ) @@ -73,11 +74,14 @@ func decodeFromValueLayout(layout *ValueLayout) (Value, error) { } return TimeValue(t), nil case TimestampValueType: - unixnano, err := strconv.ParseInt(layout.Body, 10, 64) + microsec, err := strconv.ParseInt(layout.Body, 10, 64) + microSecondsInSecond := int64(time.Second) / int64(time.Microsecond) + sec := microsec / microSecondsInSecond + remainder := microsec - (sec * microSecondsInSecond) if err != nil { - return nil, fmt.Errorf("failed to parse unixnano for timestamp value %s: %w", layout.Body, err) + return nil, fmt.Errorf("failed to parse unixmicro for timestamp value %s: %w", layout.Body, err) } - return TimestampValue(timeFromUnixNano(unixnano)), nil + return TimestampValue(time.Unix(sec, remainder*int64(time.Microsecond))), nil case IntervalValueType: return parseInterval(layout.Body) case JsonValueType: diff --git a/internal/encoder.go b/internal/encoder.go index 3be191e..f35ee58 100644 --- a/internal/encoder.go +++ b/internal/encoder.go @@ -162,12 +162,11 @@ func ValueFromZetaSQLValue(v types.Value) (Value, error) { case types.TIME: return timeValueFromLiteral(v.ToPacked64TimeMicros()) case types.TIMESTAMP: - nanosec, err := v.ToUnixNanos() - if err != nil { - return nil, err - } - sec := nanosec / int64(time.Second) - return timestampValueFromLiteral(time.Unix(sec, nanosec-sec*int64(time.Second))) + microsec := v.ToUnixMicros() + microSecondsInSecond := int64(time.Second) / int64(time.Microsecond) + sec := microsec / microSecondsInSecond + remainder := microsec - (sec * microSecondsInSecond) + return timestampValueFromLiteral(time.Unix(sec, remainder*int64(time.Microsecond))) case types.NUMERIC, types.BIG_NUMERIC: return numericValueFromLiteral(v.SQLLiteral(0)) case types.INTERVAL: @@ -659,7 +658,7 @@ func valueLayoutFromValue(v Value) (*ValueLayout, error) { case TimestampValue: return &ValueLayout{ Header: TimestampValueType, - Body: fmt.Sprint(time.Time(vv).UnixNano()), + Body: fmt.Sprint(time.Time(vv).UnixMicro()), }, nil case *IntervalValue: s, err := vv.ToString() diff --git a/query_test.go b/query_test.go index 31d13f3..4005a51 100644 --- a/query_test.go +++ b/query_test.go @@ -3981,6 +3981,16 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH {createTimestampFormatFromTime(now.UTC())}, }, }, + { + name: "minimum / maximum timestamp value uses microsecond precision and range", + query: `SELECT TIMESTAMP '0001-01-01 00:00:00.000000+00', TIMESTAMP '9999-12-31 23:59:59.999999+00'`, + expectedRows: [][]interface{}{ + { + createTimestampFormatFromTime(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)), + createTimestampFormatFromTime(time.Date(9999, 12, 31, 23, 59, 59, 999999000, time.UTC)), + }, + }, + }, { name: "string", query: `SELECT STRING(TIMESTAMP "2008-12-25 15:30:00+00", "UTC")`,