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