Tarantool development patches archive
 help / color / mirror / Atom feed
From: Timur Safin via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: v.shpilevoy@tarantool.org
Cc: tarantool-patches@dev.tarantool.org
Subject: [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support
Date: Thu, 15 Jul 2021 11:18:18 +0300	[thread overview]
Message-ID: <5f3f523b33ba209621f769582dc16e612b649a05.1626335242.git.tsafin@tarantool.org> (raw)
In-Reply-To: <cover.1626335241.git.tsafin@tarantool.org>

* 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


  parent reply	other threads:[~2021-07-15  8:24 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 02/13] lua: built-in module datetime Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 03/13] test: datetime test Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 04/13] test: datetime string formatting Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 05/13] box: add messagepack support for datetime Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 06/13] lua: positive/negative cases in datetime test Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 07/13] lua: asctime and strfime fixed Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 08/13] box, lua: renamed t_datetime_tz structure to datetime_t Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 09/13] lua: calculated attributes for date Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 10/13] lua: tostring formatization in datetime.lua Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 11/13] test: allow relaxed date format without tz Timur Safin via Tarantool-patches
2021-07-15  8:18 ` Timur Safin via Tarantool-patches [this message]
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 13/13] lua: complete time duration support Timur Safin via Tarantool-patches
2021-07-15 16:56 ` [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Oleg Babin via Tarantool-patches
2021-07-15 23:03   ` Timur Safin via Tarantool-patches
2021-07-16  6:58     ` Oleg Babin via Tarantool-patches
2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
2021-07-22 12:54   ` Timur Safin 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=5f3f523b33ba209621f769582dc16e612b649a05.1626335242.git.tsafin@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=tsafin@tarantool.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='Re: [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration 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