github.com/jackc/pgx/v5@v5.5.5/pgtype/timestamptz.go (about) 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 // Timestamptz represents the PostgreSQL timestamptz type. 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 // Scan implements the database/sql Scanner interface. 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 // Value implements the database/sql/driver Valuer interface. 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 // PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz 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 // Year 0000 is 1 BC 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 }