github.com/jackc/pgx/v5@v5.5.5/pgtype/date.go (about) 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 // Scan implements the database/sql Scanner interface. 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 // Value implements the database/sql/driver Valuer interface. 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 // Year 0000 is 1 BC 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 // BC matched 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 }