package pgtype
import (
)
const (
pgTimestampFormat = "2006-01-02 15:04:05.999999999"
jsonISO8601 = "2006-01-02T15:04:05.999999999"
)
type TimestampScanner interface {
ScanTimestamp(v Timestamp) error
}
type TimestampValuer interface {
TimestampValue() (Timestamp, error)
}
type Timestamp struct {
Time time.Time
InfinityModifier InfinityModifier
Valid bool
}
func ( *Timestamp) ( Timestamp) error {
* =
return nil
}
func ( Timestamp) () (Timestamp, error) {
return , nil
}
func ( *Timestamp) ( any) error {
if == nil {
* = Timestamp{}
return nil
}
switch src := .(type) {
case string:
return (&scanPlanTextTimestampToTimestampScanner{}).Scan([]byte(), )
case time.Time:
* = Timestamp{Time: , Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", )
}
func ( Timestamp) () (driver.Value, error) {
if !.Valid {
return nil, nil
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( Timestamp) () ([]byte, error) {
if !.Valid {
return []byte("null"), nil
}
var string
switch .InfinityModifier {
case Finite:
= .Time.Format(jsonISO8601)
case Infinity:
= "infinity"
case NegativeInfinity:
= "-infinity"
}
return json.Marshal()
}
func ( *Timestamp) ( []byte) error {
var *string
:= json.Unmarshal(, &)
if != nil {
return
}
if == nil {
* = Timestamp{}
return nil
}
switch * {
case "infinity":
* = Timestamp{Valid: true, InfinityModifier: Infinity}
case "-infinity":
* = Timestamp{Valid: true, InfinityModifier: -Infinity}
default:
:= *
, := time.Parse(time.RFC3339Nano, )
if == nil {
* = Timestamp{Time: , Valid: true}
return nil
}
, = time.ParseInLocation(jsonISO8601, , time.UTC)
if == nil {
* = Timestamp{Time: , Valid: true}
return nil
}
.Valid = false
return fmt.Errorf("cannot unmarshal %s to timestamp with layout %s or %s (%w)",
*, time.RFC3339Nano, jsonISO8601, )
}
return nil
}
type TimestampCodec struct {
ScanLocation *time.Location
}
func (*TimestampCodec) ( int16) bool {
return == TextFormatCode || == BinaryFormatCode
}
func (*TimestampCodec) () int16 {
return BinaryFormatCode
}
func (*TimestampCodec) ( *Map, uint32, int16, any) EncodePlan {
if , := .(TimestampValuer); ! {
return nil
}
switch {
case BinaryFormatCode:
return encodePlanTimestampCodecBinary{}
case TextFormatCode:
return encodePlanTimestampCodecText{}
}
return nil
}
type encodePlanTimestampCodecBinary struct{}
func (encodePlanTimestampCodecBinary) ( any, []byte) ( []byte, error) {
, := .(TimestampValuer).TimestampValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
var int64
switch .InfinityModifier {
case Finite:
:= discardTimeZone(.Time)
:= .Unix()*1_000_000 + int64(.Nanosecond())/1000
= - microsecFromUnixEpochToY2K
case Infinity:
= infinityMicrosecondOffset
case NegativeInfinity:
= negativeInfinityMicrosecondOffset
}
= pgio.AppendInt64(, )
return , nil
}
type encodePlanTimestampCodecText struct{}
func (encodePlanTimestampCodecText) ( any, []byte) ( []byte, error) {
, := .(TimestampValuer).TimestampValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
var string
switch .InfinityModifier {
case Finite:
:= discardTimeZone(.Time)
:= false
if := .Year(); <= 0 {
= - + 1
= time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), time.UTC)
= true
}
= .Truncate(time.Microsecond).Format(pgTimestampFormat)
if {
= + " BC"
}
case Infinity:
= "infinity"
case NegativeInfinity:
= "-infinity"
}
= append(, ...)
return , nil
}
func ( time.Time) time.Time {
if .Location() != time.UTC {
return time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), time.UTC)
}
return
}
func ( *TimestampCodec) ( *Map, uint32, int16, any) ScanPlan {
switch {
case BinaryFormatCode:
switch .(type) {
case TimestampScanner:
return &scanPlanBinaryTimestampToTimestampScanner{location: .ScanLocation}
}
case TextFormatCode:
switch .(type) {
case TimestampScanner:
return &scanPlanTextTimestampToTimestampScanner{location: .ScanLocation}
}
}
return nil
}
type scanPlanBinaryTimestampToTimestampScanner struct{ location *time.Location }
func ( *scanPlanBinaryTimestampToTimestampScanner) ( []byte, any) error {
:= ().(TimestampScanner)
if == nil {
return .ScanTimestamp(Timestamp{})
}
if len() != 8 {
return fmt.Errorf("invalid length for timestamp: %v", len())
}
var Timestamp
:= int64(binary.BigEndian.Uint64())
switch {
case infinityMicrosecondOffset:
= Timestamp{Valid: true, InfinityModifier: Infinity}
case negativeInfinityMicrosecondOffset:
= Timestamp{Valid: true, InfinityModifier: -Infinity}
default:
:= time.Unix(
microsecFromUnixEpochToY2K/1_000_000+/1_000_000,
(microsecFromUnixEpochToY2K%1_000_000*1_000)+(%1_000_000*1000),
).UTC()
if .location != nil {
= time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .location)
}
= Timestamp{Time: , Valid: true}
}
return .ScanTimestamp()
}
type scanPlanTextTimestampToTimestampScanner struct{ location *time.Location }
func ( *scanPlanTextTimestampToTimestampScanner) ( []byte, any) error {
:= ().(TimestampScanner)
if == nil {
return .ScanTimestamp(Timestamp{})
}
var Timestamp
:= string()
switch {
case "infinity":
= Timestamp{Valid: true, InfinityModifier: Infinity}
case "-infinity":
= Timestamp{Valid: true, InfinityModifier: -Infinity}
default:
:= false
if strings.HasSuffix(, " BC") {
= [:len()-3]
= true
}
, := time.Parse(pgTimestampFormat, )
if != nil {
return
}
if {
:= -.Year() + 1
= time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .Location())
}
if .location != nil {
= time.Date(.Year(), .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .location)
}
= Timestamp{Time: , Valid: true}
}
return .ScanTimestamp()
}
func ( *TimestampCodec) ( *Map, uint32, int16, []byte) (driver.Value, error) {
if == nil {
return nil, nil
}
var Timestamp
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( *TimestampCodec) ( *Map, uint32, int16, []byte) (any, error) {
if == nil {
return nil, nil
}
var Timestamp
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier, nil
}
return .Time, nil
}