...

Source file src/github.com/jackc/pgtype/timestamptz.go

Documentation: github.com/jackc/pgtype

     1  package pgtype
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/jackc/pgio"
    11  )
    12  
    13  const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
    14  const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
    15  const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
    16  const microsecFromUnixEpochToY2K = 946684800 * 1000000
    17  
    18  const (
    19  	negativeInfinityMicrosecondOffset = -9223372036854775808
    20  	infinityMicrosecondOffset         = 9223372036854775807
    21  )
    22  
    23  type Timestamptz struct {
    24  	Time             time.Time
    25  	Status           Status
    26  	InfinityModifier InfinityModifier
    27  }
    28  
    29  func (dst *Timestamptz) Set(src interface{}) error {
    30  	if src == nil {
    31  		*dst = Timestamptz{Status: Null}
    32  		return nil
    33  	}
    34  
    35  	if value, ok := src.(interface{ Get() interface{} }); ok {
    36  		value2 := value.Get()
    37  		if value2 != value {
    38  			return dst.Set(value2)
    39  		}
    40  	}
    41  
    42  	switch value := src.(type) {
    43  	case time.Time:
    44  		*dst = Timestamptz{Time: value, Status: Present}
    45  	case *time.Time:
    46  		if value == nil {
    47  			*dst = Timestamptz{Status: Null}
    48  		} else {
    49  			return dst.Set(*value)
    50  		}
    51  	case string:
    52  		return dst.DecodeText(nil, []byte(value))
    53  	case *string:
    54  		if value == nil {
    55  			*dst = Timestamptz{Status: Null}
    56  		} else {
    57  			return dst.Set(*value)
    58  		}
    59  	case InfinityModifier:
    60  		*dst = Timestamptz{InfinityModifier: value, Status: Present}
    61  	default:
    62  		if originalSrc, ok := underlyingTimeType(src); ok {
    63  			return dst.Set(originalSrc)
    64  		}
    65  		return fmt.Errorf("cannot convert %v to Timestamptz", value)
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func (dst Timestamptz) Get() interface{} {
    72  	switch dst.Status {
    73  	case Present:
    74  		if dst.InfinityModifier != None {
    75  			return dst.InfinityModifier
    76  		}
    77  		return dst.Time
    78  	case Null:
    79  		return nil
    80  	default:
    81  		return dst.Status
    82  	}
    83  }
    84  
    85  func (src *Timestamptz) AssignTo(dst interface{}) error {
    86  	switch src.Status {
    87  	case Present:
    88  		switch v := dst.(type) {
    89  		case *time.Time:
    90  			if src.InfinityModifier != None {
    91  				return fmt.Errorf("cannot assign %v to %T", src, dst)
    92  			}
    93  			*v = src.Time
    94  			return nil
    95  		default:
    96  			if nextDst, retry := GetAssignToDstType(dst); retry {
    97  				return src.AssignTo(nextDst)
    98  			}
    99  			return fmt.Errorf("unable to assign to %T", dst)
   100  		}
   101  	case Null:
   102  		return NullAssignTo(dst)
   103  	}
   104  
   105  	return fmt.Errorf("cannot decode %#v into %T", src, dst)
   106  }
   107  
   108  func (dst *Timestamptz) DecodeText(ci *ConnInfo, src []byte) error {
   109  	if src == nil {
   110  		*dst = Timestamptz{Status: Null}
   111  		return nil
   112  	}
   113  
   114  	sbuf := string(src)
   115  	switch sbuf {
   116  	case "infinity":
   117  		*dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
   118  	case "-infinity":
   119  		*dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
   120  	default:
   121  		var format string
   122  		if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
   123  			format = pgTimestamptzSecondFormat
   124  		} else if len(sbuf) >= 6 && (sbuf[len(sbuf)-6] == '-' || sbuf[len(sbuf)-6] == '+') {
   125  			format = pgTimestamptzMinuteFormat
   126  		} else {
   127  			format = pgTimestamptzHourFormat
   128  		}
   129  
   130  		tim, err := time.Parse(format, sbuf)
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		*dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present}
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (dst *Timestamptz) DecodeBinary(ci *ConnInfo, src []byte) error {
   142  	if src == nil {
   143  		*dst = Timestamptz{Status: Null}
   144  		return nil
   145  	}
   146  
   147  	if len(src) != 8 {
   148  		return fmt.Errorf("invalid length for timestamptz: %v", len(src))
   149  	}
   150  
   151  	microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
   152  
   153  	switch microsecSinceY2K {
   154  	case infinityMicrosecondOffset:
   155  		*dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
   156  	case negativeInfinityMicrosecondOffset:
   157  		*dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
   158  	default:
   159  		tim := time.Unix(
   160  			microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
   161  			(microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
   162  		)
   163  		*dst = Timestamptz{Time: tim, Status: Present}
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (src Timestamptz) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
   170  	switch src.Status {
   171  	case Null:
   172  		return nil, nil
   173  	case Undefined:
   174  		return nil, errUndefined
   175  	}
   176  
   177  	var s string
   178  
   179  	switch src.InfinityModifier {
   180  	case None:
   181  		s = src.Time.UTC().Truncate(time.Microsecond).Format(pgTimestamptzSecondFormat)
   182  	case Infinity:
   183  		s = "infinity"
   184  	case NegativeInfinity:
   185  		s = "-infinity"
   186  	}
   187  
   188  	return append(buf, s...), nil
   189  }
   190  
   191  func (src Timestamptz) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
   192  	switch src.Status {
   193  	case Null:
   194  		return nil, nil
   195  	case Undefined:
   196  		return nil, errUndefined
   197  	}
   198  
   199  	var microsecSinceY2K int64
   200  	switch src.InfinityModifier {
   201  	case None:
   202  		microsecSinceUnixEpoch := src.Time.Unix()*1000000 + int64(src.Time.Nanosecond())/1000
   203  		microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
   204  	case Infinity:
   205  		microsecSinceY2K = infinityMicrosecondOffset
   206  	case NegativeInfinity:
   207  		microsecSinceY2K = negativeInfinityMicrosecondOffset
   208  	}
   209  
   210  	return pgio.AppendInt64(buf, microsecSinceY2K), nil
   211  }
   212  
   213  // Scan implements the database/sql Scanner interface.
   214  func (dst *Timestamptz) Scan(src interface{}) error {
   215  	if src == nil {
   216  		*dst = Timestamptz{Status: Null}
   217  		return nil
   218  	}
   219  
   220  	switch src := src.(type) {
   221  	case string:
   222  		return dst.DecodeText(nil, []byte(src))
   223  	case []byte:
   224  		srcCopy := make([]byte, len(src))
   225  		copy(srcCopy, src)
   226  		return dst.DecodeText(nil, srcCopy)
   227  	case time.Time:
   228  		*dst = Timestamptz{Time: src, Status: Present}
   229  		return nil
   230  	}
   231  
   232  	return fmt.Errorf("cannot scan %T", src)
   233  }
   234  
   235  // Value implements the database/sql/driver Valuer interface.
   236  func (src Timestamptz) Value() (driver.Value, error) {
   237  	switch src.Status {
   238  	case Present:
   239  		if src.InfinityModifier != None {
   240  			return src.InfinityModifier.String(), nil
   241  		}
   242  		if src.Time.Location().String() == time.UTC.String() {
   243  			return src.Time.UTC(), nil
   244  		}
   245  		return src.Time, nil
   246  	case Null:
   247  		return nil, nil
   248  	default:
   249  		return nil, errUndefined
   250  	}
   251  }
   252  
   253  func (src Timestamptz) MarshalJSON() ([]byte, error) {
   254  	switch src.Status {
   255  	case Null:
   256  		return []byte("null"), nil
   257  	case Undefined:
   258  		return nil, errUndefined
   259  	}
   260  
   261  	if src.Status != Present {
   262  		return nil, errBadStatus
   263  	}
   264  
   265  	var s string
   266  
   267  	switch src.InfinityModifier {
   268  	case None:
   269  		s = src.Time.Format(time.RFC3339Nano)
   270  	case Infinity:
   271  		s = "infinity"
   272  	case NegativeInfinity:
   273  		s = "-infinity"
   274  	}
   275  
   276  	return json.Marshal(s)
   277  }
   278  
   279  func (dst *Timestamptz) UnmarshalJSON(b []byte) error {
   280  	var s *string
   281  	err := json.Unmarshal(b, &s)
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	if s == nil {
   287  		*dst = Timestamptz{Status: Null}
   288  		return nil
   289  	}
   290  
   291  	switch *s {
   292  	case "infinity":
   293  		*dst = Timestamptz{Status: Present, InfinityModifier: Infinity}
   294  	case "-infinity":
   295  		*dst = Timestamptz{Status: Present, InfinityModifier: -Infinity}
   296  	default:
   297  		// PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz
   298  		tim, err := time.Parse(time.RFC3339Nano, *s)
   299  		if err != nil {
   300  			return err
   301  		}
   302  
   303  		*dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present}
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  // Normalize timestamps in UTC location to behave similarly to how the Golang
   310  // standard library does it: UTC timestamps lack a .loc value.
   311  //
   312  // Reason for this: when comparing two timestamps with reflect.DeepEqual (generally
   313  // speaking not a good idea, but several testing libraries (for example testify)
   314  // does this), their location data needs to be equal for them to be considered
   315  // equal.
   316  func normalizePotentialUTC(timestamp time.Time) time.Time {
   317  	if timestamp.Location().String() != time.UTC.String() {
   318  		return timestamp
   319  	}
   320  
   321  	return timestamp.UTC()
   322  }
   323  

View as plain text