Skip to content

Commit e2d9e8f

Browse files
committed
datetime: add TZ support
The patch adds timezone support [1] for datetime. For the purpose of compatibility we got constants for timezones from Tarantool [2]. The patch adds a script to generate the data according to the instructions [3]. 1. https://github.com/tarantool/tarantool/wiki/Datetime-Internals#timezone-support 2. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/timezones.h 3. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/gen-zone-abbrevs.pl#L35-L39 Closes #163
1 parent 609268f commit e2d9e8f

8 files changed

+1803
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1717
- Support datetime type in msgpack (#118)
1818
- Prepared SQL statements (#117)
1919
- Context support for request objects (#48)
20+
- TZ support for datetime (#163)
2021

2122
### Changed
2223

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ clean:
2424
deps: clean
2525
( cd ./queue; tarantoolctl rocks install queue 1.1.0 )
2626

27+
.PHONY: datetime-timezones
28+
datetime-timezones:
29+
(cd ./datetime; ./gen-timezones.sh)
30+
2731
.PHONY: format
2832
format:
2933
goimports -l -w .

datetime/datetime.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package datetime
1212
import (
1313
"encoding/binary"
1414
"fmt"
15+
"math"
1516
"time"
1617

1718
"gopkg.in/vmihailenco/msgpack.v2"
@@ -49,13 +50,11 @@ type datetime struct {
4950
// Nanoseconds, fractional part of seconds. Tarantool uses int32_t, see
5051
// a definition in src/lib/core/datetime.h.
5152
nsec int32
52-
// Timezone offset in minutes from UTC (not implemented in Tarantool,
53-
// see gh-163). Tarantool uses a int16_t type, see a structure
54-
// definition in src/lib/core/datetime.h.
53+
// Timezone offset in minutes from UTC. Tarantool uses a int16_t type,
54+
// see a structure definition in src/lib/core/datetime.h.
5555
tzOffset int16
56-
// Olson timezone id (not implemented in Tarantool, see gh-163).
57-
// Tarantool uses a int16_t type, see a structure definition in
58-
// src/lib/core/datetime.h.
56+
// Olson timezone id. Tarantool uses a int16_t type, see a structure
57+
// definition in src/lib/core/datetime.h.
5958
tzIndex int16
6059
}
6160

@@ -81,6 +80,16 @@ type Datetime struct {
8180
time time.Time
8281
}
8382

83+
const (
84+
noTimezoneZone = ""
85+
noTimezoneOffset = 0
86+
)
87+
88+
// NoTimezone allows to create a datetime without UTC timezone for
89+
// Tarantool. The problem is that Golang by default creates a time value with
90+
// UTC timezone. So it is a way to create a datetime without timezone.
91+
var NoTimezone = time.FixedZone(noTimezoneZone, noTimezoneOffset)
92+
8493
// NewDatetime returns a pointer to a new datetime.Datetime that contains a
8594
// specified time.Time. It may returns an error if the Time value is out of
8695
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z]
@@ -91,6 +100,18 @@ func NewDatetime(t time.Time) (*Datetime, error) {
91100
return nil, fmt.Errorf("Time %s is out of supported range.", t)
92101
}
93102

103+
zone, offset := t.Zone()
104+
if zone != noTimezoneZone {
105+
if _, ok := timezoneToIndex[zone]; !ok {
106+
return nil, fmt.Errorf("Unknown timezone %s with offset %d",
107+
zone, offset)
108+
}
109+
}
110+
offset /= 60
111+
if offset < math.MinInt16 || offset > math.MaxInt16 {
112+
return nil, fmt.Errorf("Offset must be between %d and %d", math.MinInt16, math.MaxInt16)
113+
}
114+
94115
dt := new(Datetime)
95116
dt.time = t
96117
return dt, nil
@@ -110,8 +131,12 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
110131
var dt datetime
111132
dt.seconds = tm.Unix()
112133
dt.nsec = int32(tm.Nanosecond())
113-
dt.tzIndex = 0 // It is not implemented, see gh-163.
114-
dt.tzOffset = 0 // It is not implemented, see gh-163.
134+
135+
zone, offset := tm.Zone()
136+
if zone != noTimezoneZone {
137+
dt.tzIndex = int16(timezoneToIndex[zone])
138+
}
139+
dt.tzOffset = int16(offset / 60)
115140

116141
var bytesSize = secondsSize
117142
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 {
@@ -132,7 +157,7 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
132157
func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
133158
l := len(b)
134159
if l != maxSize && l != secondsSize {
135-
return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
160+
return fmt.Errorf("Invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize)
136161
}
137162

138163
var dt datetime
@@ -145,7 +170,23 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
145170
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
146171
}
147172

148-
tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
173+
tt := time.Unix(dt.seconds, int64(dt.nsec))
174+
175+
loc := NoTimezone
176+
if dt.tzIndex != 0 || dt.tzOffset != 0 {
177+
zone := noTimezoneZone
178+
offset := int(dt.tzOffset) * 60
179+
180+
if dt.tzIndex != 0 {
181+
if _, ok := indexToTimezone[int(dt.tzIndex)]; !ok {
182+
return fmt.Errorf("Unknown timezone index %d", dt.tzIndex)
183+
}
184+
zone = indexToTimezone[int(dt.tzIndex)]
185+
}
186+
loc = time.FixedZone(zone, offset)
187+
}
188+
tt = tt.In(loc)
189+
149190
dtp, err := NewDatetime(tt)
150191
if dtp != nil {
151192
*tm = *dtp

0 commit comments

Comments
 (0)