...

Source file src/github.com/jackc/pgx/v5/pgtype/date.go

Documentation: github.com/jackc/pgx/v5/pgtype

     1  package pgtype
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"fmt"
     8  	"regexp"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/jackc/pgx/v5/internal/pgio"
    13  )
    14  
    15  type DateScanner interface {
    16  	ScanDate(v Date) error
    17  }
    18  
    19  type DateValuer interface {
    20  	DateValue() (Date, error)
    21  }
    22  
    23  type Date struct {
    24  	Time             time.Time
    25  	InfinityModifier InfinityModifier
    26  	Valid            bool
    27  }
    28  
    29  func (d *Date) ScanDate(v Date) error {
    30  	*d = v
    31  	return nil
    32  }
    33  
    34  func (d Date) DateValue() (Date, error) {
    35  	return d, nil
    36  }
    37  
    38  const (
    39  	negativeInfinityDayOffset = -2147483648
    40  	infinityDayOffset         = 2147483647
    41  )
    42  
    43  // Scan implements the database/sql Scanner interface.
    44  func (dst *Date) Scan(src any) error {
    45  	if src == nil {
    46  		*dst = Date{}
    47  		return nil
    48  	}
    49  
    50  	switch src := src.(type) {
    51  	case string:
    52  		return scanPlanTextAnyToDateScanner{}.Scan([]byte(src), dst)
    53  	case time.Time:
    54  		*dst = Date{Time: src, Valid: true}
    55  		return nil
    56  	}
    57  
    58  	return fmt.Errorf("cannot scan %T", src)
    59  }
    60  
    61  // Value implements the database/sql/driver Valuer interface.
    62  func (src Date) Value() (driver.Value, error) {
    63  	if !src.Valid {
    64  		return nil, nil
    65  	}
    66  
    67  	if src.InfinityModifier != Finite {
    68  		return src.InfinityModifier.String(), nil
    69  	}
    70  	return src.Time, nil
    71  }
    72  
    73  func (src Date) MarshalJSON() ([]byte, error) {
    74  	if !src.Valid {
    75  		return []byte("null"), nil
    76  	}
    77  
    78  	var s string
    79  
    80  	switch src.InfinityModifier {
    81  	case Finite:
    82  		s = src.Time.Format("2006-01-02")
    83  	case Infinity:
    84  		s = "infinity"
    85  	case NegativeInfinity:
    86  		s = "-infinity"
    87  	}
    88  
    89  	return json.Marshal(s)
    90  }
    91  
    92  func (dst *Date) UnmarshalJSON(b []byte) error {
    93  	var s *string
    94  	err := json.Unmarshal(b, &s)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	if s == nil {
   100  		*dst = Date{}
   101  		return nil
   102  	}
   103  
   104  	switch *s {
   105  	case "infinity":
   106  		*dst = Date{Valid: true, InfinityModifier: Infinity}
   107  	case "-infinity":
   108  		*dst = Date{Valid: true, InfinityModifier: -Infinity}
   109  	default:
   110  		t, err := time.ParseInLocation("2006-01-02", *s, time.UTC)
   111  		if err != nil {
   112  			return err
   113  		}
   114  
   115  		*dst = Date{Time: t, Valid: true}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  type DateCodec struct{}
   122  
   123  func (DateCodec) FormatSupported(format int16) bool {
   124  	return format == TextFormatCode || format == BinaryFormatCode
   125  }
   126  
   127  func (DateCodec) PreferredFormat() int16 {
   128  	return BinaryFormatCode
   129  }
   130  
   131  func (DateCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
   132  	if _, ok := value.(DateValuer); !ok {
   133  		return nil
   134  	}
   135  
   136  	switch format {
   137  	case BinaryFormatCode:
   138  		return encodePlanDateCodecBinary{}
   139  	case TextFormatCode:
   140  		return encodePlanDateCodecText{}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  type encodePlanDateCodecBinary struct{}
   147  
   148  func (encodePlanDateCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
   149  	date, err := value.(DateValuer).DateValue()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	if !date.Valid {
   155  		return nil, nil
   156  	}
   157  
   158  	var daysSinceDateEpoch int32
   159  	switch date.InfinityModifier {
   160  	case Finite:
   161  		tUnix := time.Date(date.Time.Year(), date.Time.Month(), date.Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
   162  		dateEpoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
   163  
   164  		secSinceDateEpoch := tUnix - dateEpoch
   165  		daysSinceDateEpoch = int32(secSinceDateEpoch / 86400)
   166  	case Infinity:
   167  		daysSinceDateEpoch = infinityDayOffset
   168  	case NegativeInfinity:
   169  		daysSinceDateEpoch = negativeInfinityDayOffset
   170  	}
   171  
   172  	return pgio.AppendInt32(buf, daysSinceDateEpoch), nil
   173  }
   174  
   175  type encodePlanDateCodecText struct{}
   176  
   177  func (encodePlanDateCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
   178  	date, err := value.(DateValuer).DateValue()
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	if !date.Valid {
   184  		return nil, nil
   185  	}
   186  
   187  	switch date.InfinityModifier {
   188  	case Finite:
   189  		// Year 0000 is 1 BC
   190  		bc := false
   191  		year := date.Time.Year()
   192  		if year <= 0 {
   193  			year = -year + 1
   194  			bc = true
   195  		}
   196  
   197  		yearBytes := strconv.AppendInt(make([]byte, 0, 6), int64(year), 10)
   198  		for i := len(yearBytes); i < 4; i++ {
   199  			buf = append(buf, '0')
   200  		}
   201  		buf = append(buf, yearBytes...)
   202  		buf = append(buf, '-')
   203  		if date.Time.Month() < 10 {
   204  			buf = append(buf, '0')
   205  		}
   206  		buf = strconv.AppendInt(buf, int64(date.Time.Month()), 10)
   207  		buf = append(buf, '-')
   208  		if date.Time.Day() < 10 {
   209  			buf = append(buf, '0')
   210  		}
   211  		buf = strconv.AppendInt(buf, int64(date.Time.Day()), 10)
   212  
   213  		if bc {
   214  			buf = append(buf, " BC"...)
   215  		}
   216  	case Infinity:
   217  		buf = append(buf, "infinity"...)
   218  	case NegativeInfinity:
   219  		buf = append(buf, "-infinity"...)
   220  	}
   221  
   222  	return buf, nil
   223  }
   224  
   225  func (DateCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
   226  
   227  	switch format {
   228  	case BinaryFormatCode:
   229  		switch target.(type) {
   230  		case DateScanner:
   231  			return scanPlanBinaryDateToDateScanner{}
   232  		}
   233  	case TextFormatCode:
   234  		switch target.(type) {
   235  		case DateScanner:
   236  			return scanPlanTextAnyToDateScanner{}
   237  		}
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  type scanPlanBinaryDateToDateScanner struct{}
   244  
   245  func (scanPlanBinaryDateToDateScanner) Scan(src []byte, dst any) error {
   246  	scanner := (dst).(DateScanner)
   247  
   248  	if src == nil {
   249  		return scanner.ScanDate(Date{})
   250  	}
   251  
   252  	if len(src) != 4 {
   253  		return fmt.Errorf("invalid length for date: %v", len(src))
   254  	}
   255  
   256  	dayOffset := int32(binary.BigEndian.Uint32(src))
   257  
   258  	switch dayOffset {
   259  	case infinityDayOffset:
   260  		return scanner.ScanDate(Date{InfinityModifier: Infinity, Valid: true})
   261  	case negativeInfinityDayOffset:
   262  		return scanner.ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
   263  	default:
   264  		t := time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.UTC)
   265  		return scanner.ScanDate(Date{Time: t, Valid: true})
   266  	}
   267  }
   268  
   269  type scanPlanTextAnyToDateScanner struct{}
   270  
   271  var dateRegexp = regexp.MustCompile(`^(\d{4,})-(\d\d)-(\d\d)( BC)?$`)
   272  
   273  func (scanPlanTextAnyToDateScanner) Scan(src []byte, dst any) error {
   274  	scanner := (dst).(DateScanner)
   275  
   276  	if src == nil {
   277  		return scanner.ScanDate(Date{})
   278  	}
   279  
   280  	sbuf := string(src)
   281  	match := dateRegexp.FindStringSubmatch(sbuf)
   282  	if match != nil {
   283  		year, err := strconv.ParseInt(match[1], 10, 32)
   284  		if err != nil {
   285  			return fmt.Errorf("BUG: cannot parse date that regexp matched (year): %w", err)
   286  		}
   287  
   288  		month, err := strconv.ParseInt(match[2], 10, 32)
   289  		if err != nil {
   290  			return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err)
   291  		}
   292  
   293  		day, err := strconv.ParseInt(match[3], 10, 32)
   294  		if err != nil {
   295  			return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err)
   296  		}
   297  
   298  		// BC matched
   299  		if len(match[4]) > 0 {
   300  			year = -year + 1
   301  		}
   302  
   303  		t := time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC)
   304  		return scanner.ScanDate(Date{Time: t, Valid: true})
   305  	}
   306  
   307  	switch sbuf {
   308  	case "infinity":
   309  		return scanner.ScanDate(Date{InfinityModifier: Infinity, Valid: true})
   310  	case "-infinity":
   311  		return scanner.ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
   312  	default:
   313  		return fmt.Errorf("invalid date format")
   314  	}
   315  }
   316  
   317  func (c DateCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
   318  	if src == nil {
   319  		return nil, nil
   320  	}
   321  
   322  	var date Date
   323  	err := codecScan(c, m, oid, format, src, &date)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  
   328  	if date.InfinityModifier != Finite {
   329  		return date.InfinityModifier.String(), nil
   330  	}
   331  
   332  	return date.Time, nil
   333  }
   334  
   335  func (c DateCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
   336  	if src == nil {
   337  		return nil, nil
   338  	}
   339  
   340  	var date Date
   341  	err := codecScan(c, m, oid, format, src, &date)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	if date.InfinityModifier != Finite {
   347  		return date.InfinityModifier, nil
   348  	}
   349  
   350  	return date.Time, nil
   351  }
   352  

View as plain text