Skip to content

Commit 743e263

Browse files
Introduce timeTruncate parameter for time.Time arguments (#1541)
Co-authored-by: Inada Naoki <[email protected]>
1 parent c48c0e7 commit 743e263

File tree

8 files changed

+98
-26
lines changed

8 files changed

+98
-26
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Oliver Bone <owbone at github.com>
8686
Olivier Mengué <dolmen at cpan.org>
8787
oscarzhao <oscarzhaosl at gmail.com>
8888
Paul Bonser <misterpib at gmail.com>
89+
Paulius Lozys <pauliuslozys at gmail.com>
8990
Peter Schultz <peter.schultz at classmarkets.com>
9091
Phil Porada <philporada at gmail.com>
9192
Rebecca Chin <rchin at pivotal.io>

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,15 @@ Note that this sets the location for time.Time values but does not change MySQL'
285285

286286
Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
287287

288+
##### `timeTruncate`
289+
290+
```
291+
Type: duration
292+
Default: 0
293+
```
294+
295+
[Truncate time values](https://pkg.go.dev/time#Duration.Truncate) to the specified duration. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
296+
288297
##### `maxAllowedPacket`
289298
```
290299
Type: decimal number

connection.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
251251
buf = append(buf, "'0000-00-00'"...)
252252
} else {
253253
buf = append(buf, '\'')
254-
buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
254+
buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
255255
if err != nil {
256256
return "", err
257257
}

dsn.go

+12
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Config struct {
4848
pubKey *rsa.PublicKey // Server public key
4949
TLSConfig string // TLS configuration name
5050
TLS *tls.Config // TLS configuration, its priority is higher than TLSConfig
51+
TimeTruncate time.Duration // Truncate time.Time values to the specified duration
5152
Timeout time.Duration // Dial timeout
5253
ReadTimeout time.Duration // I/O read timeout
5354
WriteTimeout time.Duration // I/O write timeout
@@ -262,6 +263,10 @@ func (cfg *Config) FormatDSN() string {
262263
writeDSNParam(&buf, &hasParam, "parseTime", "true")
263264
}
264265

266+
if cfg.TimeTruncate > 0 {
267+
writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.TimeTruncate.String())
268+
}
269+
265270
if cfg.ReadTimeout > 0 {
266271
writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
267272
}
@@ -502,6 +507,13 @@ func parseDSNParams(cfg *Config, params string) (err error) {
502507
return errors.New("invalid bool value: " + value)
503508
}
504509

510+
// time.Time truncation
511+
case "timeTruncate":
512+
cfg.TimeTruncate, err = time.ParseDuration(value)
513+
if err != nil {
514+
return
515+
}
516+
505517
// I/O read Timeout
506518
case "readTimeout":
507519
cfg.ReadTimeout, err = time.ParseDuration(value)

dsn_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ var testDSNs = []struct {
7474
}, {
7575
"tcp(de:ad:be:ef::ca:fe)/dbname",
7676
&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
77+
}, {
78+
"user:password@/dbname?loc=UTC&timeout=30s&parseTime=true&timeTruncate=1h",
79+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TimeTruncate: time.Hour},
7780
},
7881
}
7982

packets.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
11721172
if v.IsZero() {
11731173
b = append(b, "0000-00-00"...)
11741174
} else {
1175-
b, err = appendDateTime(b, v.In(mc.cfg.Loc))
1175+
b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
11761176
if err != nil {
11771177
return err
11781178
}

utils.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,11 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
265265
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
266266
}
267267

268-
func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
268+
func appendDateTime(buf []byte, t time.Time, timeTruncate time.Duration) ([]byte, error) {
269+
if timeTruncate > 0 {
270+
t = t.Truncate(timeTruncate)
271+
}
272+
269273
year, month, day := t.Date()
270274
hour, min, sec := t.Clock()
271275
nsec := t.Nanosecond()

utils_test.go

+66-23
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,10 @@ func TestIsolationLevelMapping(t *testing.T) {
237237

238238
func TestAppendDateTime(t *testing.T) {
239239
tests := []struct {
240-
t time.Time
241-
str string
240+
t time.Time
241+
str string
242+
timeTruncate time.Duration
243+
expectedErr bool
242244
}{
243245
{
244246
t: time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
@@ -276,34 +278,75 @@ func TestAppendDateTime(t *testing.T) {
276278
t: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
277279
str: "0001-01-01",
278280
},
281+
// Truncated time
282+
{
283+
t: time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
284+
str: "1234-05-06",
285+
timeTruncate: time.Second,
286+
},
287+
{
288+
t: time.Date(4567, 12, 31, 12, 0, 0, 0, time.UTC),
289+
str: "4567-12-31 12:00:00",
290+
timeTruncate: time.Minute,
291+
},
292+
{
293+
t: time.Date(2020, 5, 30, 12, 34, 0, 0, time.UTC),
294+
str: "2020-05-30 12:34:00",
295+
timeTruncate: 0,
296+
},
297+
{
298+
t: time.Date(2020, 5, 30, 12, 34, 56, 0, time.UTC),
299+
str: "2020-05-30 12:34:56",
300+
timeTruncate: time.Second,
301+
},
302+
{
303+
t: time.Date(2020, 5, 30, 22, 33, 44, 123000000, time.UTC),
304+
str: "2020-05-30 22:33:44",
305+
timeTruncate: time.Second,
306+
},
307+
{
308+
t: time.Date(2020, 5, 30, 22, 33, 44, 123456000, time.UTC),
309+
str: "2020-05-30 22:33:44.123",
310+
timeTruncate: time.Millisecond,
311+
},
312+
{
313+
t: time.Date(2020, 5, 30, 22, 33, 44, 123456789, time.UTC),
314+
str: "2020-05-30 22:33:44",
315+
timeTruncate: time.Second,
316+
},
317+
{
318+
t: time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC),
319+
str: "9999-12-31 23:59:59.999999999",
320+
timeTruncate: 0,
321+
},
322+
{
323+
t: time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC),
324+
str: "0001-01-01",
325+
timeTruncate: 365 * 24 * time.Hour,
326+
},
327+
// year out of range
328+
{
329+
t: time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC),
330+
expectedErr: true,
331+
},
332+
{
333+
t: time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC),
334+
expectedErr: true,
335+
},
279336
}
280337
for _, v := range tests {
281338
buf := make([]byte, 0, 32)
282-
buf, _ = appendDateTime(buf, v.t)
339+
buf, err := appendDateTime(buf, v.t, v.timeTruncate)
340+
if err != nil {
341+
if !v.expectedErr {
342+
t.Errorf("appendDateTime(%v) returned an errror: %v", v.t, err)
343+
}
344+
continue
345+
}
283346
if str := string(buf); str != v.str {
284347
t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
285348
}
286349
}
287-
288-
// year out of range
289-
{
290-
v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
291-
buf := make([]byte, 0, 32)
292-
_, err := appendDateTime(buf, v)
293-
if err == nil {
294-
t.Error("want an error")
295-
return
296-
}
297-
}
298-
{
299-
v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
300-
buf := make([]byte, 0, 32)
301-
_, err := appendDateTime(buf, v)
302-
if err == nil {
303-
t.Error("want an error")
304-
return
305-
}
306-
}
307350
}
308351

309352
func TestParseDateTime(t *testing.T) {

0 commit comments

Comments
 (0)