From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 683A76EC56; Thu, 15 Jul 2021 11:24:57 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 683A76EC56 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1626337497; bh=ude+hBWiPMfs+axdsq3YwPcj1AN8iTv8BhcFU8Zu1OM=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=Jms30w1mtOJRKJ/tGNABwqiLIQcNcpQLzkIYtWLPJd1/ywGEY7axq1KNp+Yv4cqlS we5bSC6Mfr80jp2cIqdVqZ/3gzAd7uGgF1rQ26TqzRZFkVnQRgs8LGUNEsBXEy5IjP 3wNSWbSxeKmNwXLIlHqkqgzO0A4Lbc5cLDcpCNCg= Received: from smtp57.i.mail.ru (smtp57.i.mail.ru [217.69.128.37]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 5A4476F3CB for ; Thu, 15 Jul 2021 11:19:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 5A4476F3CB Received: by smtp57.i.mail.ru with esmtpa (envelope-from ) id 1m3waF-00070N-6b; Thu, 15 Jul 2021 11:19:03 +0300 To: v.shpilevoy@tarantool.org Date: Thu, 15 Jul 2021 11:18:18 +0300 Message-Id: <5f3f523b33ba209621f769582dc16e612b649a05.1626335242.git.tsafin@tarantool.org> X-Mailer: git-send-email 2.29.2 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-4EC0790: 10 X-7564579A: B8F34718100C35BD X-77F55803: 4F1203BC0FB41BD941C43E597735A9C3104FC76DFAAAAF7DA59606DD4E7E0E75182A05F53808504000723CAB14282F293AEEE2D4BE341BDE9E97226691C1D58FCD49ED647566E317 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE716FAD50E497B9C14EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F79006374FBED2B83F5E00CA8638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D807F125F59964489D4085D596B8D91A4F117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BAA867293B0326636D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EEC24E1E72F37C03A04782AAF36435267CD8FC6C240DEA7642DBF02ECDB25306B2B78CF848AE20165D0A6AB1C7CE11FEE3E753FA5741D1AD029735652A29929C6CC4224003CC836476EA7A3FFF5B025636E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F7900637AD0424077D726551EFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-C1DE0DAB: C20DE7B7AB408E4181F030C43753B8186998911F362727C414F749A5E30D975CE68746B1F2AB10C65A62F95FD70D53D14BBBD618DEF45C619C2B6934AE262D3EE7EAB7254005DCED7532B743992DF240BDC6A1CF3F042BAD6DF99611D93F60EF3033054805BDE987699F904B3F4130E343918A1A30D5E7FCCB5012B2E24CD356 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D34F7CC4EA888783AE05575B7EBC540B4ABBB7163EDCADFC8605347FD625107A1BBFDC994D3EBC373AF1D7E09C32AA3244C8D6B2D5F3FD5BA948C739700E8D2B9E35A1673A01BA68E4083B48618A63566E0 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojbL9S8ysBdXjnYxxC9kpVb375BHY5rMyD X-Mailru-Sender: B5B6A6EBBD94DAD8DA84A184D75F19DB2E7B7B20C3E75D0F5EA07B9A1D24B86D4B181888116467E71EC9E4A2C82A33BC8C24925A86E657CE0C70AEE3C9A96FBAB3D7EE8ED63280BE112434F685709FCF0DA7A0AF5A3A8387 X-Mras: Ok Subject: [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Timur Safin via Tarantool-patches Reply-To: Timur Safin Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" * 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