...

Source file src/github.com/jackc/pgx/v5/pgtype/timestamptz.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 pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
    15  const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
    16  const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
    17  const microsecFromUnixEpochToY2K = 946684800 * 1000000
    18  
    19  const (
    20  	negativeInfinityMicrosecondOffset = -9223372036854775808
    21  	infinityMicrosecondOffset         = 9223372036854775807
    22  )
    23  
    24  type TimestamptzScanner interface {
    25  	ScanTimestamptz(v Timestamptz) error
    26  }
    27  
    28  type TimestamptzValuer interface {
    29  	TimestamptzValue() (Timestamptz, error)
    30  }
    31  
    32  // Timestamptz represents the PostgreSQL timestamptz type.
    33  type Timestamptz struct {
    34  	Time             time.Time
    35  	InfinityModifier InfinityModifier
    36  	Valid            bool
    37  }
    38  
    39  func (tstz *Timestamptz) ScanTimestamptz(v Timestamptz) error {
    40  	*tstz = v
    41  	return nil
    42  }
    43  
    44  func (tstz Timestamptz) TimestamptzValue() (Timestamptz, error) {
    45  	return tstz, nil
    46  }
    47  
    48  // Scan implements the database/sql Scanner interface.
    49  func (tstz *Timestamptz) Scan(src any) error {
    50  	if src == nil {
    51  		*tstz = Timestamptz{}
    52  		return nil
    53  	}
    54  
    55  	switch src := src.(type) {
    56  	case string:
    57  		return scanPlanTextTimestamptzToTimestamptzScanner{}.Scan([]byte(src), tstz)
    58  	case time.Time:
    59  		*tstz = Timestamptz{Time: src, Valid: true}
    60  		return nil
    61  	}
    62  
    63  	return fmt.Errorf("cannot scan %T", src)
    64  }
    65  
    66  // Value implements the database/sql/driver Valuer interface.
    67  func (tstz Timestamptz) Value() (driver.Value, error) {
    68  	if !tstz.Valid {
    69  		return nil, nil
    70  	}
    71  
    72  	if tstz.InfinityModifier != Finite {
    73  		return tstz.InfinityModifier.String(), nil
    74  	}
    75  	return tstz.Time, nil
    76  }
    77  
    78  func (tstz Timestamptz) MarshalJSON() ([]byte, error) {
    79  	if !tstz.Valid {
    80  		return []byte("null"), nil
    81  	}
    82  
    83  	var s string
    84  
    85  	switch tstz.InfinityModifier {
    86  	case Finite:
    87  		s = tstz.Time.Format(time.RFC3339Nano)
    88  	case Infinity:
    89  		s = "infinity"
    90  	case NegativeInfinity:
    91  		s = "-infinity"
    92  	}
    93  
    94  	return json.Marshal(s)
    95  }
    96  
    97  func (tstz *Timestamptz) UnmarshalJSON(b []byte) error {
    98  	var s *string
    99  	err := json.Unmarshal(b, &s)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	if s == nil {
   105  		*tstz = Timestamptz{}
   106  		return nil
   107  	}
   108  
   109  	switch *s {
   110  	case "infinity":
   111  		*tstz = Timestamptz{Valid: true, InfinityModifier: Infinity}
   112  	case "-infinity":
   113  		*tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity}
   114  	default:
   115  		// PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz
   116  		tim, err := time.Parse(time.RFC3339Nano, *s)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		*tstz = Timestamptz{Time: tim, Valid: true}
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  type TimestamptzCodec struct{}
   128  
   129  func (TimestamptzCodec) FormatSupported(format int16) bool {
   130  	return format == TextFormatCode || format == BinaryFormatCode
   131  }
   132  
   133  func (TimestamptzCodec) PreferredFormat() int16 {
   134  	return BinaryFormatCode
   135  }
   136  
   137  func (TimestamptzCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
   138  	if _, ok := value.(TimestamptzValuer); !ok {
   139  		return nil
   140  	}
   141  
   142  	switch format {
   143  	case BinaryFormatCode:
   144  		return encodePlanTimestamptzCodecBinary{}
   145  	case TextFormatCode:
   146  		return encodePlanTimestamptzCodecText{}
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  type encodePlanTimestamptzCodecBinary struct{}
   153  
   154  func (encodePlanTimestamptzCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
   155  	ts, err := value.(TimestamptzValuer).TimestamptzValue()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	if !ts.Valid {
   161  		return nil, nil
   162  	}
   163  
   164  	var microsecSinceY2K int64
   165  	switch ts.InfinityModifier {
   166  	case Finite:
   167  		microsecSinceUnixEpoch := ts.Time.Unix()*1000000 + int64(ts.Time.Nanosecond())/1000
   168  		microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
   169  	case Infinity:
   170  		microsecSinceY2K = infinityMicrosecondOffset
   171  	case NegativeInfinity:
   172  		microsecSinceY2K = negativeInfinityMicrosecondOffset
   173  	}
   174  
   175  	buf = pgio.AppendInt64(buf, microsecSinceY2K)
   176  
   177  	return buf, nil
   178  }
   179  
   180  type encodePlanTimestamptzCodecText struct{}
   181  
   182  func (encodePlanTimestamptzCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
   183  	ts, err := value.(TimestamptzValuer).TimestamptzValue()
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	if !ts.Valid {
   189  		return nil, nil
   190  	}
   191  
   192  	var s string
   193  
   194  	switch ts.InfinityModifier {
   195  	case Finite:
   196  
   197  		t := ts.Time.UTC().Truncate(time.Microsecond)
   198  
   199  		// Year 0000 is 1 BC
   200  		bc := false
   201  		if year := t.Year(); year <= 0 {
   202  			year = -year + 1
   203  			t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
   204  			bc = true
   205  		}
   206  
   207  		s = t.Format(pgTimestamptzSecondFormat)
   208  
   209  		if bc {
   210  			s = s + " BC"
   211  		}
   212  	case Infinity:
   213  		s = "infinity"
   214  	case NegativeInfinity:
   215  		s = "-infinity"
   216  	}
   217  
   218  	buf = append(buf, s...)
   219  
   220  	return buf, nil
   221  }
   222  
   223  func (TimestamptzCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
   224  
   225  	switch format {
   226  	case BinaryFormatCode:
   227  		switch target.(type) {
   228  		case TimestamptzScanner:
   229  			return scanPlanBinaryTimestamptzToTimestamptzScanner{}
   230  		}
   231  	case TextFormatCode:
   232  		switch target.(type) {
   233  		case TimestamptzScanner:
   234  			return scanPlanTextTimestamptzToTimestamptzScanner{}
   235  		}
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  type scanPlanBinaryTimestamptzToTimestamptzScanner struct{}
   242  
   243  func (scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
   244  	scanner := (dst).(TimestamptzScanner)
   245  
   246  	if src == nil {
   247  		return scanner.ScanTimestamptz(Timestamptz{})
   248  	}
   249  
   250  	if len(src) != 8 {
   251  		return fmt.Errorf("invalid length for timestamptz: %v", len(src))
   252  	}
   253  
   254  	var tstz Timestamptz
   255  	microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
   256  
   257  	switch microsecSinceY2K {
   258  	case infinityMicrosecondOffset:
   259  		tstz = Timestamptz{Valid: true, InfinityModifier: Infinity}
   260  	case negativeInfinityMicrosecondOffset:
   261  		tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity}
   262  	default:
   263  		tim := time.Unix(
   264  			microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
   265  			(microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
   266  		)
   267  		tstz = Timestamptz{Time: tim, Valid: true}
   268  	}
   269  
   270  	return scanner.ScanTimestamptz(tstz)
   271  }
   272  
   273  type scanPlanTextTimestamptzToTimestamptzScanner struct{}
   274  
   275  func (scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
   276  	scanner := (dst).(TimestamptzScanner)
   277  
   278  	if src == nil {
   279  		return scanner.ScanTimestamptz(Timestamptz{})
   280  	}
   281  
   282  	var tstz Timestamptz
   283  	sbuf := string(src)
   284  	switch sbuf {
   285  	case "infinity":
   286  		tstz = Timestamptz{Valid: true, InfinityModifier: Infinity}
   287  	case "-infinity":
   288  		tstz = Timestamptz{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  
   296  		var format string
   297  		if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
   298  			format = pgTimestamptzSecondFormat
   299  		} else if len(sbuf) >= 6 && (sbuf[len(sbuf)-6] == '-' || sbuf[len(sbuf)-6] == '+') {
   300  			format = pgTimestamptzMinuteFormat
   301  		} else {
   302  			format = pgTimestamptzHourFormat
   303  		}
   304  
   305  		tim, err := time.Parse(format, sbuf)
   306  		if err != nil {
   307  			return err
   308  		}
   309  
   310  		if bc {
   311  			year := -tim.Year() + 1
   312  			tim = time.Date(year, tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), tim.Location())
   313  		}
   314  
   315  		tstz = Timestamptz{Time: tim, Valid: true}
   316  	}
   317  
   318  	return scanner.ScanTimestamptz(tstz)
   319  }
   320  
   321  func (c TimestamptzCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
   322  	if src == nil {
   323  		return nil, nil
   324  	}
   325  
   326  	var tstz Timestamptz
   327  	err := codecScan(c, m, oid, format, src, &tstz)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	if tstz.InfinityModifier != Finite {
   333  		return tstz.InfinityModifier.String(), nil
   334  	}
   335  
   336  	return tstz.Time, nil
   337  }
   338  
   339  func (c TimestamptzCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
   340  	if src == nil {
   341  		return nil, nil
   342  	}
   343  
   344  	var tstz Timestamptz
   345  	err := codecScan(c, m, oid, format, src, &tstz)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	if tstz.InfinityModifier != Finite {
   351  		return tstz.InfinityModifier, nil
   352  	}
   353  
   354  	return tstz.Time, nil
   355  }
   356  

View as plain text