package pgtype
import (
)
type DateScanner interface {
ScanDate(v Date) error
}
type DateValuer interface {
DateValue() (Date, error)
}
type Date struct {
Time time.Time
InfinityModifier InfinityModifier
Valid bool
}
func ( *Date) ( Date) error {
* =
return nil
}
func ( Date) () (Date, error) {
return , nil
}
const (
negativeInfinityDayOffset = -2147483648
infinityDayOffset = 2147483647
)
func ( *Date) ( any) error {
if == nil {
* = Date{}
return nil
}
switch src := .(type) {
case string:
return scanPlanTextAnyToDateScanner{}.Scan([]byte(), )
case time.Time:
* = Date{Time: , Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", )
}
func ( Date) () (driver.Value, error) {
if !.Valid {
return nil, nil
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( Date) () ([]byte, error) {
if !.Valid {
return []byte("null"), nil
}
var string
switch .InfinityModifier {
case Finite:
= .Time.Format("2006-01-02")
case Infinity:
= "infinity"
case NegativeInfinity:
= "-infinity"
}
return json.Marshal()
}
func ( *Date) ( []byte) error {
var *string
:= json.Unmarshal(, &)
if != nil {
return
}
if == nil {
* = Date{}
return nil
}
switch * {
case "infinity":
* = Date{Valid: true, InfinityModifier: Infinity}
case "-infinity":
* = Date{Valid: true, InfinityModifier: -Infinity}
default:
, := time.ParseInLocation("2006-01-02", *, time.UTC)
if != nil {
return
}
* = Date{Time: , Valid: true}
}
return nil
}
type DateCodec struct{}
func (DateCodec) ( int16) bool {
return == TextFormatCode || == BinaryFormatCode
}
func (DateCodec) () int16 {
return BinaryFormatCode
}
func (DateCodec) ( *Map, uint32, int16, any) EncodePlan {
if , := .(DateValuer); ! {
return nil
}
switch {
case BinaryFormatCode:
return encodePlanDateCodecBinary{}
case TextFormatCode:
return encodePlanDateCodecText{}
}
return nil
}
type encodePlanDateCodecBinary struct{}
func (encodePlanDateCodecBinary) ( any, []byte) ( []byte, error) {
, := .(DateValuer).DateValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
var int32
switch .InfinityModifier {
case Finite:
:= time.Date(.Time.Year(), .Time.Month(), .Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
:= time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
:= -
= int32( / 86400)
case Infinity:
= infinityDayOffset
case NegativeInfinity:
= negativeInfinityDayOffset
}
return pgio.AppendInt32(, ), nil
}
type encodePlanDateCodecText struct{}
func (encodePlanDateCodecText) ( any, []byte) ( []byte, error) {
, := .(DateValuer).DateValue()
if != nil {
return nil,
}
if !.Valid {
return nil, nil
}
switch .InfinityModifier {
case Finite:
:= false
:= .Time.Year()
if <= 0 {
= - + 1
= true
}
:= strconv.AppendInt(make([]byte, 0, 6), int64(), 10)
for := len(); < 4; ++ {
= append(, '0')
}
= append(, ...)
= append(, '-')
if .Time.Month() < 10 {
= append(, '0')
}
= strconv.AppendInt(, int64(.Time.Month()), 10)
= append(, '-')
if .Time.Day() < 10 {
= append(, '0')
}
= strconv.AppendInt(, int64(.Time.Day()), 10)
if {
= append(, " BC"...)
}
case Infinity:
= append(, "infinity"...)
case NegativeInfinity:
= append(, "-infinity"...)
}
return , nil
}
func (DateCodec) ( *Map, uint32, int16, any) ScanPlan {
switch {
case BinaryFormatCode:
switch .(type) {
case DateScanner:
return scanPlanBinaryDateToDateScanner{}
}
case TextFormatCode:
switch .(type) {
case DateScanner:
return scanPlanTextAnyToDateScanner{}
}
}
return nil
}
type scanPlanBinaryDateToDateScanner struct{}
func (scanPlanBinaryDateToDateScanner) ( []byte, any) error {
:= ().(DateScanner)
if == nil {
return .ScanDate(Date{})
}
if len() != 4 {
return fmt.Errorf("invalid length for date: %v", len())
}
:= int32(binary.BigEndian.Uint32())
switch {
case infinityDayOffset:
return .ScanDate(Date{InfinityModifier: Infinity, Valid: true})
case negativeInfinityDayOffset:
return .ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
default:
:= time.Date(2000, 1, int(1+), 0, 0, 0, 0, time.UTC)
return .ScanDate(Date{Time: , Valid: true})
}
}
type scanPlanTextAnyToDateScanner struct{}
func (scanPlanTextAnyToDateScanner) ( []byte, any) error {
:= ().(DateScanner)
if == nil {
return .ScanDate(Date{})
}
if len() == 8 && string() == "infinity" {
return .ScanDate(Date{InfinityModifier: Infinity, Valid: true})
}
if len() == 9 && string() == "-infinity" {
return .ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
}
if len() < 10 {
return fmt.Errorf("invalid date format")
}
:= false
:=
if len() >= 13 && string([len()-3:]) == " BC" {
= true
= [:len()-3]
}
:= -1
for := 4; < len(); ++ {
if [] == '-' {
=
break
}
if [] < '0' || [] > '9' {
return fmt.Errorf("invalid date format")
}
}
if == -1 || +6 > len() {
return fmt.Errorf("invalid date format")
}
if [+3] != '-' {
return fmt.Errorf("invalid date format")
}
, := parseDigits([:])
if != nil {
return fmt.Errorf("invalid date format")
}
, := parse2Digits([+1 : +3])
if != nil {
return fmt.Errorf("invalid date format")
}
, := parse2Digits([+4 : +6])
if != nil {
return fmt.Errorf("invalid date format")
}
if +6 != len() {
return fmt.Errorf("invalid date format")
}
if {
= - + 1
}
:= time.Date(int(), time.Month(), int(), 0, 0, 0, 0, time.UTC)
return .ScanDate(Date{Time: , Valid: true})
}
func ( []byte) (int64, error) {
if len() != 2 {
return 0, fmt.Errorf("expected 2 digits")
}
, := [0], [1]
if < '0' || > '9' || < '0' || > '9' {
return 0, fmt.Errorf("expected digits")
}
return int64(-'0')*10 + int64(-'0'), nil
}
func ( []byte) (int64, error) {
if len() == 0 {
return 0, fmt.Errorf("empty")
}
var int64
for , := range {
if < '0' || > '9' {
return 0, fmt.Errorf("non-digit")
}
= *10 + int64(-'0')
}
return , nil
}
func ( DateCodec) ( *Map, uint32, int16, []byte) (driver.Value, error) {
if == nil {
return nil, nil
}
var Date
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier.String(), nil
}
return .Time, nil
}
func ( DateCodec) ( *Map, uint32, int16, []byte) (any, error) {
if == nil {
return nil, nil
}
var Date
:= codecScan(, , , , , &)
if != nil {
return nil,
}
if .InfinityModifier != Finite {
return .InfinityModifier, nil
}
return .Time, nil
}