1 package pgtype
2
3 import (
4 "database/sql/driver"
5 "encoding/binary"
6 "encoding/json"
7 "fmt"
8 "time"
9
10 "github.com/jackc/pgio"
11 )
12
13 const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
14 const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
15 const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
16 const microsecFromUnixEpochToY2K = 946684800 * 1000000
17
18 const (
19 negativeInfinityMicrosecondOffset = -9223372036854775808
20 infinityMicrosecondOffset = 9223372036854775807
21 )
22
23 type Timestamptz struct {
24 Time time.Time
25 Status Status
26 InfinityModifier InfinityModifier
27 }
28
29 func (dst *Timestamptz) Set(src interface{}) error {
30 if src == nil {
31 *dst = Timestamptz{Status: Null}
32 return nil
33 }
34
35 if value, ok := src.(interface{ Get() interface{} }); ok {
36 value2 := value.Get()
37 if value2 != value {
38 return dst.Set(value2)
39 }
40 }
41
42 switch value := src.(type) {
43 case time.Time:
44 *dst = Timestamptz{Time: value, Status: Present}
45 case *time.Time:
46 if value == nil {
47 *dst = Timestamptz{Status: Null}
48 } else {
49 return dst.Set(*value)
50 }
51 case string:
52 return dst.DecodeText(nil, []byte(value))
53 case *string:
54 if value == nil {
55 *dst = Timestamptz{Status: Null}
56 } else {
57 return dst.Set(*value)
58 }
59 case InfinityModifier:
60 *dst = Timestamptz{InfinityModifier: value, Status: Present}
61 default:
62 if originalSrc, ok := underlyingTimeType(src); ok {
63 return dst.Set(originalSrc)
64 }
65 return fmt.Errorf("cannot convert %v to Timestamptz", value)
66 }
67
68 return nil
69 }
70
71 func (dst Timestamptz) Get() interface{} {
72 switch dst.Status {
73 case Present:
74 if dst.InfinityModifier != None {
75 return dst.InfinityModifier
76 }
77 return dst.Time
78 case Null:
79 return nil
80 default:
81 return dst.Status
82 }
83 }
84
85 func (src *Timestamptz) AssignTo(dst interface{}) error {
86 switch src.Status {
87 case Present:
88 switch v := dst.(type) {
89 case *time.Time:
90 if src.InfinityModifier != None {
91 return fmt.Errorf("cannot assign %v to %T", src, dst)
92 }
93 *v = src.Time
94 return nil
95 default:
96 if nextDst, retry := GetAssignToDstType(dst); retry {
97 return src.AssignTo(nextDst)
98 }
99 return fmt.Errorf("unable to assign to %T", dst)
100 }
101 case Null:
102 return NullAssignTo(dst)
103 }
104
105 return fmt.Errorf("cannot decode %#v into %T", src, dst)
106 }
107
108 func (dst *Timestamptz) DecodeText(ci *ConnInfo, src []byte) error {
109 if src == nil {
110 *dst = Timestamptz{Status: Null}
111 return nil
112 }
113
114 sbuf := string(src)
115 switch sbuf {
116 case "infinity":
117 *dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
118 case "-infinity":
119 *dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
120 default:
121 var format string
122 if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
123 format = pgTimestamptzSecondFormat
124 } else if len(sbuf) >= 6 && (sbuf[len(sbuf)-6] == '-' || sbuf[len(sbuf)-6] == '+') {
125 format = pgTimestamptzMinuteFormat
126 } else {
127 format = pgTimestamptzHourFormat
128 }
129
130 tim, err := time.Parse(format, sbuf)
131 if err != nil {
132 return err
133 }
134
135 *dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present}
136 }
137
138 return nil
139 }
140
141 func (dst *Timestamptz) DecodeBinary(ci *ConnInfo, src []byte) error {
142 if src == nil {
143 *dst = Timestamptz{Status: Null}
144 return nil
145 }
146
147 if len(src) != 8 {
148 return fmt.Errorf("invalid length for timestamptz: %v", len(src))
149 }
150
151 microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
152
153 switch microsecSinceY2K {
154 case infinityMicrosecondOffset:
155 *dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
156 case negativeInfinityMicrosecondOffset:
157 *dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
158 default:
159 tim := time.Unix(
160 microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
161 (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
162 )
163 *dst = Timestamptz{Time: tim, Status: Present}
164 }
165
166 return nil
167 }
168
169 func (src Timestamptz) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
170 switch src.Status {
171 case Null:
172 return nil, nil
173 case Undefined:
174 return nil, errUndefined
175 }
176
177 var s string
178
179 switch src.InfinityModifier {
180 case None:
181 s = src.Time.UTC().Truncate(time.Microsecond).Format(pgTimestamptzSecondFormat)
182 case Infinity:
183 s = "infinity"
184 case NegativeInfinity:
185 s = "-infinity"
186 }
187
188 return append(buf, s...), nil
189 }
190
191 func (src Timestamptz) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
192 switch src.Status {
193 case Null:
194 return nil, nil
195 case Undefined:
196 return nil, errUndefined
197 }
198
199 var microsecSinceY2K int64
200 switch src.InfinityModifier {
201 case None:
202 microsecSinceUnixEpoch := src.Time.Unix()*1000000 + int64(src.Time.Nanosecond())/1000
203 microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
204 case Infinity:
205 microsecSinceY2K = infinityMicrosecondOffset
206 case NegativeInfinity:
207 microsecSinceY2K = negativeInfinityMicrosecondOffset
208 }
209
210 return pgio.AppendInt64(buf, microsecSinceY2K), nil
211 }
212
213
214 func (dst *Timestamptz) Scan(src interface{}) error {
215 if src == nil {
216 *dst = Timestamptz{Status: Null}
217 return nil
218 }
219
220 switch src := src.(type) {
221 case string:
222 return dst.DecodeText(nil, []byte(src))
223 case []byte:
224 srcCopy := make([]byte, len(src))
225 copy(srcCopy, src)
226 return dst.DecodeText(nil, srcCopy)
227 case time.Time:
228 *dst = Timestamptz{Time: src, Status: Present}
229 return nil
230 }
231
232 return fmt.Errorf("cannot scan %T", src)
233 }
234
235
236 func (src Timestamptz) Value() (driver.Value, error) {
237 switch src.Status {
238 case Present:
239 if src.InfinityModifier != None {
240 return src.InfinityModifier.String(), nil
241 }
242 if src.Time.Location().String() == time.UTC.String() {
243 return src.Time.UTC(), nil
244 }
245 return src.Time, nil
246 case Null:
247 return nil, nil
248 default:
249 return nil, errUndefined
250 }
251 }
252
253 func (src Timestamptz) MarshalJSON() ([]byte, error) {
254 switch src.Status {
255 case Null:
256 return []byte("null"), nil
257 case Undefined:
258 return nil, errUndefined
259 }
260
261 if src.Status != Present {
262 return nil, errBadStatus
263 }
264
265 var s string
266
267 switch src.InfinityModifier {
268 case None:
269 s = src.Time.Format(time.RFC3339Nano)
270 case Infinity:
271 s = "infinity"
272 case NegativeInfinity:
273 s = "-infinity"
274 }
275
276 return json.Marshal(s)
277 }
278
279 func (dst *Timestamptz) UnmarshalJSON(b []byte) error {
280 var s *string
281 err := json.Unmarshal(b, &s)
282 if err != nil {
283 return err
284 }
285
286 if s == nil {
287 *dst = Timestamptz{Status: Null}
288 return nil
289 }
290
291 switch *s {
292 case "infinity":
293 *dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
294 case "-infinity":
295 *dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
296 default:
297
298 tim, err := time.Parse(time.RFC3339Nano, *s)
299 if err != nil {
300 return err
301 }
302
303 *dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present}
304 }
305
306 return nil
307 }
308
309
310
311
312
313
314
315
316 func normalizePotentialUTC(timestamp time.Time) time.Time {
317 if timestamp.Location().String() != time.UTC.String() {
318 return timestamp
319 }
320
321 return timestamp.UTC()
322 }
323
View as plain text