From: Timur Safin via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: v.shpilevoy@tarantool.org, imun@tarantool.org,
imeevma@tarantool.org, tarantool-patches@dev.tarantool.org
Subject: [Tarantool-patches] [PATCH v5 6/8] lua, datetime: time intervals support
Date: Mon, 16 Aug 2021 02:59:40 +0300 [thread overview]
Message-ID: <58bf0cbee84cccbe619c225aafa7d471a4c920ca.1629071531.git.tsafin@tarantool.org> (raw)
In-Reply-To: <cover.1629071531.git.tsafin@tarantool.org>
* created few entry points (months(N), years(N), days(N), etc.)
for easier datetime arithmetic;
* additions/subtractions of years/months use `dt_add_years()`
and `dt_add_months()` from 3rd party c-dt library;
* also there are `:add{}` and `:sub{}` methods in datetime
object to add or substract more complex intervals;
* introduced `is_datetime()` and `is_interval()` helpers for checking
of validity of passed arguments;
* human-readable stringization implemented for interval objects.
Note, that additions/subtractions completed for all _reasonable_
combinations of values of date and interval types;
Time + Interval => Time
Interval + Time => Time
Time - Time => Interval
Time - Interval => Time
Interval + Interval => Interval
Interval - Interval => Interval
Part of #5941
---
| 3 +
src/lua/datetime.lua | 349 ++++++++++++++++++++++++++++++++-
test/app-tap/datetime.test.lua | 159 ++++++++++++++-
3 files changed, 506 insertions(+), 5 deletions(-)
--git a/extra/exports b/extra/exports
index c34a5c2b5..421dda51f 100644
--- a/extra/exports
+++ b/extra/exports
@@ -157,6 +157,9 @@ datetime_to_string
datetime_unpack
decimal_from_string
decimal_unpack
+tnt_dt_add_years
+tnt_dt_add_quarters
+tnt_dt_add_months
tnt_dt_dow
tnt_dt_from_rdn
tnt_dt_from_struct_tm
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 4d946f194..5fd0565ac 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -24,6 +24,17 @@ ffi.cdef [[
int tnt_dt_rdn (dt_t dt);
+ // dt_arithmetic.h
+ typedef enum {
+ DT_EXCESS,
+ DT_LIMIT,
+ DT_SNAP
+ } dt_adjust_t;
+
+ dt_t tnt_dt_add_years (dt_t dt, int delta, dt_adjust_t adjust);
+ dt_t tnt_dt_add_quarters (dt_t dt, int delta, dt_adjust_t adjust);
+ dt_t tnt_dt_add_months (dt_t dt, int delta, dt_adjust_t adjust);
+
// dt_parse_iso.h
size_t tnt_dt_parse_iso_date (const char *str, size_t len, dt_t *dt);
size_t tnt_dt_parse_iso_time (const char *str, size_t len, int *sod, int *nsec);
@@ -50,8 +61,10 @@ ffi.cdef [[
local builtin = ffi.C
local math_modf = math.modf
+local math_floor = math.floor
local SECS_PER_DAY = 86400
+local NANOS_PER_SEC = 1000000000
-- c-dt/dt_config.h
@@ -62,8 +75,23 @@ local DT_EPOCH_1970_OFFSET = 719163
local datetime_t = ffi.typeof('struct datetime')
local interval_t = ffi.typeof('struct datetime_interval')
+ffi.cdef [[
+ struct interval_months {
+ int m;
+ };
+
+ struct interval_years {
+ int y;
+ };
+]]
+local interval_months_t = ffi.typeof('struct interval_months')
+local interval_years_t = ffi.typeof('struct interval_years')
+
local function is_interval(o)
- return type(o) == 'cdata' and ffi.istype(interval_t, o)
+ return type(o) == 'cdata' and
+ (ffi.istype(interval_t, o) or
+ ffi.istype(interval_months_t, o) or
+ ffi.istype(interval_years_t, o))
end
local function is_datetime(o)
@@ -72,7 +100,10 @@ end
local function is_date_interval(o)
return type(o) == 'cdata' and
- (ffi.istype(interval_t, o) or ffi.istype(datetime_t, o))
+ (ffi.istype(datetime_t, o) or
+ ffi.istype(interval_t, o) or
+ ffi.istype(interval_months_t, o) or
+ ffi.istype(interval_years_t, o))
end
local function interval_new()
@@ -80,6 +111,13 @@ local function interval_new()
return interval
end
+local function check_number(n, message)
+ if type(n) ~= 'number' then
+ return error(("%s: expected number, but received %s"):
+ format(message, n), 2)
+ end
+end
+
local function check_date(o, message)
if not is_datetime(o) then
return error(("%s: expected datetime, but received %s"):
@@ -87,6 +125,20 @@ local function check_date(o, message)
end
end
+local function check_date_interval(o, message)
+ if not is_datetime(o) and not is_interval(o) then
+ return error(("%s: expected datetime or interval, but received %s"):
+ format(message, o), 2)
+ end
+end
+
+local function check_interval(o, message)
+ if not is_interval(o) then
+ return error(("%s: expected interval, but received %s"):
+ format(message, o), 2)
+ end
+end
+
local function check_str(s, message)
if not type(s) == 'string' then
return error(("%s: expected string, but received %s"):
@@ -102,6 +154,77 @@ local function check_range(v, range, txt)
end
end
+local function interval_years_new(y)
+ check_number(y, "years(number)")
+ local o = ffi.new(interval_years_t)
+ o.y = y
+ return o
+end
+
+local function interval_months_new(m)
+ check_number(m, "months(number)")
+ local o = ffi.new(interval_months_t)
+ o.m = m
+ return o
+end
+
+local function interval_weeks_new(w)
+ check_number(w, "weeks(number)")
+ local o = ffi.new(interval_t)
+ o.secs = w * SECS_PER_DAY * 7
+ return o
+end
+
+local function interval_days_new(d)
+ check_number(d, "days(number)")
+ local o = ffi.new(interval_t)
+ o.secs = d * SECS_PER_DAY
+ return o
+end
+
+local function interval_hours_new(h)
+ check_number(h, "hours(number)")
+ local o = ffi.new(interval_t)
+ o.secs = h * 60 * 60
+ return o
+end
+
+local function interval_minutes_new(m)
+ check_number(m, "minutes(number)")
+ local o = ffi.new(interval_t)
+ o.secs = m * 60
+ return o
+end
+
+local function interval_seconds_new(s)
+ check_number(s, "seconds(number)")
+ local o = ffi.new(interval_t)
+ o.nsec = s % 1 * 1e9
+ o.secs = s - (s % 1)
+ return o
+end
+
+local SECS_EPOCH_OFFSET = (DT_EPOCH_1970_OFFSET * SECS_PER_DAY)
+
+local function local_rd(secs)
+ return math_floor((secs + SECS_EPOCH_OFFSET) / SECS_PER_DAY)
+end
+
+local function local_dt(secs)
+ return builtin.tnt_dt_from_rdn(local_rd(secs))
+end
+
+local function normalize_nsec(secs, nsec)
+ if nsec < 0 then
+ secs = secs - 1
+ nsec = nsec + NANOS_PER_SEC
+ elseif nsec >= NANOS_PER_SEC then
+ secs = secs + 1
+ nsec = nsec - NANOS_PER_SEC
+ end
+ return secs, nsec
+end
+
local function datetime_cmp(lhs, rhs)
if not is_date_interval(lhs) or
not is_date_interval(rhs) then
@@ -256,6 +379,10 @@ local function datetime_tostring(o)
local len = builtin.datetime_to_string(o, buff, sz)
assert(len < sz)
return ffi.string(buff)
+ elseif ffi.typeof(o) == interval_years_t then
+ return ('%+d years'):format(o.y)
+ elseif ffi.typeof(o) == interval_months_t then
+ return ('%+d months'):format(o.m)
elseif ffi.typeof(o) == interval_t then
local ts = o.timestamp
local sign = '+'
@@ -279,6 +406,126 @@ local function datetime_tostring(o)
end
end
+local function date_first(lhs, rhs)
+ if is_datetime(lhs) then
+ return lhs, rhs
+ else
+ return rhs, lhs
+ end
+end
+
+local function error_incompatible(name)
+ error(("datetime:%s() - incompatible type of arguments"):
+ format(name), 3)
+end
+
+--[[
+Matrix of subtraction operands eligibility and their result type
+
+| | datetime | interval | interval_months | interval_years |
++-----------------+-----------+----------+-----------------+----------------+
+| datetime | interval | datetime | datetime | datetime |
+| interval | | interval | | |
+| interval_months | | | interval_months | |
+| interval_years | | | | interval_years |
+]]
+local function datetime_sub(lhs, rhs)
+ check_date_interval(lhs, "operator -")
+ local d, s = lhs, rhs
+ local left_t = ffi.typeof(d)
+ local right_t = ffi.typeof(s)
+ local o
+
+ if left_t == datetime_t then
+ -- 1. left is date, right is date or generic interval
+ if right_t == datetime_t or right_t == interval_t then
+ o = right_t == datetime_t and interval_new() or datetime_new()
+ o.secs, o.nsec = normalize_nsec(lhs.secs - rhs.secs,
+ lhs.nsec - rhs.nsec)
+ return o
+ -- 2. left is date, right is interval in months
+ elseif right_t == interval_months_t then
+ local dt = local_dt(lhs.secs)
+ dt = builtin.tnt_dt_add_months(dt, -rhs.m, builtin.DT_LIMIT)
+ return datetime_new_dt(dt, lhs.secs % SECS_PER_DAY,
+ lhs.nsec, lhs.offset)
+
+ -- 3. left is date, right is interval in years
+ elseif right_t == interval_years_t then
+ local dt = local_dt(lhs.secs)
+ dt = builtin.tnt_dt_add_years(dt, -rhs.y, builtin.DT_LIMIT)
+ return datetime_new_dt(dt, lhs.secs % SECS_PER_DAY,
+ lhs.nsec, lhs.offset)
+ else
+ error_incompatible("operator -")
+ end
+ -- 4. both left and right are generic intervals
+ elseif left_t == interval_t and right_t == interval_t then
+ o = interval_new()
+ o.secs, o.nsec = normalize_nsec(lhs.secs - rhs.secs,
+ lhs.nsec - rhs.nsec)
+ return o
+ -- 5. both left and right are intervals in months
+ elseif left_t == interval_months_t and right_t == interval_months_t then
+ return interval_months_new(lhs.m - rhs.m)
+ -- 5. both left and right are intervals in years
+ elseif left_t == interval_years_t and right_t == interval_years_t then
+ return interval_years_new(lhs.y - rhs.y)
+ else
+ error_incompatible("operator -")
+ end
+end
+
+--[[
+Matrix of addition operands eligibility and their result type
+
+| | datetime | interval | interval_months | interval_years |
++-----------------+-----------+----------+-----------------+----------------+
+| datetime | datetime | datetime | datetime | datetime |
+| interval | datetime | interval | | |
+| interval_months | datetime | | interval_months | |
+| interval_years | datetime | | | interval_years |
+]]
+local function datetime_add(lhs, rhs)
+ local d, s = date_first(lhs, rhs)
+
+ check_date_interval(d, "operator +")
+ check_interval(s, "operator +")
+ local left_t = ffi.typeof(d)
+ local right_t = ffi.typeof(s)
+ local o
+
+ -- 1. left is date, right is date or interval
+ if left_t == datetime_t and right_t == interval_t then
+ o = datetime_new()
+ o.secs, o.nsec = normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
+ return o
+ -- 2. left is date, right is interval in months
+ elseif left_t == datetime_t and right_t == interval_months_t then
+ local dt = builtin.tnt_dt_add_months(local_dt(d.secs), s.m, builtin.DT_LIMIT)
+ local secs = d.secs % SECS_PER_DAY
+ return datetime_new_dt(dt, secs, d.nsec, d.offset or 0)
+
+ -- 3. left is date, right is interval in years
+ elseif left_t == datetime_t and right_t == interval_years_t then
+ local dt = builtin.tnt_dt_add_years(local_dt(d.secs), s.y, builtin.DT_LIMIT)
+ local secs = d.secs % SECS_PER_DAY
+ return datetime_new_dt(dt, secs, d.nsec, d.offset or 0)
+ -- 4. both left and right are generic intervals
+ elseif left_t == interval_t and right_t == interval_t then
+ o = interval_new()
+ o.secs, o.nsec = normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
+ return o
+ -- 5. both left and right are intervals in months
+ elseif left_t == interval_months_t and right_t == interval_months_t then
+ return interval_months_new(d.m + s.m)
+ -- 6. both left and right are intervals in years
+ elseif left_t == interval_years_t and right_t == interval_years_t then
+ return interval_years_new(d.y + s.y)
+ else
+ error_incompatible("operator +")
+ end
+end
--[[
Basic Extended
@@ -400,6 +647,75 @@ local function local_now()
return d
end
+-- addition or subtraction from date/time of a given interval
+-- described via table direction should be +1 or -1
+local function interval_increment(self, o, direction)
+ assert(direction == -1 or direction == 1)
+ local title = direction > 0 and "datetime.add" or "datetime.sub"
+ check_date(self, title)
+ if type(o) ~= 'table' then
+ error(('%s - object expected'):format(title), 2)
+ end
+
+ local ym_updated = false
+ local dhms_updated = false
+
+ local secs, nsec
+ secs, nsec = self.secs, self.nsec
+ -- operations with intervals should be done using human dates
+ -- not UTC dates, thus we normalize to UTC
+ local dt = local_dt(secs)
+
+ for key, value in pairs(o) do
+ if key == 'years' then
+ check_range(value, {0, 9999}, key)
+ dt = builtin.tnt_dt_add_years(dt, direction * value, builtin.DT_LIMIT)
+ ym_updated = true
+ elseif key == 'months' then
+ check_range(value, {0, 12}, key)
+ dt = builtin.tnt_dt_add_months(dt, direction * value, builtin.DT_LIMIT)
+ ym_updated = true
+ elseif key == 'weeks' then
+ check_range(value, {0, 52}, key)
+ secs = secs + direction * 7 * value * SECS_PER_DAY
+ dhms_updated = true
+ elseif key == 'days' then
+ check_range(value, {0, 31}, key)
+ secs = secs + direction * value * SECS_PER_DAY
+ dhms_updated = true
+ elseif key == 'hours' then
+ check_range(value, {0, 23}, key)
+ secs = secs + direction * 60 * 60 * value
+ dhms_updated = true
+ elseif key == 'minutes' then
+ check_range(value, {0, 59}, key)
+ secs = secs + direction * 60 * value
+ elseif key == 'seconds' then
+ check_range(value, {0, 60}, key)
+ local s, frac = math.modf(value)
+ secs = secs + direction * s
+ nsec = nsec + direction * frac * 1e9
+ dhms_updated = true
+ end
+ end
+
+ secs, nsec = normalize_nsec(secs, nsec)
+
+ -- .days, .hours, .minutes, .seconds
+ if dhms_updated then
+ self.secs = secs
+ self.nsec = nsec
+ end
+
+ -- .years, .months updated
+ if ym_updated then
+ self.secs = (builtin.tnt_dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY +
+ secs % SECS_PER_DAY
+ end
+
+ return self
+end
+
-- Change the time-zone to the provided target_offset
-- Time `.secs`/`.nsec` are always UTC normalized, we need only to
-- reattribute object with different `.offset`
@@ -437,6 +753,14 @@ local function datetime_index(self, key)
return (self.secs + self.nsec / 1e9) / (60 * 60)
elseif key == 'd' or key == 'days' then
return (self.secs + self.nsec / 1e9) / (24 * 60 * 60)
+ elseif key == 'add' then
+ return function(self, obj)
+ return interval_increment(self, obj, 1)
+ end
+ elseif key == 'sub' then
+ return function(self, obj)
+ return interval_increment(self, obj, -1)
+ end
elseif key == 'to_utc' then
return function(self)
return datetime_to_tz(self, 0)
@@ -493,6 +817,8 @@ local datetime_mt = {
__eq = datetime_eq,
__lt = datetime_lt,
__le = datetime_le,
+ __sub = datetime_sub,
+ __add = datetime_add,
__index = datetime_index,
__newindex = datetime_newindex,
}
@@ -503,16 +829,35 @@ local interval_mt = {
__eq = datetime_eq,
__lt = datetime_lt,
__le = datetime_le,
+ __sub = datetime_sub,
+ __add = datetime_add,
+ __index = datetime_index,
+}
+
+local interval_tiny_mt = {
+ __tostring = datetime_tostring,
+ __serialize = interval_serialize,
+ __sub = datetime_sub,
+ __add = datetime_add,
__index = datetime_index,
}
ffi.metatype(interval_t, interval_mt)
ffi.metatype(datetime_t, datetime_mt)
+ffi.metatype(interval_years_t, interval_tiny_mt)
+ffi.metatype(interval_months_t, interval_tiny_mt)
return setmetatable(
{
new = datetime_new,
new_raw = datetime_new_obj,
+ years = interval_years_new,
+ months = interval_months_new,
+ weeks = interval_weeks_new,
+ days = interval_days_new,
+ hours = interval_hours_new,
+ minutes = interval_minutes_new,
+ seconds = interval_seconds_new,
interval = interval_new,
parse = parse,
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 244ec2575..a2d7fac57 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -6,7 +6,7 @@ local date = require('datetime')
local ffi = require('ffi')
-test:plan(7)
+test:plan(10)
test:test("Simple tests for parser", function(test)
test:plan(2)
@@ -207,11 +207,164 @@ test:test("Parse tiny date into seconds and other parts", function(test)
test:ok(tiny.hours == 0.00848, "hours")
end)
-test:test("Stringization of date", function(test)
- test:plan(1)
+test:test("Stringization of dates and intervals", function(test)
+ test:plan(13)
local str = '19700101Z'
local dt = date(str)
test:ok(tostring(dt) == '1970-01-01T00:00Z', ('tostring(%s)'):format(str))
+ test:ok(tostring(date.seconds(12)) == '+12 secs', '+12 seconds')
+ test:ok(tostring(date.seconds(-12)) == '-12 secs', '-12 seconds')
+ test:ok(tostring(date.minutes(12)) == '+12 minutes, 0 seconds', '+12 minutes')
+ test:ok(tostring(date.minutes(-12)) == '-12 minutes, 0 seconds', '-12 minutes')
+ test:ok(tostring(date.hours(12)) == '+12 hours, 0 minutes, 0 seconds',
+ '+12 hours')
+ test:ok(tostring(date.hours(-12)) == '-12 hours, 0 minutes, 0 seconds',
+ '-12 hours')
+ test:ok(tostring(date.days(12)) == '+12 days, 0 hours, 0 minutes, 0 seconds',
+ '+12 days')
+ test:ok(tostring(date.days(-12)) == '-12 days, 0 hours, 0 minutes, 0 seconds',
+ '-12 days')
+ test:ok(tostring(date.months(5)) == '+5 months', '+5 months')
+ test:ok(tostring(date.months(-5)) == '-5 months', '-5 months')
+ test:ok(tostring(date.years(4)) == '+4 years', '+4 years')
+ test:ok(tostring(date.years(-4)) == '-4 years', '-4 years')
+end)
+
+test:test("Time interval operations", function(test)
+ test:plan(12)
+
+ -- check arithmetic with leap dates
+ local T = date('1972-02-29')
+ local M = date.months(2)
+ local Y = date.years(1)
+ test:ok(tostring(T + M) == '1972-04-29T00:00Z', ('T(%s) + M(%s'):format(T, M))
+ test:ok(tostring(T + Y) == '1973-03-01T00:00Z', ('T(%s) + Y(%s'):format(T, Y))
+ test:ok(tostring(T + M + Y) == '1973-04-30T00:00Z',
+ ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+ test:ok(tostring(T + Y + M) == '1973-05-01T00:00Z',
+ ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+ test:ok(tostring(T:add{years = 1, months = 2}) == '1973-04-30T00:00Z',
+ ('T:add{years=1,months=2}(%s)'):format(T))
+
+ -- check average, not leap dates
+ T = date('1970-01-08')
+ test:ok(tostring(T + M) == '1970-03-08T00:00Z', ('T(%s) + M(%s'):format(T, M))
+ test:ok(tostring(T + Y) == '1971-01-08T00:00Z', ('T(%s) + Y(%s'):format(T, Y))
+ test:ok(tostring(T + M + Y) == '1971-03-08T00:00Z',
+ ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+ test:ok(tostring(T + Y + M) == '1971-03-08T00:00Z',
+ ('T(%s) + Y(%s) + M(%s'):format(T, Y, M))
+ test:ok(tostring(T:add{years = 1, months = 2}) == '1971-03-08T00:00Z',
+ ('T:add{years=1,months=2}(%s)'):format(T))
+
+
+ -- subtraction of 2 dates
+ local T2 = date('19700103')
+ local T1 = date('1970-01-01')
+ test:ok(tostring(T2 - T1) == '+2 days, 0 hours, 0 minutes, 0 seconds',
+ ('T2(%s) - T1(%s'):format(T2, T1))
+ test:ok(tostring(T1 - T2) == '-2 days, 0 hours, 0 minutes, 0 seconds',
+ ('T2(%s) - T1(%s'):format(T2, T1))
+end)
+
+local function catchadd(A, B)
+ return pcall(function() return A + B end)
+end
+
+--[[
+Matrix of addition operands eligibility and their result type
+
+| | datetime | interval | interval_months | interval_years |
++-----------------+-----------+----------+-----------------+----------------+
+| datetime | datetime | datetime | datetime | datetime |
+| interval | datetime | interval | | |
+| interval_months | datetime | | interval_months | |
+| interval_years | datetime | | | interval_years |
+]]
+
+test:test("Matrix of allowed time and interval additions", function(test)
+ test:plan(20)
+
+ -- check arithmetic with leap dates
+ local T1970 = date('1970-01-01')
+ local T2000 = date('2000-01-01')
+ local I1 = date.days(1)
+ local M2 = date.months(2)
+ local M10 = date.months(10)
+ local Y1 = date.years(1)
+ local Y5 = date.years(5)
+
+ test:ok(catchadd(T1970, I1) == true, "status: T + I")
+ test:ok(catchadd(T1970, M2) == true, "status: T + M")
+ test:ok(catchadd(T1970, Y1) == true, "status: T + Y")
+ test:ok(catchadd(T1970, T2000) == false, "status: T + T")
+ test:ok(catchadd(I1, T1970) == true, "status: I + T")
+ test:ok(catchadd(M2, T1970) == true, "status: M + T")
+ test:ok(catchadd(Y1, T1970) == true, "status: Y + T")
+ test:ok(catchadd(I1, Y1) == false, "status: I + Y")
+ test:ok(catchadd(M2, Y1) == false, "status: M + Y")
+ test:ok(catchadd(I1, Y1) == false, "status: I + Y")
+ test:ok(catchadd(Y5, M10) == false, "status: Y + M")
+ test:ok(catchadd(Y5, I1) == false, "status: Y + I")
+ test:ok(catchadd(Y5, Y1) == true, "status: Y + Y")
+
+ test:ok(tostring(T1970 + I1) == "1970-01-02T00:00Z", "value: T + I")
+ test:ok(tostring(T1970 + M2) == "1970-03-01T00:00Z", "value: T + M")
+ test:ok(tostring(T1970 + Y1) == "1971-01-01T00:00Z", "value: T + Y")
+ test:ok(tostring(I1 + T1970) == "1970-01-02T00:00Z", "value: I + T")
+ test:ok(tostring(M2 + T1970) == "1970-03-01T00:00Z", "value: M + T")
+ test:ok(tostring(Y1 + T1970) == "1971-01-01T00:00Z", "value: Y + T")
+ test:ok(tostring(Y5 + Y1) == "+6 years", "Y + Y")
+
+end)
+
+local function catchsub_status(A, B)
+ return pcall(function() return A - B end)
+end
+
+--[[
+Matrix of subtraction operands eligibility and their result type
+
+| | datetime | interval | interval_months | interval_years |
++-----------------+-----------+----------+-----------------+----------------+
+| datetime | interval | datetime | datetime | datetime |
+| interval | | interval | | |
+| interval_months | | | interval_months | |
+| interval_years | | | | interval_years |
+]]
+test:test("Matrix of allowed time and interval subtractions", function(test)
+ test:plan(18)
+
+ -- check arithmetic with leap dates
+ local T1970 = date('1970-01-01')
+ local T2000 = date('2000-01-01')
+ local I1 = date.days(1)
+ local M2 = date.months(2)
+ local M10 = date.months(10)
+ local Y1 = date.years(1)
+ local Y5 = date.years(5)
+
+ test:ok(catchsub_status(T1970, I1) == true, "status: T - I")
+ test:ok(catchsub_status(T1970, M2) == true, "status: T - M")
+ test:ok(catchsub_status(T1970, Y1) == true, "status: T - Y")
+ test:ok(catchsub_status(T1970, T2000) == true, "status: T - T")
+ test:ok(catchsub_status(I1, T1970) == false, "status: I + T")
+ test:ok(catchsub_status(M2, T1970) == false, "status: M + T")
+ test:ok(catchsub_status(Y1, T1970) == false, "status: Y + T")
+ test:ok(catchsub_status(I1, Y1) == false, "status: I - Y")
+ test:ok(catchsub_status(M2, Y1) == false, "status: M - Y")
+ test:ok(catchsub_status(I1, Y1) == false, "status: I - Y")
+ test:ok(catchsub_status(Y5, M10) == false, "status: Y - M")
+ test:ok(catchsub_status(Y5, I1) == false, "status: Y - I")
+ test:ok(catchsub_status(Y5, Y1) == true, "status: Y - Y")
+
+ test:ok(tostring(T1970 - I1) == "1969-12-31T00:00Z", "value: T - I")
+ test:ok(tostring(T1970 - M2) == "1969-11-01T00:00Z", "value: T - M")
+ test:ok(tostring(T1970 - Y1) == "1969-01-01T00:00Z", "value: T - Y")
+ test:ok(tostring(T1970 - T2000) == "-10957 days, 0 hours, 0 minutes, 0 seconds",
+ "value: T - T")
+ test:ok(tostring(Y5 - Y1) == "+4 years", "value: Y - Y")
+
end)
os.exit(test:check() and 0 or 1)
--
2.29.2
next prev parent reply other threads:[~2021-08-16 0:02 UTC|newest]
Thread overview: 50+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-08-15 23:59 [Tarantool-patches] [PATCH v5 0/8] Initial datetime implementation Timur Safin via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 1/8] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
2021-08-17 12:15 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:24 ` Safin Timur via Tarantool-patches
2021-08-18 8:56 ` Serge Petrenko via Tarantool-patches
2021-08-17 15:50 ` Vladimir Davydov via Tarantool-patches
2021-08-18 10:04 ` Safin Timur via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 2/8] lua: built-in module datetime Timur Safin via Tarantool-patches
2021-08-17 12:15 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:30 ` Safin Timur via Tarantool-patches
2021-08-18 8:56 ` Serge Petrenko via Tarantool-patches
2021-08-17 16:52 ` Vladimir Davydov via Tarantool-patches
2021-08-17 19:16 ` Vladimir Davydov via Tarantool-patches
2021-08-18 13:38 ` Safin Timur via Tarantool-patches
2021-08-18 10:03 ` Safin Timur via Tarantool-patches
2021-08-18 10:06 ` Safin Timur via Tarantool-patches
2021-08-18 11:45 ` Vladimir Davydov via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 3/8] lua, datetime: display datetime Timur Safin via Tarantool-patches
2021-08-17 12:15 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:32 ` Safin Timur via Tarantool-patches
2021-08-17 17:06 ` Vladimir Davydov via Tarantool-patches
2021-08-18 14:10 ` Safin Timur via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 4/8] box, datetime: messagepack support for datetime Timur Safin via Tarantool-patches
2021-08-16 0:20 ` Safin Timur via Tarantool-patches
2021-08-17 12:15 ` Serge Petrenko via Tarantool-patches
2021-08-17 12:16 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:42 ` Safin Timur via Tarantool-patches
2021-08-18 9:01 ` Serge Petrenko via Tarantool-patches
2021-08-17 18:36 ` Vladimir Davydov via Tarantool-patches
2021-08-18 14:27 ` Safin Timur via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 5/8] box, datetime: datetime comparison for indices Timur Safin via Tarantool-patches
2021-08-17 12:16 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:43 ` Safin Timur via Tarantool-patches
2021-08-18 9:03 ` Serge Petrenko via Tarantool-patches
2021-08-17 19:05 ` Vladimir Davydov via Tarantool-patches
2021-08-18 17:18 ` Safin Timur via Tarantool-patches
2021-08-15 23:59 ` Timur Safin via Tarantool-patches [this message]
2021-08-17 12:16 ` [Tarantool-patches] [PATCH v5 6/8] lua, datetime: time intervals support Serge Petrenko via Tarantool-patches
2021-08-17 23:44 ` Safin Timur via Tarantool-patches
2021-08-17 18:52 ` Vladimir Davydov via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 7/8] datetime: perf test for datetime parser Timur Safin via Tarantool-patches
2021-08-17 19:13 ` Vladimir Davydov via Tarantool-patches
2021-08-15 23:59 ` [Tarantool-patches] [PATCH v5 8/8] datetime: changelog for datetime module Timur Safin via Tarantool-patches
2021-08-17 12:16 ` Serge Petrenko via Tarantool-patches
2021-08-17 23:44 ` Safin Timur via Tarantool-patches
2021-08-18 9:04 ` Serge Petrenko via Tarantool-patches
2021-08-17 12:15 ` [Tarantool-patches] [PATCH v5 0/8] Initial datetime implementation Serge Petrenko via Tarantool-patches
[not found] ` <20210818082222.mofgheciutpipelz@esperanza>
2021-08-18 8:25 ` Vladimir Davydov via Tarantool-patches
2021-08-18 13:24 ` Safin Timur via Tarantool-patches
2021-08-18 14:22 ` Vladimir Davydov via Tarantool-patches
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=58bf0cbee84cccbe619c225aafa7d471a4c920ca.1629071531.git.tsafin@tarantool.org \
--to=tarantool-patches@dev.tarantool.org \
--cc=imeevma@tarantool.org \
--cc=imun@tarantool.org \
--cc=tsafin@tarantool.org \
--cc=v.shpilevoy@tarantool.org \
--subject='Re: [Tarantool-patches] [PATCH v5 6/8] lua, datetime: time intervals support' \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox