Timestamps with millisecond precision, for data that gets stored and compared as text.
Go's time.Time marshals with nanosecond precision and a variable-length
fraction, so two timestamps that are the same instant can serialise differently —
which matters when the text is a CouchDB document key, a database column, or part
of an API response someone diffs. at.Timestamp always writes exactly three
decimal places, so encoding is stable and lexical order matches chronological
order.
This package started inside invopop/couch and was moved out so anything can use it without depending on a persistence layer.
go get github.com/invopop/atTimestamp embeds time.Time, so every method you already know is there:
ts := at.Now()
fmt.Println(ts.String()) // 2026-08-03T12:29:08.123Z
fmt.Println(ts.Year()) // 2026, via the embedded time.Time
fmt.Println(ts.Before(other.Time))In a struct it marshals and unmarshals as a quoted string, and an empty value
round-trips as null:
type Invoice struct {
IssuedAt at.Timestamp `json:"issued_at"`
PaidAt *at.Timestamp `json:"paid_at,omitempty"`
}{ "issued_at": "2026-08-03T12:29:08.123Z" }Parsing takes any RFC3339 timestamp — milliseconds optional, offset or Z — and
normalises it to UTC, so input that was written by something less strict still
reads:
ts, _ := at.ParseTimestamp("2026-08-03T12:29:08.123Z")
ts, _ = at.ParseTimestamp("2026-08-03T14:29:08.123+02:00") // → 12:29:08.123Z
ts, _ = at.ParseTimestamp("2026-08-03T12:29:08Z") // → 12:29:08.000ZLocalTime is the same idea for a wall-clock time that must keep its zone rather
than being normalised to UTC — a scheduled hour in a merchant's own timezone, for
example:
lt := at.LocalTimeNow(loc)
fmt.Println(lt.String()) // 2026-08-03T14:29:08.123+02:00- Marshalling always emits three decimal places, padding or truncating as needed. A timestamp with sub-millisecond precision loses it on the way out.
- The zero
Timestampmarshals asnull, andnullunmarshals back to the zero value, soIsZero()is the test for "not set".
Apache 2.0 — see LICENSE.