package pgtype
import (
)
const (
pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
microsecFromUnixEpochToY2K = 946_684_800 * 1_000_000
)
const (
negativeInfinityMicrosecondOffset = -9223372036854775808
infinityMicrosecondOffset = 9223372036854775807
)
type TimestamptzScanner interface {
ScanTimestamptz(v Timestamptz) error
}
type TimestamptzValuer interface {
TimestamptzValue() (Timestamptz, error)
}
type Timestamptz struct {
Time time.Time
InfinityModifier InfinityModifier
Valid bool
}
func ( *Timestamptz) ( Timestamptz) error {
* =
return nil
}
func ( Timestamptz) () (Timestamptz, error) {
return , nil
}
func ( *Timestamptz) ( any) error {
if == nil {
* = Timestamptz{}
return nil
}
switch src := .(type) {
case string:
return (&scanPlanTextTimestamptzToTimestamptzScanner{}).Scan([]byte(), )
case time.Time:
* = Timestamptz{Time: , Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", )
}
func ( Timestamptz) () (driver.Value, error) {
if !.Valid {
return nil, nil
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( Timestamptz) () ([]byte, error) {
if !.Valid {
return []byte("null"), nil
}
var string
switch .InfinityModifier {
case Finite:
= .Time.Format(time.RFC3339Nano)
case Infinity:
= "infinity"
case NegativeInfinity:
= "-infinity"
}
return json.Marshal()
}
func ( *Timestamptz) ( []byte) error {
var *string
:= json.Unmarshal(, &)
if != nil {
return
}
if == nil {
* = Timestamptz{}
return nil
}
switch * {
case "infinity":
* = Timestamptz{Valid: true, InfinityModifier: Infinity}
case "-infinity":
* = Timestamptz{Valid: true, InfinityModifier: -Infinity}
default:
, := time.Parse(time.RFC3339Nano, *)
if != nil {
return
}
* = Timestamptz{Time: , Valid: true}
}
return nil
}
type TimestamptzCodec struct {
ScanLocation *time.Location
}
func (*TimestamptzCodec) ( int16) bool {
return == TextFormatCode || == BinaryFormatCode
}
func (*TimestamptzCodec) () int16 {
return BinaryFormatCode
}
func (*TimestamptzCodec) ( *Map, uint32, int16, any) EncodePlan {
if , := .(TimestamptzValuer); ! {
return nil
}
switch {
case BinaryFormatCode:
return encodePlanTimestamptzCodecBinary{}
case TextFormatCode:
return encodePlanTimestamptzCodecText{}
}
return nil
}
type encodePlanTimestamptzCodecBinary struct{}
func (encodePlanTimestamptzCodecBinary) ( any, []byte) ( []byte, error) {
, := .(TimestamptzValuer).TimestamptzValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
var int64
switch .InfinityModifier {
case Finite:
:= .Time.Unix()*1000000 + int64(.Time.Nanosecond())/1000
= - microsecFromUnixEpochToY2K
case Infinity:
= infinityMicrosecondOffset
case NegativeInfinity:
= negativeInfinityMicrosecondOffset
}
= pgio.AppendInt64(, )
return , nil
}
type encodePlanTimestamptzCodecText struct{}
func (encodePlanTimestamptzCodecText) ( any, []byte) ( []byte, error) {
, := .(TimestamptzValuer).TimestamptzValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
var string
switch .InfinityModifier {
case Finite:
:= .Time.UTC().Truncate(time.Microsecond)
:= false
if := .Year(); <= 0 {
= - + 1
= time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), time.UTC)
= true
}
= .Format(pgTimestamptzSecondFormat)
if {
= + " BC"
}
case Infinity:
= "infinity"
case NegativeInfinity:
= "-infinity"
}
= append(, ...)
return , nil
}
func ( *TimestamptzCodec) ( *Map, uint32, int16, any) ScanPlan {
switch {
case BinaryFormatCode:
switch .(type) {
case TimestamptzScanner:
return &scanPlanBinaryTimestamptzToTimestamptzScanner{location: .ScanLocation}
}
case TextFormatCode:
switch .(type) {
case TimestamptzScanner:
return &scanPlanTextTimestamptzToTimestamptzScanner{location: .ScanLocation}
}
}
return nil
}
type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }
func ( *scanPlanBinaryTimestamptzToTimestamptzScanner) ( []byte, any) error {
:= ().(TimestamptzScanner)
if == nil {
return .ScanTimestamptz(Timestamptz{})
}
if len() != 8 {
return fmt.Errorf("invalid length for timestamptz: %v", len())
}
var Timestamptz
:= int64(binary.BigEndian.Uint64())
switch {
case infinityMicrosecondOffset:
= Timestamptz{Valid: true, InfinityModifier: Infinity}
case negativeInfinityMicrosecondOffset:
= Timestamptz{Valid: true, InfinityModifier: -Infinity}
default:
:= time.Unix(
microsecFromUnixEpochToY2K/1_000_000+/1_000_000,
(microsecFromUnixEpochToY2K%1_000_000*1_000)+(%1_000_000*1_000),
)
if .location != nil {
= .In(.location)
}
= Timestamptz{Time: , Valid: true}
}
return .ScanTimestamptz()
}
type scanPlanTextTimestamptzToTimestamptzScanner struct{ location *time.Location }
func ( *scanPlanTextTimestamptzToTimestamptzScanner) ( []byte, any) error {
:= ().(TimestamptzScanner)
if == nil {
return .ScanTimestamptz(Timestamptz{})
}
var Timestamptz
:= string()
switch {
case "infinity":
= Timestamptz{Valid: true, InfinityModifier: Infinity}
case "-infinity":
= Timestamptz{Valid: true, InfinityModifier: -Infinity}
default:
:= false
if strings.HasSuffix(, " BC") {
= [:len()-3]
= true
}
var string
if len() >= 9 && ([len()-9] == '-' || [len()-9] == '+') {
= pgTimestamptzSecondFormat
} else if len() >= 6 && ([len()-6] == '-' || [len()-6] == '+') {
= pgTimestamptzMinuteFormat
} else {
= pgTimestamptzHourFormat
}
, := time.Parse(, )
if != nil {
return
}
if {
:= -.Year() + 1
= time.Date(, .Month(), .Day(), .Hour(), .Minute(), .Second(), .Nanosecond(), .Location())
}
if .location != nil {
= .In(.location)
}
= Timestamptz{Time: , Valid: true}
}
return .ScanTimestamptz()
}
func ( *TimestamptzCodec) ( *Map, uint32, int16, []byte) (driver.Value, error) {
if == nil {
return nil, nil
}
var Timestamptz
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( *TimestamptzCodec) ( *Map, uint32, int16, []byte) (any, error) {
if == nil {
return nil, nil
}
var Timestamptz
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier, nil
}
return .Time, nil
}