From b7b581ff5ac57c16b9a1677e38e5bdaefb5adfa0 Mon Sep 17 00:00:00 2001 From: wispem-wantex Date: Sat, 8 Nov 2025 19:36:10 -0800 Subject: [PATCH] Add Timestamp type for db --- pkg/db/timestamp.go | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 pkg/db/timestamp.go diff --git a/pkg/db/timestamp.go b/pkg/db/timestamp.go new file mode 100644 index 0000000..3dc3992 --- /dev/null +++ b/pkg/db/timestamp.go @@ -0,0 +1,64 @@ +package db + +import ( + "database/sql/driver" + "fmt" + "strconv" + "time" +) + +// Timestamp is a type that wraps `time.Time`. It implements `driver.Value` and `json.Marshal` to +// store its value as an integer, a Unix timestamp in milliseconds. +// +// It uses type embedding (`struct { time.Time }`), instead of making it simply a typedef of +// `time.Time`, because that keeps methods on `time.Time` (like Add, UnixMilli, etc.) intact and +// exposed on instances of Timestamp. +type Timestamp struct { + time.Time +} + +func TimestampFromUnix(num int64) Timestamp { + return Timestamp{time.Unix(num, 0)} +} +func TimestampFromUnixMilli(num int64) Timestamp { + return Timestamp{time.UnixMilli(num)} +} + +// TimestampNow returns a new Timestamp corresponding to the current time, rounded to the nearest millisecond. +func TimestampNow() Timestamp { + return Timestamp{time.Now().Round(time.Millisecond)} +} + +// ------------ +// driver.Value +// ------------ + +func (t Timestamp) Value() (driver.Value, error) { + return t.UnixMilli(), nil +} + +func (t *Timestamp) Scan(src any) error { + val, isOk := src.(int64) + if !isOk { + return fmt.Errorf("incompatible type for Timestamp: %#v", src) + } + *t = TimestampFromUnixMilli(val) + return nil +} + +// ------------------------ +// json.Marshal / Unmarshal +// ------------------------ + +func (t Timestamp) MarshalJSON() ([]byte, error) { + return fmt.Appendf(nil, "%d", t.UnixMilli()), nil +} + +func (t *Timestamp) UnmarshalJSON(b []byte) error { + ms, err := strconv.ParseInt(string(b), 10, 64) + if err != nil { + return fmt.Errorf("invalid timestamp %q: %w", string(b), err) + } + *t = TimestampFromUnixMilli(ms) + return nil +}