1 package pgtype
2
3 import (
4 "database/sql/driver"
5 "encoding/binary"
6 "encoding/json"
7 "fmt"
8 "strings"
9 "time"
10
11 "github.com/jackc/pgx/v5/internal/pgio"
12 )
13
14 const pgTimestampFormat = "2006-01-02 15:04:05.999999999"
15
16 type TimestampScanner interface {
17 ScanTimestamp(v Timestamp) error
18 }
19
20 type TimestampValuer interface {
21 TimestampValue() (Timestamp, error)
22 }
23
24
25 type Timestamp struct {
26 Time time.Time
27 InfinityModifier InfinityModifier
28 Valid bool
29 }
30
31 func (ts *Timestamp) ScanTimestamp(v Timestamp) error {
32 *ts = v
33 return nil
34 }
35
36 func (ts Timestamp) TimestampValue() (Timestamp, error) {
37 return ts, nil
38 }
39
40
41 func (ts *Timestamp) Scan(src any) error {
42 if src == nil {
43 *ts = Timestamp{}
44 return nil
45 }
46
47 switch src := src.(type) {
48 case string:
49 return scanPlanTextTimestampToTimestampScanner{}.Scan([]byte(src), ts)
50 case time.Time:
51 *ts = Timestamp{Time: src, Valid: true}
52 return nil
53 }
54
55 return fmt.Errorf("cannot scan %T", src)
56 }
57
58
59 func (ts Timestamp) Value() (driver.Value, error) {
60 if !ts.Valid {
61 return nil, nil
62 }
63
64 if ts.InfinityModifier != Finite {
65 return ts.InfinityModifier.String(), nil
66 }
67 return ts.Time, nil
68 }
69
70 func (ts Timestamp) MarshalJSON() ([]byte, error) {
71 if !ts.Valid {
72 return []byte("null"), nil
73 }
74
75 var s string
76
77 switch ts.InfinityModifier {
78 case Finite:
79 s = ts.Time.Format(time.RFC3339Nano)
80 case Infinity:
81 s = "infinity"
82 case NegativeInfinity:
83 s = "-infinity"
84 }
85
86 return json.Marshal(s)
87 }
88
89 func (ts *Timestamp) UnmarshalJSON(b []byte) error {
90 var s *string
91 err := json.Unmarshal(b, &s)
92 if err != nil {
93 return err
94 }
95
96 if s == nil {
97 *ts = Timestamp{}
98 return nil
99 }
100
101 switch *s {
102 case "infinity":
103 *ts = Timestamp{Valid: true, InfinityModifier: Infinity}
104 case "-infinity":
105 *ts = Timestamp{Valid: true, InfinityModifier: -Infinity}
106 default:
107
108 tim, err := time.Parse(time.RFC3339Nano, *s)
109 if err != nil {
110 return err
111 }
112
113 *ts = Timestamp{Time: tim, Valid: true}
114 }
115
116 return nil
117 }
118
119 type TimestampCodec struct{}
120
121 func (TimestampCodec) FormatSupported(format int16) bool {
122 return format == TextFormatCode || format == BinaryFormatCode
123 }
124
125 func (TimestampCodec) PreferredFormat() int16 {
126 return BinaryFormatCode
127 }
128
129 func (TimestampCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
130 if _, ok := value.(TimestampValuer); !ok {
131 return nil
132 }
133
134 switch format {
135 case BinaryFormatCode:
136 return encodePlanTimestampCodecBinary{}
137 case TextFormatCode:
138 return encodePlanTimestampCodecText{}
139 }
140
141 return nil
142 }
143
144 type encodePlanTimestampCodecBinary struct{}
145
146 func (encodePlanTimestampCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
147 ts, err := value.(TimestampValuer).TimestampValue()
148 if err != nil {
149 return nil, err
150 }
151
152 if !ts.Valid {
153 return nil, nil
154 }
155
156 var microsecSinceY2K int64
157 switch ts.InfinityModifier {
158 case Finite:
159 t := discardTimeZone(ts.Time)
160 microsecSinceUnixEpoch := t.Unix()*1000000 + int64(t.Nanosecond())/1000
161 microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
162 case Infinity:
163 microsecSinceY2K = infinityMicrosecondOffset
164 case NegativeInfinity:
165 microsecSinceY2K = negativeInfinityMicrosecondOffset
166 }
167
168 buf = pgio.AppendInt64(buf, microsecSinceY2K)
169
170 return buf, nil
171 }
172
173 type encodePlanTimestampCodecText struct{}
174
175 func (encodePlanTimestampCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
176 ts, err := value.(TimestampValuer).TimestampValue()
177 if err != nil {
178 return nil, err
179 }
180
181 if !ts.Valid {
182 return nil, nil
183 }
184
185 var s string
186
187 switch ts.InfinityModifier {
188 case Finite:
189 t := discardTimeZone(ts.Time)
190
191
192 bc := false
193 if year := t.Year(); year <= 0 {
194 year = -year + 1
195 t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
196 bc = true
197 }
198
199 s = t.Truncate(time.Microsecond).Format(pgTimestampFormat)
200
201 if bc {
202 s = s + " BC"
203 }
204 case Infinity:
205 s = "infinity"
206 case NegativeInfinity:
207 s = "-infinity"
208 }
209
210 buf = append(buf, s...)
211
212 return buf, nil
213 }
214
215 func discardTimeZone(t time.Time) time.Time {
216 if t.Location() != time.UTC {
217 return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
218 }
219
220 return t
221 }
222
223 func (TimestampCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
224
225 switch format {
226 case BinaryFormatCode:
227 switch target.(type) {
228 case TimestampScanner:
229 return scanPlanBinaryTimestampToTimestampScanner{}
230 }
231 case TextFormatCode:
232 switch target.(type) {
233 case TimestampScanner:
234 return scanPlanTextTimestampToTimestampScanner{}
235 }
236 }
237
238 return nil
239 }
240
241 type scanPlanBinaryTimestampToTimestampScanner struct{}
242
243 func (scanPlanBinaryTimestampToTimestampScanner) Scan(src []byte, dst any) error {
244 scanner := (dst).(TimestampScanner)
245
246 if src == nil {
247 return scanner.ScanTimestamp(Timestamp{})
248 }
249
250 if len(src) != 8 {
251 return fmt.Errorf("invalid length for timestamp: %v", len(src))
252 }
253
254 var ts Timestamp
255 microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
256
257 switch microsecSinceY2K {
258 case infinityMicrosecondOffset:
259 ts = Timestamp{Valid: true, InfinityModifier: Infinity}
260 case negativeInfinityMicrosecondOffset:
261 ts = Timestamp{Valid: true, InfinityModifier: -Infinity}
262 default:
263 tim := time.Unix(
264 microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
265 (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
266 ).UTC()
267 ts = Timestamp{Time: tim, Valid: true}
268 }
269
270 return scanner.ScanTimestamp(ts)
271 }
272
273 type scanPlanTextTimestampToTimestampScanner struct{}
274
275 func (scanPlanTextTimestampToTimestampScanner) Scan(src []byte, dst any) error {
276 scanner := (dst).(TimestampScanner)
277
278 if src == nil {
279 return scanner.ScanTimestamp(Timestamp{})
280 }
281
282 var ts Timestamp
283 sbuf := string(src)
284 switch sbuf {
285 case "infinity":
286 ts = Timestamp{Valid: true, InfinityModifier: Infinity}
287 case "-infinity":
288 ts = Timestamp{Valid: true, InfinityModifier: -Infinity}
289 default:
290 bc := false
291 if strings.HasSuffix(sbuf, " BC") {
292 sbuf = sbuf[:len(sbuf)-3]
293 bc = true
294 }
295 tim, err := time.Parse(pgTimestampFormat, sbuf)
296 if err != nil {
297 return err
298 }
299
300 if bc {
301 year := -tim.Year() + 1
302 tim = time.Date(year, tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), tim.Location())
303 }
304
305 ts = Timestamp{Time: tim, Valid: true}
306 }
307
308 return scanner.ScanTimestamp(ts)
309 }
310
311 func (c TimestampCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
312 if src == nil {
313 return nil, nil
314 }
315
316 var ts Timestamp
317 err := codecScan(c, m, oid, format, src, &ts)
318 if err != nil {
319 return nil, err
320 }
321
322 if ts.InfinityModifier != Finite {
323 return ts.InfinityModifier.String(), nil
324 }
325
326 return ts.Time, nil
327 }
328
329 func (c TimestampCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
330 if src == nil {
331 return nil, nil
332 }
333
334 var ts Timestamp
335 err := codecScan(c, m, oid, format, src, &ts)
336 if err != nil {
337 return nil, err
338 }
339
340 if ts.InfinityModifier != Finite {
341 return ts.InfinityModifier, nil
342 }
343
344 return ts.Time, nil
345 }
346
View as plain text