...

Source file src/github.com/jackc/pgx/v5/pgtype/timestamp.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  	"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  }
   346  

View as plain text