Skip to content

Commit

Permalink
Merge pull request #944 from otan-cockroach/attempt_2400_fix
Browse files Browse the repository at this point in the history
encode: fix 2400 time encoding for time/timetz
  • Loading branch information
maddyblue authored May 7, 2020
2 parents c904eab + 3dcc148 commit f3b22b2
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
20 changes: 20 additions & 0 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"sync"
Expand All @@ -16,6 +17,8 @@ import (
"github.com/lib/pq/oid"
)

var time2400Regex = regexp.MustCompile(`^(24:00(?::00(?:\.0+)?)?)(?:[Z+-].*)?$`)

func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte {
switch v := x.(type) {
case []byte:
Expand Down Expand Up @@ -202,10 +205,27 @@ func mustParse(f string, typ oid.Oid, s []byte) time.Time {
str[len(str)-3] == ':' {
f += ":00"
}
// Special case for 24:00 time.
// Unfortunately, golang does not parse 24:00 as a proper time.
// In this case, we want to try "round to the next day", to differentiate.
// As such, we find if the 24:00 time matches at the beginning; if so,
// we default it back to 00:00 but add a day later.
var is2400Time bool
switch typ {
case oid.T_timetz, oid.T_time:
if matches := time2400Regex.FindStringSubmatch(str); matches != nil {
// Concatenate timezone information at the back.
str = "00:00:00" + str[len(matches[1]):]
is2400Time = true
}
}
t, err := time.Parse(f, str)
if err != nil {
errorf("decode: %s", err)
}
if is2400Time {
t = t.Add(24 * time.Hour)
}
return t
}

Expand Down
79 changes: 79 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,85 @@ func TestFormatTsBackend(t *testing.T) {
}
}

func TestTimeWithoutTimezone(t *testing.T) {
db := openTestConn(t)
defer db.Close()

tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()

for _, tc := range []struct {
refTime string
expectedTime time.Time
}{
{"11:59:59", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
{"24:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00:00.0", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00:00.000000", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
} {
t.Run(
fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
func(t *testing.T) {
var gotTime time.Time
row := tx.QueryRow("select $1::time", tc.refTime)
err = row.Scan(&gotTime)
if err != nil {
t.Fatal(err)
}

if !tc.expectedTime.Equal(gotTime) {
t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
}
},
)
}
}

func TestTimeWithTimezone(t *testing.T) {
db := openTestConn(t)
defer db.Close()

tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()

for _, tc := range []struct {
refTime string
expectedTime time.Time
}{
{"11:59:59+00:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
{"11:59:59+04:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04", 4*60*60))},
{"24:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00Z", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00-04:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.FixedZone("-04", -4*60*60))},
{"24:00:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00:00.0+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
{"24:00:00.000000+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
} {
t.Run(
fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
func(t *testing.T) {
var gotTime time.Time
row := tx.QueryRow("select $1::timetz", tc.refTime)
err = row.Scan(&gotTime)
if err != nil {
t.Fatal(err)
}

if !tc.expectedTime.Equal(gotTime) {
t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
}
},
)
}
}

func TestTimestampWithTimeZone(t *testing.T) {
db := openTestConn(t)
defer db.Close()
Expand Down

0 comments on commit f3b22b2

Please sign in to comment.