[Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support

Timur Safin tsafin at tarantool.org
Thu Jul 15 11:18:18 MSK 2021


* created few entries (months(N), years(N), days(N), etc.)
  for easier date/time arithmetic;
* at the moment we require that on either side of + or -
  we should have at least one datetime. There is no (yet) support
  for duration + duration operations.
---
 src/lua/datetime.lua | 230 +++++++++++++++++++++++++++++++++----------
 1 file changed, 176 insertions(+), 54 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 081e759fd..698c55ebd 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -37,6 +37,17 @@ ffi.cdef [[
     int      dt_rdn          (dt_t dt);
     dt_dow_t dt_dow          (dt_t dt);
 
+    // dt_arithmetic.h
+    typedef enum {
+        DT_EXCESS,
+        DT_LIMIT,
+        DT_SNAP
+    } dt_adjust_t;
+
+    dt_t    dt_add_years        (dt_t dt, int delta, dt_adjust_t adjust);
+    dt_t    dt_add_quarters     (dt_t dt, int delta, dt_adjust_t adjust);
+    dt_t    dt_add_months       (dt_t dt, int delta, dt_adjust_t adjust);
+
     // dt_parse_iso.h
     size_t dt_parse_iso_date          (const char *str, size_t len, dt_t *dt);
 
@@ -158,40 +169,51 @@ local DT_EPOCH_1970_OFFSET = 719163LL
 
 local datetime_t = ffi.typeof('struct datetime_t')
 local duration_t = ffi.typeof('struct t_datetime_duration')
+ffi.cdef [[
+    struct t_duration_months {
+        int m;
+    };
+
+    struct t_duration_years {
+        int y;
+    };
+]]
+local duration_months_t = ffi.typeof('struct t_duration_months')
+local duration_years_t = ffi.typeof('struct t_duration_years')
 
 local function duration_new()
     local delta = ffi.new(duration_t)
     return delta
 end
 
-local function adjusted_secs(dt)
-    return dt.secs - dt.offset * 60
+local function duration_years_new(y)
+    local o = ffi.new(duration_years_t)
+    o.y = y
+    return o
 end
 
-local function datetime_sub(lhs, rhs)
-    local s1 = adjusted_secs(lhs)
-    local s2 = adjusted_secs(rhs)
-    local d = duration_new()
-    d.secs = s2 - s1
-    d.nsec = rhs.nsec - lhs.nsec
-    if d.nsec < 0 then
-        d.secs = d.secs - 1
-        d.nsec = d.nsec + NANOS_PER_SEC
-    end
-    return d
+local function duration_months_new(m)
+    local o = ffi.new(duration_months_t)
+    o.m = m
+    return o
 end
 
-local function datetime_add(lhs, rhs)
-    local s1 = adjusted_secs(lhs)
-    local s2 = adjusted_secs(rhs)
-    local d = duration_new()
-    d.secs = s2 + s1
-    d.nsec = rhs.nsec + lhs.nsec
-    if d.nsec >= NANOS_PER_SEC then
-        d.secs = d.secs + 1
-        d.nsec = d.nsec - NANOS_PER_SEC
-    end
-    return d
+local function duration_days_new(d)
+    local o = ffi.new(duration_t)
+    o.secs = d * SECS_PER_DAY
+    return o
+end
+
+local function duration_hours_new(h)
+    local o = ffi.new(duration_t)
+    o.secs = h * 60 * 60
+    return o
+end
+
+local function duration_minutes_new(m)
+    local o = ffi.new(duration_t)
+    o.secs = m * 60
+    return o
 end
 
 local function datetime_eq(lhs, rhs)
@@ -254,28 +276,6 @@ local datetime_index = function(self, key)
     return attributes[key] ~= nil and attributes[key](self) or nil
 end
 
-local datetime_mt = {
-    -- __tostring = datetime_tostring,
-    __serialize = datetime_serialize,
-    __eq = datetime_eq,
-    __lt = datetime_lt,
-    __le = datetime_le,
-    __sub = datetime_sub,
-    __add = datetime_add,
-    __index = datetime_index,
-}
-
-local duration_mt = {
-    -- __tostring = duration_tostring,
-    __serialize = duration_serialize,
-    __eq = datetime_eq,
-    __lt = datetime_lt,
-    __le = datetime_le,
-    __sub = datetime_sub,
-    __add = datetime_add,
-    __index = datetime_index,
-}
-
 local function datetime_new_raw(secs, nsec, offset)
     local dt_obj = ffi.new(datetime_t)
     dt_obj.secs = secs
@@ -402,6 +402,110 @@ local function datetime_new(o)
     return mk_timestamp(dt, secs, frac, offset)
 end
 
+local function datetime_tostring(o)
+    print(ffi.typeof(o))
+    assert(ffi.typeof(o) == datetime_t)
+    local sz = 48
+    local buff = ffi.new('char[?]', sz)
+    local len = native.datetime_to_string(o, buff, sz)
+    assert(len < sz)
+    return ffi.string(buff)
+end
+
+local function dt_to_ymd(dt)
+    local y, m, d
+    y = ffi.new('int[1]')
+    m = ffi.new('int[1]')
+    d = ffi.new('int[1]')
+    cdt.dt_to_ymd(dt, y, m, d)
+    return y[0], m[0], d[0]
+end
+
+local function check_date(o)
+    assert(ffi.typeof(o) == datetime_t, "date/time expected")
+end
+
+local function date_first(lhs, rhs)
+    if (ffi.typeof(lhs) == datetime_t) then
+        return lhs, rhs
+    else
+        return rhs, lhs
+    end
+end
+
+local function shift_months(y, M, deltaM)
+    M = M + deltaM
+    local newM = (M - 1) % 12 + 1
+    local newY = y + math.floor((M - 1)/12)
+    assert(newM >= 1 and newM <= 12, "month value is outside of range")
+    return newY, newM
+end
+
+local function datetime_sub(lhs, rhs)
+    check_date(lhs) -- make sure left is date
+    local d, s = lhs, rhs
+
+    -- 1. left is date, right is date or delta
+    if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
+        d.secs = d.secs - s.secs
+        d.nsec = s.nsec - s.nsec
+        if d.nsec < 0 then
+            d.secs = d.secs - 1
+            d.nsec = d.nsec + NANOS_PER_SEC
+        end
+
+    -- 2. left is date, right is duration in months
+    elseif ffi.typeof(s) == duration_months_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y, M = shift_months(y, M, -s.m)
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+
+    -- 2. left is date, right is duration in years
+    elseif ffi.typeof(s) == duration_years_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y = y - s.y
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+    else
+        assert(false, "unexpected type")
+    end
+end
+
+local function datetime_add(lhs, rhs)
+    local d, s = date_first(lhs, rhs)
+
+    -- 1. left is date, right is date or delta
+    if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
+        d.secs = d.secs + s.secs
+        d.nsec = d.nsec + s.nsec
+        if d.nsec >= NANOS_PER_SEC then
+            d.secs = d.secs + 1
+            d.nsec = d.nsec - NANOS_PER_SEC
+        end
+        return d
+
+    -- 2. left is date, right is duration in months
+    elseif ffi.typeof(s) == duration_months_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y, M = shift_months(y, M, s.m)
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+
+    -- 2. left is date, right is duration in years
+    elseif ffi.typeof(s) == duration_years_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y = y + s.y
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+    else
+        assert(false, "unexpected type")
+    end
+end
 
 -- simple parse functions:
 -- parse_date/parse_time/parse_zone
@@ -572,15 +676,28 @@ local function strftime(fmt, o)
     return ffi.string(buff)
 end
 
-local function datetime_tostring(o)
-    assert(ffi.typeof(o) == datetime_t)
-    local sz = 48
-    local buff = ffi.new('char[?]', sz)
-    local len = native.datetime_to_string(o, buff, sz)
-    assert(len < sz)
-    return ffi.string(buff)
-end
 
+local datetime_mt = {
+    -- __tostring = datetime_tostring,
+    __serialize = datetime_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
+
+local duration_mt = {
+    -- __tostring = duration_tostring,
+    __serialize = duration_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
 
 ffi.metatype(duration_t, duration_mt)
 ffi.metatype(datetime_t, datetime_mt)
@@ -588,6 +705,11 @@ ffi.metatype(datetime_t, datetime_mt)
 return setmetatable(
     {
         datetime = datetime_new,
+        years = duration_years_new,
+        months = duration_months_new,
+        days = duration_days_new,
+        hours = duration_hours_new,
+        minutes = duration_minutes_new,
         delta = duration_new,
 
         parse = parse_str,
-- 
2.29.2



More information about the Tarantool-patches mailing list