diff --git a/internal/store/time.go b/internal/store/time.go index 05a0401..cb05120 100644 --- a/internal/store/time.go +++ b/internal/store/time.go @@ -29,11 +29,15 @@ func (t Timestamp) String() string { return t.UTC().Format(timestampLayout) } -// Value implements driver.Valuer. The zero Timestamp is an error. +// Value implements driver.Valuer. The zero Timestamp is an error, as is a UTC +// year outside 0000–9999, which the four-digit stored form cannot represent. func (t Timestamp) Value() (driver.Value, error) { if t.IsZero() { return nil, errors.New("store: zero Timestamp") } + if y := t.UTC().Year(); y < 0 || y > 9999 { + return nil, fmt.Errorf("store: Timestamp year %d outside 0000-9999", y) + } return t.String(), nil } diff --git a/internal/store/time_test.go b/internal/store/time_test.go index eb7d1cc..0af3a45 100644 --- a/internal/store/time_test.go +++ b/internal/store/time_test.go @@ -60,6 +60,15 @@ func TestTimestampRejects(t *testing.T) { if _, err := (Timestamp{}).Value(); err == nil { t.Error("zero Timestamp Value succeeded") } + for _, tm := range []time.Time{ + time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(-1, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(9999, 12, 31, 23, 0, 0, 0, time.FixedZone("west", -2*3600)), + } { + if v, err := (Timestamp{tm}).Value(); err == nil { + t.Errorf("Timestamp{%v}.Value() = %v, want error", tm, v) + } + } for _, src := range []any{ nil, int64(0), []byte("2026-10-03T12:37:05.123Z"), "2026-10-03T12:37:05Z", "2026-10-03T08:37:05.123-04:00", "2026-10-03T12:37:05,123Z", "garbage",