From 0c166c7620b50edf7d05c38de991132c90bf6318 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 12 Mar 2022 12:47:01 -0600 Subject: [PATCH] Fix BC dates in text format --- pgtype/date.go | 54 ++++++++++++++++++++++++++++++++++++--------- pgtype/date_test.go | 4 ++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/pgtype/date.go b/pgtype/date.go index 1d27fc78..db331e6c 100644 --- a/pgtype/date.go +++ b/pgtype/date.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/json" "fmt" + "strconv" "time" "github.com/jackc/pgx/v5/internal/pgio" @@ -182,18 +183,32 @@ func (encodePlanDateCodecText) Encode(value interface{}, buf []byte) (newBuf []b return nil, nil } - var s string - switch date.InfinityModifier { case Finite: - s = date.Time.Format("2006-01-02") + // Year 0000 is 1 BC + bc := false + year := date.Time.Year() + if year <= 0 { + year = -year + 1 + bc = true + } + + buf = strconv.AppendInt(buf, int64(year), 10) + buf = append(buf, '-') + buf = strconv.AppendInt(buf, int64(date.Time.Month()), 10) + buf = append(buf, '-') + buf = strconv.AppendInt(buf, int64(date.Time.Day()), 10) + + if bc { + buf = append(buf, " BC"...) + } case Infinity: - s = "infinity" + buf = append(buf, "infinity"...) case NegativeInfinity: - s = "-infinity" + buf = append(buf, "-infinity"...) } - return append(buf, s...), nil + return buf, nil } func (DateCodec) PlanScan(m *Map, oid uint32, format int16, target interface{}) ScanPlan { @@ -256,12 +271,29 @@ func (scanPlanTextAnyToDateScanner) Scan(src []byte, dst interface{}) error { case "-infinity": return scanner.ScanDate(Date{InfinityModifier: -Infinity, Valid: true}) default: - t, err := time.ParseInLocation("2006-01-02", sbuf, time.UTC) - if err != nil { - return err - } + if len(sbuf) >= 10 { + year, err := strconv.ParseInt(sbuf[0:4], 10, 32) + if err != nil { + return fmt.Errorf("cannot parse year: %v", err) + } + month, err := strconv.ParseInt(sbuf[5:7], 10, 32) + if err != nil { + return fmt.Errorf("cannot parse month: %v", err) + } + day, err := strconv.ParseInt(sbuf[8:10], 10, 32) + if err != nil { + return fmt.Errorf("cannot parse day: %v", err) + } - return scanner.ScanDate(Date{Time: t, Valid: true}) + if len(sbuf) == 13 && sbuf[11:] == "BC" { + year = -year + 1 + } + + t := time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC) + return scanner.ScanDate(Date{Time: t, Valid: true}) + } else { + return fmt.Errorf("date too short") + } } } diff --git a/pgtype/date_test.go b/pgtype/date_test.go index d57b9115..06539822 100644 --- a/pgtype/date_test.go +++ b/pgtype/date_test.go @@ -19,6 +19,10 @@ func isExpectedEqTime(a interface{}) func(interface{}) bool { func TestDateCodec(t *testing.T) { testutil.RunTranscodeTests(t, "date", []testutil.TranscodeTestCase{ + {time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-100, 1, 1, 0, 0, 0, 0, time.UTC))}, + {time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(-1, 1, 1, 0, 0, 0, 0, time.UTC))}, + {time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC))}, + {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC))}, {time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), new(time.Time), isExpectedEqTime(time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC))},