Skip to content

Commit 8774e55

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 913bf30 commit 8774e55

10 files changed

+980
-0
lines changed

CHANGELOG.md

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

1313
- Optional msgpack.v5 usage (#124)
1414
- TZ support for datetime (#163)
15+
- Interval support for datetime (#165)
1516

1617
### Changed
1718

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

+10
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ 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+
69+
local function call_datetime_interval(dtleft, dtright)
70+
return dtright - dtleft
71+
end
72+
rawset(_G, 'call_datetime_interval', call_datetime_interval)
73+
6474
-- Set listen only when every other thing is configured.
6575
box.cfg{
6676
listen = os.getenv("TEST_TNT_LISTEN"),

datetime/datetime.go

+98
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,104 @@ func NewDatetime(t time.Time) (*Datetime, error) {
120120
return dt, nil
121121
}
122122

123+
func intervalFromDatetime(dtime *Datetime) (ival Interval) {
124+
ival.Year = int64(dtime.time.Year())
125+
ival.Month = int64(dtime.time.Month())
126+
ival.Day = int64(dtime.time.Day())
127+
ival.Hour = int64(dtime.time.Hour())
128+
ival.Min = int64(dtime.time.Minute())
129+
ival.Sec = int64(dtime.time.Second())
130+
ival.Nsec = int64(dtime.time.Nanosecond())
131+
ival.Adjust = NoneAdjust
132+
133+
return ival
134+
}
135+
136+
func daysInMonth(year int64, month int64) int64 {
137+
if month == 12 {
138+
year++
139+
month = 1
140+
} else {
141+
month += 1
142+
}
143+
144+
// We use the fact that time.Date accepts values outside their usual
145+
// ranges - the values are normalized during the conversion.
146+
//
147+
// So we got a day (year, month - 1, last day of the month) before
148+
// (year, month, 1) because we pass (year, month, 0).
149+
return int64(time.Date(int(year), time.Month(month), 0, 0, 0, 0, 0, time.UTC).Day())
150+
}
151+
152+
// C imlementation:
153+
// https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.c#L74-L98
154+
func addMonth(ival *Interval, delta int64, adjust Adjust) {
155+
oldYear := ival.Year
156+
oldMonth := ival.Month
157+
158+
ival.Month += delta
159+
if ival.Month < 1 || ival.Month > 12 {
160+
ival.Year += ival.Month / 12
161+
ival.Month %= 12
162+
if ival.Month < 1 {
163+
ival.Year--
164+
ival.Month += 12
165+
}
166+
}
167+
if adjust == ExcessAdjust || ival.Day < 28 {
168+
return
169+
}
170+
171+
dim := daysInMonth(ival.Year, ival.Month)
172+
if ival.Day > dim || (adjust == LastAdjust && ival.Day == daysInMonth(oldYear, oldMonth)) {
173+
ival.Day = dim
174+
}
175+
}
176+
177+
func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
178+
newVal := intervalFromDatetime(dtime)
179+
180+
var direction int64
181+
if positive {
182+
direction = 1
183+
} else {
184+
direction = -1
185+
}
186+
187+
addMonth(&newVal, direction*ival.Year*12+direction*ival.Month, ival.Adjust)
188+
newVal.Day += direction * 7 * ival.Week
189+
newVal.Day += direction * ival.Day
190+
newVal.Hour += direction * ival.Hour
191+
newVal.Min += direction * ival.Min
192+
newVal.Sec += direction * ival.Sec
193+
newVal.Nsec += direction * ival.Nsec
194+
195+
tm := time.Date(int(newVal.Year), time.Month(newVal.Month),
196+
int(newVal.Day), int(newVal.Hour), int(newVal.Min),
197+
int(newVal.Sec), int(newVal.Nsec), dtime.time.Location())
198+
199+
return NewDatetime(tm)
200+
}
201+
202+
// Add creates a new Datetime as addition of the Datetime and Interval. It may
203+
// return an error if a new Datetime is out of supported range.
204+
func (dtime *Datetime) Add(ival Interval) (*Datetime, error) {
205+
return dtime.add(ival, true)
206+
}
207+
208+
// Sub creates a new Datetime as subtraction of the Datetime and Interval. It
209+
// may return an error if a new Datetime is out of supported range.
210+
func (dtime *Datetime) Sub(ival Interval) (*Datetime, error) {
211+
return dtime.add(ival, false)
212+
}
213+
214+
// Interval returns an Interval value to a next Datetime value.
215+
func (dtime *Datetime) Interval(next *Datetime) Interval {
216+
curIval := intervalFromDatetime(dtime)
217+
nextIval := intervalFromDatetime(next)
218+
return nextIval.Sub(curIval)
219+
}
220+
123221
// ToTime returns a time.Time that Datetime contains.
124222
func (dtime *Datetime) ToTime() time.Time {
125223
return dtime.time

0 commit comments

Comments
 (0)