mirror of https://github.com/jackc/pgx.git
Add timestamptz null and infinity
parent
7d9f954de4
commit
de8c140cfb
|
@ -62,6 +62,12 @@ func forceEncoder(e interface{}, formatCode int16) interface{} {
|
|||
}
|
||||
|
||||
func testSuccessfulTranscode(t testing.TB, pgTypeName string, values []interface{}) {
|
||||
testSuccessfulTranscodeEqFunc(t, pgTypeName, values, func(a, b interface{}) bool {
|
||||
return reflect.DeepEqual(a, b)
|
||||
})
|
||||
}
|
||||
|
||||
func testSuccessfulTranscodeEqFunc(t testing.TB, pgTypeName string, values []interface{}, eqFunc func(a, b interface{}) bool) {
|
||||
conn := mustConnectPgx(t)
|
||||
defer mustClose(t, conn)
|
||||
|
||||
|
@ -87,7 +93,7 @@ func testSuccessfulTranscode(t testing.TB, pgTypeName string, values []interface
|
|||
t.Errorf("%v %d: %v", fc.name, i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Elem().Interface(), v) {
|
||||
if !eqFunc(result.Elem().Interface(), v) {
|
||||
t.Errorf("%v %d: expected %v, got %v", fc.name, i, v, result.Interface())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,36 @@ import (
|
|||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
const pgTimestamptzFormat = "2006-01-02 15:04:05.999999999Z07:00"
|
||||
const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
|
||||
const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
|
||||
const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
|
||||
const microsecFromUnixEpochToY2K = 946684800 * 1000000
|
||||
|
||||
const (
|
||||
negativeInfinityMicrosecondOffset = -9223372036854775808
|
||||
infinityMicrosecondOffset = 9223372036854775807
|
||||
)
|
||||
|
||||
type Timestamptz struct {
|
||||
// time.Time is embedded to handle Infinity and -Infinity
|
||||
// TODO - infinity
|
||||
t time.Time
|
||||
Time time.Time
|
||||
Status Status
|
||||
InfinityModifier
|
||||
}
|
||||
|
||||
func (t *Timestamptz) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Timestamptz:
|
||||
*t = value
|
||||
case time.Time:
|
||||
*t = Timestamptz{Time: value, Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingTimeType(src); ok {
|
||||
return t.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Timestamptz", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Timestamptz) DecodeText(r io.Reader) error {
|
||||
|
@ -24,7 +47,8 @@ func (t *Timestamptz) DecodeText(r io.Reader) error {
|
|||
}
|
||||
|
||||
if size == -1 {
|
||||
return fmt.Errorf("invalid length for timestamptz: %v", size)
|
||||
*t = Timestamptz{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, int(size))
|
||||
|
@ -33,9 +57,28 @@ func (t *Timestamptz) DecodeText(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
t.t, err = time.Parse(pgTimestamptzFormat, string(buf))
|
||||
if err != nil {
|
||||
return err
|
||||
sbuf := string(buf)
|
||||
switch sbuf {
|
||||
case "infinity":
|
||||
*t = Timestamptz{Status: Present, InfinityModifier: Infinity}
|
||||
case "-infinity":
|
||||
*t = Timestamptz{Status: Present, InfinityModifier: -Infinity}
|
||||
default:
|
||||
var format string
|
||||
if sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+' {
|
||||
format = pgTimestamptzSecondFormat
|
||||
} else if sbuf[len(sbuf)-6] == '-' || sbuf[len(sbuf)-6] == '+' {
|
||||
format = pgTimestamptzMinuteFormat
|
||||
} else {
|
||||
format = pgTimestamptzHourFormat
|
||||
}
|
||||
|
||||
tim, err := time.Parse(format, sbuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = Timestamptz{Time: tim, Status: Present}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -47,6 +90,11 @@ func (t *Timestamptz) DecodeBinary(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*t = Timestamptz{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
if size != 8 {
|
||||
return fmt.Errorf("invalid length for timestamptz: %v", size)
|
||||
}
|
||||
|
@ -56,41 +104,66 @@ func (t *Timestamptz) DecodeBinary(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K
|
||||
t.t = time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000)
|
||||
switch microsecSinceY2K {
|
||||
case infinityMicrosecondOffset:
|
||||
*t = Timestamptz{Status: Present, InfinityModifier: Infinity}
|
||||
case negativeInfinityMicrosecondOffset:
|
||||
*t = Timestamptz{Status: Present, InfinityModifier: -Infinity}
|
||||
default:
|
||||
microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K
|
||||
tim := time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000)
|
||||
*t = Timestamptz{Time: tim, Status: Present}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Timestamptz) EncodeText(w io.Writer) error {
|
||||
buf := []byte(t.t.Format(pgTimestamptzFormat))
|
||||
if done, err := encodeNotPresent(w, t.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, int32(len(buf)))
|
||||
var s string
|
||||
|
||||
switch t.InfinityModifier {
|
||||
case None:
|
||||
s = t.Time.UTC().Format(pgTimestamptzSecondFormat)
|
||||
case Infinity:
|
||||
s = "infinity"
|
||||
case NegativeInfinity:
|
||||
s = "-infinity"
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, int32(len(s)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = w.Write(buf)
|
||||
_, err = w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func (t Timestamptz) EncodeBinary(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, t.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
microsecSinceUnixEpoch := t.t.Unix()*1000000 + int64(t.t.Nanosecond())/1000
|
||||
microsecSinceY2K := microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
|
||||
var microsecSinceY2K int64
|
||||
switch t.InfinityModifier {
|
||||
case None:
|
||||
microsecSinceUnixEpoch := t.Time.Unix()*1000000 + int64(t.Time.Nanosecond())/1000
|
||||
microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
|
||||
case Infinity:
|
||||
microsecSinceY2K = infinityMicrosecondOffset
|
||||
case NegativeInfinity:
|
||||
microsecSinceY2K = negativeInfinityMicrosecondOffset
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt64(w, microsecSinceY2K)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t Timestamptz) Time() time.Time {
|
||||
return t.t
|
||||
}
|
||||
|
||||
func TimestamptzFromTime(t time.Time) Timestamptz {
|
||||
return Timestamptz{t: t}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestTimestamptzTranscode(t *testing.T) {
|
||||
testSuccessfulTranscodeEqFunc(t, "timestamptz", []interface{}{
|
||||
pgtype.Timestamptz{Time: time.Date(1800, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1905, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1940, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1960, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present},
|
||||
pgtype.Timestamptz{Status: pgtype.Null},
|
||||
pgtype.Timestamptz{Status: pgtype.Present, InfinityModifier: pgtype.Infinity},
|
||||
pgtype.Timestamptz{Status: pgtype.Present, InfinityModifier: -pgtype.Infinity},
|
||||
}, func(a, b interface{}) bool {
|
||||
at := a.(pgtype.Timestamptz)
|
||||
bt := b.(pgtype.Timestamptz)
|
||||
|
||||
return at.Time.Equal(bt.Time) && at.Status == bt.Status && at.InfinityModifier == bt.InfinityModifier
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimestamptzConvertFrom(t *testing.T) {
|
||||
type _time time.Time
|
||||
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Timestamptz
|
||||
}{
|
||||
{source: time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: time.Date(1999, 12, 31, 12, 59, 59, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(1999, 12, 31, 12, 59, 59, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: time.Date(2000, 1, 1, 0, 0, 1, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(2000, 1, 1, 0, 0, 1, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: time.Date(2200, 1, 1, 0, 0, 0, 0, time.Local), result: pgtype.Timestamptz{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
|
||||
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)), result: pgtype.Timestamptz{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var d pgtype.Timestamptz
|
||||
err := d.ConvertFrom(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if d != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d)
|
||||
}
|
||||
}
|
||||
}
|
14
values.go
14
values.go
|
@ -2038,7 +2038,12 @@ func encodeTime(w *WriteBuf, oid OID, value time.Time) error {
|
|||
}
|
||||
return d.EncodeBinary(w)
|
||||
case TimestampTzOID, TimestampOID:
|
||||
return pgtype.TimestamptzFromTime(value).EncodeBinary(w)
|
||||
var t pgtype.Timestamptz
|
||||
err := t.ConvertFrom(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.EncodeBinary(w)
|
||||
default:
|
||||
return fmt.Errorf("cannot encode %s into oid %v", "time.Time", oid)
|
||||
}
|
||||
|
@ -2078,7 +2083,12 @@ func decodeTimestampTz(vr *ValueReader) time.Time {
|
|||
return time.Time{}
|
||||
}
|
||||
|
||||
return t.Time()
|
||||
if t.Status == pgtype.Null {
|
||||
vr.Fatal(ProtocolError("Cannot decode null into time.Time"))
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
return t.Time
|
||||
}
|
||||
|
||||
func decodeTimestamp(vr *ValueReader) time.Time {
|
||||
|
|
Loading…
Reference in New Issue