Skip to content

Commit 5566fb6

Browse files
committed
Round time.Time to the nearest even microsecond
fixes #257
1 parent ae8662f commit 5566fb6

File tree

3 files changed

+196
-2
lines changed

3 files changed

+196
-2
lines changed

doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
// cal::local_date edgedb.LocalDate, edgedb.OptionalLocalDate
108108
// cal::local_time edgedb.LocalTime, edgedb.OptionalLocalTime
109109
// duration edgedb.Duration, edgedb.OptionalDuration
110-
// cal::relative_duraation edgedb.RelativeDuration,
110+
// cal::relative_duration edgedb.RelativeDuration,
111111
// edgedb.OptionalRelativeDuration
112112
// float32 float32, edgedb.OptionalFloat32
113113
// float64 float64, edgedb.OptionalFloat64

internal/codecs/datetime.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,14 @@ func (c *DateTimeCodec) Encode(
8585
func (c *DateTimeCodec) encodeData(w *buff.Writer, data time.Time) error {
8686
seconds := data.Unix() - 946_684_800
8787
nanoseconds := int64(data.Sub(time.Unix(data.Unix(), 0)))
88-
microseconds := seconds*1_000_000 + nanoseconds/1_000
88+
89+
rounded := nanoseconds / 1_000
90+
remainder := nanoseconds % 1_000
91+
if remainder == 500 && rounded%2 == 1 || remainder > 500 {
92+
rounded++
93+
}
94+
95+
microseconds := seconds*1_000_000 + rounded
8996
w.PushUint32(8) // data length
9097
w.PushUint64(uint64(microseconds))
9198
return nil

internal/codecs/datetime_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// This source file is part of the EdgeDB open source project.
2+
//
3+
// Copyright EdgeDB Inc. and the EdgeDB authors.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package codecs
18+
19+
import (
20+
"encoding/binary"
21+
"testing"
22+
"time"
23+
24+
"github.com/edgedb/edgedb-go/internal/buff"
25+
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
func TestRoundingGoTime(t *testing.T) {
30+
cases := []struct {
31+
name string
32+
input string
33+
expected int64
34+
}{
35+
{
36+
name: "maximum value",
37+
input: "9999-12-31T23:59:59.999999499Z",
38+
expected: 252455615999999999,
39+
},
40+
{
41+
name: "minimum value",
42+
input: "0001-01-01T00:00:00.000000Z",
43+
expected: -63082281600000000,
44+
},
45+
{
46+
// remainder: 500
47+
// rounding: +1
48+
input: "1814-03-09T01:02:03.000005500Z",
49+
expected: -5863791476999994,
50+
name: "negative unix timestamp round up",
51+
},
52+
{
53+
// remainder: 501
54+
// rounding: -1
55+
input: "1814-03-09T01:02:03.000005501Z",
56+
expected: -5863791476999994,
57+
name: "negative unix timestamp 5501",
58+
},
59+
{
60+
// remainder: 499
61+
input: "1814-03-09T01:02:03.000005499Z",
62+
expected: -5863791476999995,
63+
name: "negative unix timestamp 5499",
64+
},
65+
{
66+
// remainder: 500
67+
input: "1856-08-27T01:02:03.000004500Z",
68+
expected: -4523554676999996,
69+
name: "negative unix timestamp round down",
70+
},
71+
{
72+
// remainder: 501
73+
// rounding: +1
74+
input: "1856-08-27T01:02:03.000004501Z",
75+
expected: -4523554676999995,
76+
name: "negative unix timestamp 4501",
77+
},
78+
{
79+
// remainder: 499
80+
input: "1856-08-27T01:02:03.000004499Z",
81+
expected: -4523554676999996,
82+
name: "negative unix timestamp 4499",
83+
},
84+
{
85+
// remainder: 500
86+
input: "1969-12-31T23:59:59.999999500Z",
87+
expected: -946684800000000,
88+
name: "unix timestamp to zero",
89+
},
90+
{
91+
// remainder: 500
92+
// rounding: +1
93+
input: "1997-07-05T01:02:03.000009500Z",
94+
expected: -78620276999990,
95+
name: "negative postgres timestamp round up",
96+
},
97+
{
98+
// remainder: 500
99+
// rounding: +1
100+
input: "1997-07-05T01:02:03.000009500Z",
101+
expected: -78620276999990,
102+
name: "negative postgres timestamp 9501",
103+
},
104+
{
105+
// remainder: 499
106+
input: "1997-07-05T01:02:03.000009499Z",
107+
expected: -78620276999991,
108+
name: "negative postgres timestamp 9499",
109+
},
110+
{
111+
// remainder: 500
112+
input: "1997-07-05T01:02:03.000000500Z",
113+
expected: -78620277000000,
114+
name: "negative postgres timestamp round down",
115+
},
116+
{
117+
// remainder: 501
118+
// rounding: -1
119+
input: "1997-07-05T01:02:03.000000501Z",
120+
expected: -78620276999999,
121+
name: "negative postgres timestamp 501",
122+
},
123+
{
124+
input: "1997-07-05T01:02:03.000000499Z",
125+
expected: -78620277000000,
126+
name: "negative postgres timestamp 499",
127+
},
128+
{
129+
input: "1999-12-31T23:59:59.999999500Z",
130+
expected: 0,
131+
name: "postgres timestamp to zero",
132+
},
133+
{
134+
input: "2014-02-27T00:00:00.000001500Z",
135+
expected: 446774400000002,
136+
name: "positive timestamp round up",
137+
},
138+
{
139+
input: "2014-02-27T00:00:00.000001501Z",
140+
expected: 446774400000002,
141+
name: "positive timestamp 1501",
142+
},
143+
{
144+
input: "2014-02-27T00:00:00.000001499Z",
145+
expected: 446774400000001,
146+
name: "positive timestamp 1499",
147+
},
148+
{
149+
input: "2022-02-24T05:43:03.000002500Z",
150+
expected: 698996583000002,
151+
name: "positive timestamp round down",
152+
},
153+
{
154+
input: "2022-02-24T05:43:03.000002501Z",
155+
expected: 698996583000003,
156+
name: "positive timestamp 2501",
157+
},
158+
{
159+
input: "2022-02-24T05:43:03.000002499Z",
160+
expected: 698996583000002,
161+
name: "positive timestamp 2499",
162+
},
163+
}
164+
165+
for _, test := range cases {
166+
t.Run(test.name, func(t *testing.T) {
167+
val, err := time.Parse(
168+
"2006-01-02T15:04:05.999999999Z",
169+
test.input,
170+
)
171+
require.NoError(t, err)
172+
173+
data := make([]byte, 12)
174+
buf := buff.NewWriter(data)
175+
codec := DateTimeCodec{}
176+
err = codec.Encode(buf, val, "path-root", true)
177+
require.NoError(t, err)
178+
179+
assert.Equal(
180+
t,
181+
test.expected,
182+
int64(binary.BigEndian.Uint64(data[4:])),
183+
"intput: %s", test.input,
184+
)
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)