Skip to content

datetime: add TZ support #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
### Added

- Optional msgpack.v5 usage (#124)
- TZ support for datetime (#163)

### Changed

Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ clean:
deps: clean
( cd ./queue; tarantoolctl rocks install queue 1.1.0 )

.PHONY: datetime-timezones
datetime-timezones:
(cd ./datetime; ./gen-timezones.sh)

.PHONY: format
format:
goimports -l -w .
Expand Down
72 changes: 60 additions & 12 deletions datetime/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,11 @@ type datetime struct {
// Nanoseconds, fractional part of seconds. Tarantool uses int32_t, see
// a definition in src/lib/core/datetime.h.
nsec int32
// Timezone offset in minutes from UTC (not implemented in Tarantool,
// see gh-163). Tarantool uses a int16_t type, see a structure
// definition in src/lib/core/datetime.h.
// Timezone offset in minutes from UTC. Tarantool uses a int16_t type,
// see a structure definition in src/lib/core/datetime.h.
tzOffset int16
// Olson timezone id (not implemented in Tarantool, see gh-163).
// Tarantool uses a int16_t type, see a structure definition in
// src/lib/core/datetime.h.
// Olson timezone id. Tarantool uses a int16_t type, see a structure
// definition in src/lib/core/datetime.h.
tzIndex int16
}

Expand All @@ -79,14 +77,42 @@ type Datetime struct {
time time.Time
}

const (
// NoTimezone allows to create a datetime without UTC timezone for
// Tarantool. The problem is that Golang by default creates a time value
// with UTC timezone. So it is a way to create a datetime without timezone.
NoTimezone = ""
)

var noTimezoneLoc = time.FixedZone(NoTimezone, 0)

const (
offsetMin = -12 * 60 * 60
offsetMax = 14 * 60 * 60
)

// NewDatetime returns a pointer to a new datetime.Datetime that contains a
// specified time.Time. It may returns an error if the Time value is out of
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z]
// specified time.Time. It may return an error if the Time value is out of
// supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] or
// an invalid timezone or offset value is out of supported range:
// [-12 * 60 * 60, 14 * 60 * 60].
func NewDatetime(t time.Time) (*Datetime, error) {
seconds := t.Unix()

if seconds < minSeconds || seconds > maxSeconds {
return nil, fmt.Errorf("Time %s is out of supported range.", t)
return nil, fmt.Errorf("time %s is out of supported range", t)
}

zone, offset := t.Zone()
if zone != NoTimezone {
if _, ok := timezoneToIndex[zone]; !ok {
return nil, fmt.Errorf("unknown timezone %s with offset %d",
zone, offset)
}
}
if offset < offsetMin || offset > offsetMax {
return nil, fmt.Errorf("offset must be between %d and %d hours",
offsetMin, offsetMax)
}

dt := new(Datetime)
Expand All @@ -105,8 +131,14 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) {
var dt datetime
dt.seconds = tm.Unix()
dt.nsec = int32(tm.Nanosecond())
dt.tzIndex = 0 // It is not implemented, see gh-163.
dt.tzOffset = 0 // It is not implemented, see gh-163.

zone, offset := tm.Zone()
if zone != NoTimezone {
// The zone value already checked in NewDatetime() or
// UnmarshalMsgpack() calls.
dt.tzIndex = int16(timezoneToIndex[zone])
}
dt.tzOffset = int16(offset / 60)

var bytesSize = secondsSize
if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 {
Expand Down Expand Up @@ -140,7 +172,23 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error {
dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:]))
}

tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC()
tt := time.Unix(dt.seconds, int64(dt.nsec))

loc := noTimezoneLoc
if dt.tzIndex != 0 || dt.tzOffset != 0 {
zone := NoTimezone
offset := int(dt.tzOffset) * 60

if dt.tzIndex != 0 {
if _, ok := indexToTimezone[int(dt.tzIndex)]; !ok {
return fmt.Errorf("unknown timezone index %d", dt.tzIndex)
}
zone = indexToTimezone[int(dt.tzIndex)]
}
loc = time.FixedZone(zone, offset)
}
tt = tt.In(loc)

dtp, err := NewDatetime(tt)
if dtp != nil {
*tm = *dtp
Expand Down
Loading