Skip to content

Commit 65849d2

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 65849d2

10 files changed

+933
-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

+98
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,104 @@ 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+
118+
// We use the fact that time.Date accepts values outside their usual
119+
// ranges - the values are normalized during the conversion.
120+
//
121+
// So we got a day before (year, month, 1) -> (year, month - 1, last day)
122+
// because we pass (year, month, 0).
123+
return int64(time.Date(int(year), time.Month(month), 0, 0, 0, 0, 0, time.UTC).Day())
124+
}
125+
126+
// C imlementation:
127+
// https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.c#L74-L98
128+
func addMonth(ival *Interval, delta int64, adjust Adjust) {
129+
oldYear := ival.Year
130+
oldMonth := ival.Month
131+
132+
ival.Month += delta
133+
if ival.Month < 1 || ival.Month > 12 {
134+
ival.Year += ival.Month / 12
135+
ival.Month %= 12
136+
if ival.Month < 1 {
137+
ival.Year--
138+
ival.Month += 12
139+
}
140+
}
141+
if adjust == ExcessAdjust || ival.Day < 28 {
142+
return
143+
}
144+
145+
dim := daysInMonth(ival.Year, ival.Month)
146+
if ival.Day > dim || (adjust == LastAdjust && ival.Day == daysInMonth(oldYear, oldMonth)) {
147+
ival.Day = dim
148+
}
149+
}
150+
151+
func (dtime *Datetime) add(ival Interval, positive bool) (*Datetime, error) {
152+
newVal := intervalFromDatetime(dtime)
153+
154+
var direction int64
155+
if positive {
156+
direction = 1
157+
} else {
158+
direction = -1
159+
}
160+
161+
addMonth(&newVal, direction*ival.Year*12+direction*ival.Month, ival.Adjust)
162+
newVal.Day += direction * 7 * ival.Week
163+
newVal.Day += direction * ival.Day
164+
newVal.Hour += direction * ival.Hour
165+
newVal.Min += direction * ival.Min
166+
newVal.Sec += direction * ival.Sec
167+
newVal.Nsec += direction * ival.Nsec
168+
169+
tm := time.Date(int(newVal.Year), time.Month(newVal.Month),
170+
int(newVal.Day), int(newVal.Hour), int(newVal.Min),
171+
int(newVal.Sec), int(newVal.Nsec), dtime.time.Location())
172+
173+
return NewDatetime(tm)
174+
}
175+
176+
// Add creates a new Datetime as addition of the Datetime and Interval. It may
177+
// return an error if a new Datetime is out of supported range.
178+
func (dtime *Datetime) Add(ival Interval) (*Datetime, error) {
179+
return dtime.add(ival, true)
180+
}
181+
182+
// Sub creates a new Datetime as subtraction of the Datetime and Interval. It
183+
// may return an error if a new Datetime is out of supported range.
184+
func (dtime *Datetime) Sub(ival Interval) (*Datetime, error) {
185+
return dtime.add(ival, false)
186+
}
187+
188+
// Interval returns an Interval value to a next Datetime value.
189+
func (dtime *Datetime) Interval(next *Datetime) Interval {
190+
curIval := intervalFromDatetime(dtime)
191+
nextIval := intervalFromDatetime(next)
192+
return nextIval.Sub(curIval)
193+
}
194+
97195
// ToTime returns a time.Time that Datetime contains.
98196
func (dtime *Datetime) ToTime() time.Time {
99197
return dtime.time

0 commit comments

Comments
 (0)