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