Skip to content

Commit 2c6e845

Browse files
committed
datetime: add interval support
The patch adds interval [1] support for datetime. Except encoding/decoding interval values from MessagePack, it adds a several functions for addition and substraction Interval and Datetime types in GoLang. Reproducing, thus, arithmetic operations from the Lua implementation [2]. 1. https://github.com/tarantool/tarantool/wiki/Datetime-Internals#interval-arithmetic 2. https://github.com/tarantool/tarantool/wiki/Datetime-Internals#arithmetic-operations Closes #165
1 parent f5cbb11 commit 2c6e845

10 files changed

+930
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1111
### Added
1212

1313
- Optional msgpack.v5 usage (#124)
14+
- Interval support for datetime (#165)
1415

1516
### Changed
1617

datetime/adjust.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package datetime
2+
3+
// An Adjust is used as a parameter for date adjustions, see:
4+
// https://github.com/tarantool/tarantool/wiki/Datetime-Internals#date-adjustions-and-leap-years
5+
type Adjust int
6+
7+
const (
8+
NoneAdjust Adjust = 0 // adjust = "none" in Tarantool
9+
ExcessAdjust Adjust = 1 // adjust = "excess" in Tarantool
10+
LastAdjust Adjust = 2 // adjust = "last" in Tarantool
11+
)
12+
13+
// We need the mappings to make NoneAdjust as a default value instead of
14+
// dtExcess.
15+
const (
16+
dtExcess = 0 // DT_EXCESS from dt-c/dt_arithmetic.h
17+
dtLimit = 1 // DT_LIMIT
18+
dtSnap = 2 // DT_SNAP
19+
)
20+
21+
var adjustToDt = map[Adjust]int64{
22+
NoneAdjust: dtLimit,
23+
ExcessAdjust: dtExcess,
24+
LastAdjust: dtSnap,
25+
}
26+
27+
var dtToAdjust = map[int64]Adjust{
28+
dtExcess: ExcessAdjust,
29+
dtLimit: NoneAdjust,
30+
dtSnap: LastAdjust,
31+
}

datetime/config.lua

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ local function call_datetime_testdata()
6161
end
6262
rawset(_G, 'call_datetime_testdata', call_datetime_testdata)
6363

64+
local function call_interval_testdata(interval)
65+
return interval
66+
end
67+
rawset(_G, 'call_interval_testdata', call_interval_testdata)
68+
6469
-- Set listen only when every other thing is configured.
6570
box.cfg{
6671
listen = os.getenv("TEST_TNT_LISTEN"),

datetime/datetime.go

+95
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,101 @@ func NewDatetime(t time.Time) (*Datetime, error) {
9494
return dt, nil
9595
}
9696

97+
func intervalFromDatetime(dtime *Datetime) (ival Interval) {
98+
ival.Year = int64(dtime.time.Year())
99+
ival.Month = int64(dtime.time.Month())
100+
ival.Day = int64(dtime.time.Day())
101+
ival.Hour = int64(dtime.time.Hour())
102+
ival.Min = int64(dtime.time.Minute())
103+
ival.Sec = int64(dtime.time.Second())
104+
ival.Nsec = int64(dtime.time.Nanosecond())
105+
ival.Adjust = NoneAdjust
106+
107+
return ival
108+
}
109+
110+
func daysInMonth(year int64, month int64) int64 {
111+
if month == 12 {
112+
year++
113+
month = 1
114+
} else {
115+
month += 1
116+
}
117+
return int64(time.Date(int(year), time.Month(month), 0, 0, 0, 0, 0, time.UTC).Day())
118+
}
119+
120+
// C imlementation:
121+
// https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.c#L74-L98
122+
func addMonth(ival *Interval, delta int64, adjust Adjust) {
123+
oldYear := ival.Year
124+
oldMonth := ival.Month
125+
126+
ival.Month += delta
127+
if ival.Month < 1 || ival.Month > 12 {
128+
ival.Year += ival.Month / 12
129+
ival.Month %= 12
130+
if ival.Month < 1 {
131+
ival.Year--
132+
ival.Month += 12
133+
}
134+
}
135+
if adjust == ExcessAdjust || ival.Day < 28 {
136+
return
137+
}
138+
139+
dim := daysInMonth(ival.Year, ival.Month)
140+
if ival.Day > dim ||
141+
(adjust == LastAdjust && ival.Day == daysInMonth(oldYear, oldMonth)) {
142+
ival.Day = dim
143+
}
144+
}
145+
146+
func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
147+
newVal := intervalFromDatetime(dtime)
148+
149+
var direction int64
150+
if positive {
151+
direction = 1
152+
} else {
153+
direction = -1
154+
}
155+
156+
addMonth(&newVal, direction*ival.Year*12+direction*ival.Month, ival.Adjust)
157+
newVal.Day += direction * 7 * ival.Week
158+
newVal.Day += direction * ival.Day
159+
newVal.Hour += direction * ival.Hour
160+
newVal.Min += direction * ival.Min
161+
newVal.Sec += direction * ival.Sec
162+
newVal.Nsec += direction * ival.Nsec
163+
164+
tm := time.Date(int(newVal.Year), time.Month(newVal.Month),
165+
int(newVal.Day), int(newVal.Hour), int(newVal.Min),
166+
int(newVal.Sec), int(newVal.Nsec), dtime.time.Location())
167+
168+
return NewDatetime(tm)
169+
}
170+
171+
// Add creates a new Datetime as addition of the Datetime and Interval. It may
172+
// return an error if a new Datetime is out of supported range,
173+
// see [NewDatetime].
174+
func (dtime *Datetime) Add(ival Interval) (*Datetime, error) {
175+
return dtime.add(ival, true)
176+
}
177+
178+
// Add creates a new Datetime as subtraction of the Datetime and Interval. It
179+
// may return an error if a new Datetime is out of supported range, see
180+
// [NewDatetime].
181+
func (dtime *Datetime) Sub(ival Interval) (*Datetime, error) {
182+
return dtime.add(ival, false)
183+
}
184+
185+
// Interval returns an Interval between a next Datetime value.
186+
func (dtime *Datetime) Interval(next *Datetime) Interval {
187+
curIval := intervalFromDatetime(dtime)
188+
nextIval := intervalFromDatetime(next)
189+
return nextIval.Sub(curIval)
190+
}
191+
97192
// ToTime returns a time.Time that Datetime contains.
98193
func (dtime *Datetime) ToTime() time.Time {
99194
return dtime.time

0 commit comments

Comments
 (0)