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