Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Timestamp] Use microsecond precision to support full range of BigQuery timestamps #7

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions internal/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math/big"
"strconv"
"time"

"github.com/goccy/go-json"
)
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 6 additions & 7 deletions internal/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")`,
Expand Down