-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimestamp.go
More file actions
132 lines (116 loc) · 4.18 KB
/
timestamp.go
File metadata and controls
132 lines (116 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package flashduty
import (
"bytes"
"strconv"
"time"
)
// Timestamp is a Unix-seconds instant as it appears on the Flashduty API wire.
//
// It marshals to an RFC3339 string in the local timezone, so structured output
// is human- and LLM-readable instead of an opaque integer. It unmarshals from
// either a numeric epoch (the wire form) or an RFC3339 string (so a marshaled
// value round-trips). The zero value marshals to 0 — an unset sentinel, never a
// 1970 date — and is dropped by `json:",omitempty"`.
//
// Use Timestamp only for absolute instants. Durations, cyclic-window offsets,
// and counts stay int64.
type Timestamp int64
// Time returns the instant as a time.Time.
func (t Timestamp) Time() time.Time { return time.Unix(int64(t), 0) }
// Unix returns the raw wire value (Unix seconds).
func (t Timestamp) Unix() int64 { return int64(t) }
// IsZero reports whether the value is the unset sentinel (0).
func (t Timestamp) IsZero() bool { return t == 0 }
// String renders the instant as RFC3339 in the local timezone, or "0" when
// unset. Non-JSON encoders (TOON, fmt) render the value through this method.
func (t Timestamp) String() string {
if t == 0 {
return "0"
}
return t.Time().In(time.Local).Format(time.RFC3339)
}
// MarshalJSON renders a non-zero value as a quoted RFC3339 string in the local
// timezone; zero renders as the bare integer 0.
func (t Timestamp) MarshalJSON() ([]byte, error) {
if t == 0 {
return []byte("0"), nil
}
return []byte(strconv.Quote(t.String())), nil
}
// UnmarshalJSON accepts a numeric Unix-seconds epoch, a quoted integer, an
// RFC3339 string, or null (→ 0).
func (t *Timestamp) UnmarshalJSON(b []byte) error {
n, err := parseEpochOrRFC3339(b, time.Second)
if err != nil {
return err
}
*t = Timestamp(n)
return nil
}
// TimestampMilli is a Unix-milliseconds instant. It has the same rendering
// contract as Timestamp (RFC3339 out, epoch-or-RFC3339 in, zero→0); only the
// wire unit differs.
type TimestampMilli int64
// Time returns the instant as a time.Time.
func (t TimestampMilli) Time() time.Time { return time.UnixMilli(int64(t)) }
// Unix returns the raw wire value (milliseconds since the Unix epoch).
func (t TimestampMilli) Unix() int64 { return int64(t) }
// IsZero reports whether the value is the unset sentinel (0).
func (t TimestampMilli) IsZero() bool { return t == 0 }
// String renders the instant as RFC3339Nano in the local timezone (preserving
// sub-second precision), or "0" when unset. Non-JSON encoders (TOON, fmt)
// render the value through this method.
func (t TimestampMilli) String() string {
if t == 0 {
return "0"
}
return t.Time().In(time.Local).Format(time.RFC3339Nano)
}
// MarshalJSON renders a non-zero value as a quoted RFC3339 string in the local
// timezone; zero renders as the bare integer 0. RFC3339Nano is used so that
// sub-second (millisecond) precision survives a marshal→unmarshal round-trip;
// it elides trailing zeros, so whole-second values render identically to a
// plain RFC3339 timestamp.
func (t TimestampMilli) MarshalJSON() ([]byte, error) {
if t == 0 {
return []byte("0"), nil
}
return []byte(strconv.Quote(t.String())), nil
}
// UnmarshalJSON accepts a numeric Unix-milliseconds epoch, a quoted integer, an
// RFC3339 string, or null (→ 0).
func (t *TimestampMilli) UnmarshalJSON(b []byte) error {
n, err := parseEpochOrRFC3339(b, time.Millisecond)
if err != nil {
return err
}
*t = TimestampMilli(n)
return nil
}
// parseEpochOrRFC3339 decodes a JSON token into a wire integer of the given unit
// (time.Second or time.Millisecond). Accepts null/empty → 0, a bare or quoted
// integer (returned as-is), or a quoted RFC3339 string (converted to the unit).
func parseEpochOrRFC3339(b []byte, unit time.Duration) (int64, error) {
s := string(bytes.TrimSpace(b))
if s == "" || s == "null" {
return 0, nil
}
if s[0] == '"' {
inner := s[1 : len(s)-1]
if inner == "" {
return 0, nil
}
if n, err := strconv.ParseInt(inner, 10, 64); err == nil {
return n, nil
}
tm, err := time.Parse(time.RFC3339, inner)
if err != nil {
return 0, err
}
if unit == time.Millisecond {
return tm.UnixMilli(), nil
}
return tm.Unix(), nil
}
return strconv.ParseInt(s, 10, 64)
}