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 02/13] lua: built-in module datetime Date: Thu, 15 Jul 2021 11:18:08 +0300 [thread overview] Message-ID: <36385495b5da7608828ab3da3e592a4a25421220.1626335241.git.tsafin@tarantool.org> (raw) In-Reply-To: <cover.1626335241.git.tsafin@tarantool.org> * Added a new Tarantool built-in module `datetime`; * Register cdef types for this module; * Export some dt_* functions from c-dt library; * Datetime parse unit tests; --- src/CMakeLists.txt | 3 + src/exports.h | 21 ++ src/lib/core/datetime.h | 61 +++++ src/lua/datetime.c | 64 +++++ src/lua/datetime.h | 53 ++++ src/lua/datetime.lua | 564 ++++++++++++++++++++++++++++++++++++++ src/lua/init.c | 6 +- test/unit/CMakeLists.txt | 2 + test/unit/datetime.c | 157 +++++++++++ test/unit/datetime.result | 135 +++++++++ 10 files changed, 1065 insertions(+), 1 deletion(-) create mode 100644 src/lib/core/datetime.h create mode 100644 src/lua/datetime.c create mode 100644 src/lua/datetime.h create mode 100644 src/lua/datetime.lua create mode 100644 test/unit/datetime.c create mode 100644 test/unit/datetime.result diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef6a295d5..9d3da10d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,8 @@ lua_source(lua_sources ../third_party/luafun/fun.lua) lua_source(lua_sources lua/httpc.lua) lua_source(lua_sources lua/iconv.lua) lua_source(lua_sources lua/swim.lua) +lua_source(lua_sources lua/datetime.lua) + # LuaJIT jit.* library lua_source(lua_sources ${LUAJIT_SOURCE_ROOT}/src/jit/bc.lua) lua_source(lua_sources ${LUAJIT_SOURCE_ROOT}/src/jit/bcsave.lua) @@ -136,6 +138,7 @@ set (server_sources lua/string.c lua/swim.c lua/decimal.c + lua/datetime.c ${lua_sources} ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c diff --git a/src/exports.h b/src/exports.h index 5bb3e6a2b..db40c03a4 100644 --- a/src/exports.h +++ b/src/exports.h @@ -531,3 +531,24 @@ EXPORT(uri_format) EXPORT(uri_parse) EXPORT(uuid_nil) EXPORT(uuid_unpack) +EXPORT(dt_from_rdn) +EXPORT(dt_from_yd) +EXPORT(dt_from_ymd) +EXPORT(dt_from_yqd) +EXPORT(dt_from_ywd) +EXPORT(dt_to_yd) +EXPORT(dt_to_ymd) +EXPORT(dt_to_yqd) +EXPORT(dt_to_ywd) +EXPORT(dt_rdn) +EXPORT(dt_dow) +EXPORT(dt_parse_iso_date) +EXPORT(dt_parse_iso_time) +EXPORT(dt_parse_iso_time_basic) +EXPORT(dt_parse_iso_time_extended) +EXPORT(dt_parse_iso_zone) +EXPORT(dt_parse_iso_zone_basic) +EXPORT(dt_parse_iso_zone_extended) +EXPORT(dt_parse_iso_zone_lenient) +EXPORT(dt_from_struct_tm) +EXPORT(dt_to_struct_tm) diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h new file mode 100644 index 000000000..837ed346c --- /dev/null +++ b/src/lib/core/datetime.h @@ -0,0 +1,61 @@ +#pragma once +/* + * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <c-dt/dt_core.h> +#include <stdint.h> +#include <stdbool.h> + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +/** + * datetime structure consisting of: + */ +struct t_datetime_tz { + int secs; ///< seconds since epoch + int nsec; ///< nanoseconds if any + int offset; ///< offset in minutes from GMT +}; + +/** + * Date/time delta structure + */ +struct t_datetime_duration { + int secs; ///< relative seconds delta + int nsec; ///< nanoseconds delta +}; + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + diff --git a/src/lua/datetime.c b/src/lua/datetime.c new file mode 100644 index 000000000..37a8bc020 --- /dev/null +++ b/src/lua/datetime.c @@ -0,0 +1,64 @@ +/* + * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "datetime.h" + +#include <assert.h> + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +uint32_t CTID_DATETIME_TZ = 0; +uint32_t CTID_DURATION = 0; + +void +tarantool_lua_datetime_init(struct lua_State *L) +{ + int rc = luaL_cdef(L, "struct t_datetime_tz {" + "int secs;" + "int nsec;" + "int offset;" + "};"); + assert(rc == 0); + (void) rc; + CTID_DATETIME_TZ = luaL_ctypeid(L, "struct t_datetime_tz"); + assert(CTID_DATETIME_TZ != 0); + + + rc = luaL_cdef(L, "struct t_datetime_duration {" + "int secs;" + "int nsec;" + "};"); + assert(rc == 0); + (void) rc; + CTID_DURATION = luaL_ctypeid(L, "struct t_datetime_duration"); + assert(CTID_DURATION != 0); +} diff --git a/src/lua/datetime.h b/src/lua/datetime.h new file mode 100644 index 000000000..d290a6d13 --- /dev/null +++ b/src/lua/datetime.h @@ -0,0 +1,53 @@ +#pragma once +/* + * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "lib/core/datetime.h" +#include "utils.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +extern uint32_t CTID_DATETIME_TZ; +extern uint32_t CTID_DURATION; + +struct lua_State; + +struct t_datetime_tz* +lua_pushdatetime(struct lua_State *L); + +void +tarantool_lua_datetime_init(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua new file mode 100644 index 000000000..cffa38cd5 --- /dev/null +++ b/src/lua/datetime.lua @@ -0,0 +1,564 @@ +local ffi = require('ffi') +local cdt = ffi.C + +ffi.cdef [[ + + typedef int dt_t; + + // dt_core.h + typedef enum { + DT_MON = 1, + DT_MONDAY = 1, + DT_TUE = 2, + DT_TUESDAY = 2, + DT_WED = 3, + DT_WEDNESDAY = 3, + DT_THU = 4, + DT_THURSDAY = 4, + DT_FRI = 5, + DT_FRIDAY = 5, + DT_SAT = 6, + DT_SATURDAY = 6, + DT_SUN = 7, + DT_SUNDAY = 7, + } dt_dow_t; + + dt_t dt_from_rdn (int n); + dt_t dt_from_yd (int y, int d); + dt_t dt_from_ymd (int y, int m, int d); + dt_t dt_from_yqd (int y, int q, int d); + dt_t dt_from_ywd (int y, int w, int d); + + void dt_to_yd (dt_t dt, int *y, int *d); + void dt_to_ymd (dt_t dt, int *y, int *m, int *d); + void dt_to_yqd (dt_t dt, int *y, int *q, int *d); + void dt_to_ywd (dt_t dt, int *y, int *w, int *d); + + int dt_rdn (dt_t dt); + dt_dow_t dt_dow (dt_t dt); + + // dt_parse_iso.h + size_t dt_parse_iso_date (const char *str, size_t len, dt_t *dt); + + size_t dt_parse_iso_time (const char *str, size_t len, int *sod, int *nsec); + size_t dt_parse_iso_time_basic (const char *str, size_t len, int *sod, int *nsec); + size_t dt_parse_iso_time_extended (const char *str, size_t len, int *sod, int *nsec); + + size_t dt_parse_iso_zone (const char *str, size_t len, int *offset); + size_t dt_parse_iso_zone_basic (const char *str, size_t len, int *offset); + size_t dt_parse_iso_zone_extended (const char *str, size_t len, int *offset); + size_t dt_parse_iso_zone_lenient (const char *str, size_t len, int *offset); + + // dt_tm.h + dt_t dt_from_struct_tm (const struct tm *tm); + void dt_to_struct_tm (dt_t dt, struct tm *tm); + + // <asm-generic/posix_types.h> + typedef long __kernel_long_t; + typedef unsigned long __kernel_ulong_t; + // /usr/include/x86_64-linux-gnu/bits/types/time_t.h + typedef long time_t; + + + // <time.h> + typedef __kernel_long_t __kernel_time_t; + typedef __kernel_long_t __kernel_suseconds_t; + + struct timespec { + __kernel_time_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ + }; + + struct timeval { + __kernel_time_t tv_sec; /* seconds */ + __kernel_suseconds_t tv_usec; /* microseconds */ + }; + + struct timezone { + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of dst correction */ + }; + + // /usr/include/x86_64-linux-gnu/sys/time.h + typedef struct timezone * __timezone_ptr_t; + + /* Get the current time of day and timezone information, + putting it into *TV and *TZ. If TZ is NULL, *TZ is not filled. + Returns 0 on success, -1 on errors. + + NOTE: This form of timezone information is obsolete. + Use the functions and variables declared in <time.h> instead. */ + int gettimeofday (struct timeval *__tv, struct timezone * __tz); + + // /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h + /* ISO C `broken-down time' structure. */ + struct tm + { + int tm_sec; /* Seconds. [0-60] (1 leap second) */ + int tm_min; /* Minutes. [0-59] */ + int tm_hour; /* Hours. [0-23] */ + int tm_mday; /* Day. [1-31] */ + int tm_mon; /* Month. [0-11] */ + int tm_year; /* Year - 1900. */ + int tm_wday; /* Day of week. [0-6] */ + int tm_yday; /* Days in year.[0-365] */ + int tm_isdst; /* DST. [-1/0/1]*/ + + long int tm_gmtoff; /* Seconds east of UTC. */ + const char *tm_zone;/* Timezone abbreviation. */ + }; + + // <time.h> + /* Return the current time and put it in *TIMER if TIMER is not NULL. */ + time_t time (time_t *__timer); + + /* Format TP into S according to FORMAT. + Write no more than MAXSIZE characters and return the number + of characters written, or 0 if it would exceed MAXSIZE. */ + size_t strftime (char * __s, size_t __maxsize, const char * __format, + const struct tm * __tp); + + /* Parse S according to FORMAT and store binary time information in TP. + The return value is a pointer to the first unparsed character in S. */ + char *strptime (const char * __s, const char * __fmt, struct tm *__tp); + + /* Return the `struct tm' representation of *TIMER in UTC, + using *TP to store the result. */ + struct tm *gmtime_r (const time_t * __timer, struct tm * __tp); + + /* Return the `struct tm' representation of *TIMER in local time, + using *TP to store the result. */ + struct tm *localtime_r (const time_t * __timer, struct tm * __tp); + + /* Return a string of the form "Day Mon dd hh:mm:ss yyyy\n" + that is the representation of TP in this format. */ + char *asctime (const struct tm *__tp); + + /* Equivalent to `asctime (localtime (timer))'. */ + char *ctime (const time_t *__timer); + +]] + +local native = ffi.C + +local SECS_PER_DAY = 86400 +local NANOS_PER_SEC = 1000000000LL + +-- c-dt/dt_config.h + +-- Unix, January 1, 1970, Thursday +local DT_EPOCH_1970_OFFSET = 719163LL + + +local datetime_t = ffi.typeof('struct t_datetime_tz') +local duration_t = ffi.typeof('struct t_datetime_duration') + +local function duration_new() + local delta = ffi.new(duration_t) + return delta +end + +local function adjusted_secs(dt) + return dt.secs - dt.offset * 60 +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 +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 +end + +local function datetime_eq(lhs, rhs) + -- we usually don't need to check nullness + -- but older tarantool console will call us checking for equality to nil + if rhs == nil then + return false + end + return (lhs.secs == rhs.secs) and (lhs.nsec == rhs.nsec) +end + + +local function datetime_lt(lhs, rhs) + return (lhs.secs < rhs.secs) or + (lhs.secs == rhs.secs and lhs.nsec < rhs.nsec) +end + +local function datetime_le(lhs, rhs) + return (lhs.secs <= rhs.secs) or + (lhs.secs == rhs.secs and lhs.nsec <= rhs.nsec) +end + +local function datetime_serialize(self) + -- Allow YAML, MsgPack and JSON to dump objects with sockets + return { secs = self.secs, nsec = self.nsec, tz = self.offset } +end + +local function duration_serialize(self) + -- Allow YAML and JSON to dump objects with sockets + return { secs = self.secs, nsec = self.nsec } +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, + + nanoseconds = function(self) + return tonumber(self.secs*NANOS_PER_SEC + self.nsec) + end, + microseconds = function(self) + return tonumber(self.secs*1e6 + self.nsec*1e3) + end, + seconds = function(self) + return tonumber(self.secs + self.nsec*1e3) + end, + minutes = function(self) + return tonumber((self._ticks/(1e6*60))%60) + end, + hours = function(self) + return tonumber(self._ticks/(1e6*60*60)) + end, + +} + +local duration_mt = { + -- __tostring = duration_tostring, + __serialize = duration_serialize, + __eq = datetime_eq, + __lt = datetime_lt, + __le = datetime_le, +} + +local function datetime_new_raw(secs, nsec, offset) + local dt_obj = ffi.new(datetime_t) + dt_obj.secs = secs + dt_obj.nsec = nsec + dt_obj.offset = offset + return dt_obj +end + +local function local_rd(o) + return math.floor(o.secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET +end + +local function local_dt(o) + return cdt.dt_from_rdn(local_rd(o)) +end + +local function mk_timestamp(dt, sp, fp, offset) + local epochV = dt ~= nil and (cdt.dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY or 0 + local spV = sp ~= nil and sp or 0 + local fpV = fp ~= nil and fp or 0 + local ofsV = offset ~= nil and offset or 0 + return datetime_new_raw (epochV + spV - ofsV * 60, fpV, ofsV) +end + +-- create @datetime_t given object @o fields +local function datetime_new(o) + if o == nil then + return datetime_new_raw(0, 0, 0) + end + local secs = 0 + local nsec = 0 + local offset = 0 + local easy_way = false + local y, M, d, ymd + y, M, d, ymd = 0, 0, 0, false + + local h, m, s, frac, hms + h, m, s, frac, hms = 0, 0, 0, 0, false + + local dt = 0 + + for key, value in pairs(o) do + local handlers = { + secs = function(v) + secs = v + easy_way = true + end, + + nsec = function(v) + nsec = v + easy_way = true + end, + + offset = function (v) + offset = v + easy_way = true + end, + + year = function(v) + assert(v > 0 and v < 10000) + y = v + ymd = true + end, + + month = function(v) + assert(v > 0 and v < 12 ) + M = v + ymd = true + end, + + day = function(v) + assert(v > 0 and v < 32) + d = v + ymd = true + end, + + hour = function(v) + assert(v >= 0 and v < 24) + h = v + hms = true + end, + + minute = function(v) + assert(v >= 0 and v < 60) + m = v + hms = true + end, + + second = function(v) + assert(v >= 0 and v < 61) + frac = v % 1 + if frac then + s = v - (v % 1) + else + s = v + end + hms = true + end, + + -- tz offset in minutes + tz = function(v) + assert(v >= 0 and v <= 720) + offset = v + end + } + handlers[key](value) + end + + -- .sec, .nsec, .offset + if easy_way then + return datetime_new_raw(secs, nsec, offset) + end + + -- .year, .month, .day + if ymd then + dt = dt + cdt.dt_from_ymd(y, M, d) + end + + -- .hour, .minute, .second + if hms then + secs = h * 3600 + m * 60 + s + end + + return mk_timestamp(dt, secs, frac, offset) +end + + +-- simple parse functions: +-- parse_date/parse_time/parse_zone + +--[[ + Basic Extended + 20121224 2012-12-24 Calendar date (ISO 8601) + 2012359 2012-359 Ordinal date (ISO 8601) + 2012W521 2012-W52-1 Week date (ISO 8601) + 2012Q485 2012-Q4-85 Quarter date +]] + +local function parse_date(str) + local dt = ffi.new('dt_t[1]') + local rc = cdt.dt_parse_iso_date(str, #str, dt) + assert(rc > 0) + return mk_timestamp(dt[0]) +end + +--[[ + Basic Extended + T12 N/A + T1230 T12:30 + T123045 T12:30:45 + T123045.123456789 T12:30:45.123456789 + T123045,123456789 T12:30:45,123456789 + + The time designator [T] may be omitted. +]] +local function parse_time(str) + local sp = ffi.new('int[1]') + local fp = ffi.new('int[1]') + local rc = cdt.dt_parse_iso_time(str, #str, sp, fp) + assert(rc > 0) + return mk_timestamp(nil, sp[0], fp[0]) +end + +--[[ + Basic Extended + Z N/A + ±hh N/A + ±hhmm ±hh:mm +]] +local function parse_zone(str) + local offset = ffi.new('int[1]') + local rc = cdt.dt_parse_iso_zone(str, #str, offset) + assert(rc > 0) + return mk_timestamp(nil, nil, nil, offset[0]) +end + + +--[[ + aggregated parse functions + assumes to deal with date T time time_zone + at once + + date [T] time [ ] time_zone +]] +local function parse_str(str) + local dt = ffi.new('dt_t[1]') + local len = #str + local n = cdt.dt_parse_iso_date(str, len, dt) + local dt_ = dt[0] + if n == 0 or len == n then + return mk_timestamp(dt_) + end + + str = str:sub(tonumber(n) + 1) + + local ch = str:sub(1,1) + if ch ~= 't' and ch ~= 'T' and ch ~= ' ' then + return mk_timestamp(dt_) + end + + str = str:sub(2) + len = #str + + local sp = ffi.new('int[1]') + local fp = ffi.new('int[1]') + local n = cdt.dt_parse_iso_time(str, len, sp, fp) + if n == 0 then + return mk_timestamp(dt_) + end + local sp_ = sp[0] + local fp_ = fp[0] + if len == n then + return mk_timestamp(dt_, sp_, fp_) + end + + str = str:sub(tonumber(n) + 1) + + if str:sub(1,1) == ' ' then + str = str:sub(2) + end + + len = #str + + local offset = ffi.new('int[1]') + n = cdt.dt_parse_iso_zone(str, len, offset) + if n == 0 then + return mk_timestamp(dt_, sp_, fp_) + end + return mk_timestamp(dt_, sp_, fp_, offset[0]) +end + +local function datetime_from(o) + if o == nil or type(o) == 'table' then + return datetime_new(o) + elseif type(o) == 'string' then + return parse_str(o) + end +end + +local function local_now() + local p_tv = ffi.new ' struct timeval [1] ' + local rc = native.gettimeofday(p_tv, nil) + assert(rc == 0) + + local secs = p_tv[0].tv_sec + local nsec = p_tv[0].tv_usec * 1000 + + local p_time = ffi.new 'time_t[1]' + local p_tm = ffi.new 'struct tm[1]' + native.time(p_time) + native.localtime_r(p_time, p_tm) + -- local dt = cdt.dt_from_struct_tm(p_tm) + local ofs = p_tm[0].tm_gmtoff / 60 -- convert seconds to minutes + + return datetime_new_raw(secs, nsec, ofs) -- FIXME +end + +local function asctime(o) + assert(ffi.typeof(o) == datetime_t) + local p_tm = ffi.new 'struct tm[1]' + cdt.dt_to_struct_tm(local_dt(o), p_tm) + return ffi.string(native.asctime(p_tm)) +end + +local function ctime(o) + assert(ffi.typeof(o) == datetime_t) + local p_time = ffi.new 'time_t[1]' + p_time[0] = o.secs + return ffi.string(native.ctime(p_time)) +end + +local function strftime(fmt, o) + assert(ffi.typeof(o) == datetime_t) + local sz = 50 + local buff = ffi.new('char[?]', sz) + local p_tm = ffi.new 'struct tm[1]' + cdt.dt_to_struct_tm(local_dt(o), p_tm) + native.strftime(buff, sz, fmt, p_tm) + return ffi.string(buff) +end + +-- strftime may be redirected to datetime:fmt("format") +local function datetime_fmt() +end + + +ffi.metatype(duration_t, duration_mt) +ffi.metatype(datetime_t, datetime_mt) + +return setmetatable( + { + datetime = datetime_new, + delta = duration_new, + + parse = parse_str, + parse_date = parse_date, + parse_time = parse_time, + parse_zone = parse_zone, + fmt = datetime_fmt, + + now = local_now, + -- strptime = strptime; + strftime = strftime, + asctime = asctime, + ctime = ctime, + }, { + __call = function(self, ...) return datetime_from(...) end + } +) diff --git a/src/lua/init.c b/src/lua/init.c index f9738025d..89b070310 100644 --- a/src/lua/init.c +++ b/src/lua/init.c @@ -62,6 +62,7 @@ #include "lua/utf8.h" #include "lua/swim.h" #include "lua/decimal.h" +#include "lua/datetime.h" #include "digest.h" #include "errinj.h" #include <small/ibuf.h> @@ -129,7 +130,8 @@ extern char strict_lua[], parse_lua[], process_lua[], humanize_lua[], - memprof_lua[] + memprof_lua[], + datetime_lua[] ; static const char *lua_modules[] = { @@ -184,6 +186,7 @@ static const char *lua_modules[] = { "memprof.process", process_lua, "memprof.humanize", humanize_lua, "memprof", memprof_lua, + "datetime", datetime_lua, NULL }; @@ -479,6 +482,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv) tarantool_lua_serializer_init(L); tarantool_lua_swim_init(L); tarantool_lua_decimal_init(L); + tarantool_lua_datetime_init(L); luaopen_http_client_driver(L); lua_pop(L, 1); luaopen_msgpack(L); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5bb7cd6e7..f8320aebd 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -56,6 +56,8 @@ add_executable(uuid.test uuid.c core_test_utils.c) target_link_libraries(uuid.test uuid unit) add_executable(random.test random.c core_test_utils.c) target_link_libraries(random.test core unit) +add_executable(datetime.test datetime.c) +target_link_libraries(datetime.test cdt unit) add_executable(bps_tree.test bps_tree.cc) target_link_libraries(bps_tree.test small misc) diff --git a/test/unit/datetime.c b/test/unit/datetime.c new file mode 100644 index 000000000..25c9c1f1a --- /dev/null +++ b/test/unit/datetime.c @@ -0,0 +1,157 @@ +#include "dt.h" +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#include "unit.h" + +const char sample[] = "2012-12-24T15:30Z"; + +#define S(s) {s, sizeof(s) - 1} +struct { + const char * sz; + size_t len; +} tests[] = { + S("2012-12-24 15:30Z"), + S("2012-12-24 15:30z"), + S("2012-12-24 16:30+01:00"), + S("2012-12-24 16:30+0100"), + S("2012-12-24 16:30+01"), + S("2012-12-24 14:30-01:00"), + S("2012-12-24 14:30-0100"), + S("2012-12-24 14:30-01"), + S("2012-12-24 15:30:00Z"), + S("2012-12-24 15:30:00z"), + S("2012-12-24 16:30:00+01:00"), + S("2012-12-24 16:30:00+0100"), + S("2012-12-24 14:30:00-01:00"), + S("2012-12-24 14:30:00-0100"), + S("2012-12-24 15:30:00.123456Z"), + S("2012-12-24 15:30:00.123456z"), + S("2012-12-24 16:30:00.123456+01:00"), + S("2012-12-24 16:30:00.123456+01"), + S("2012-12-24 14:30:00.123456-01:00"), + S("2012-12-24 14:30:00.123456-01"), + S("2012-12-24t15:30Z"), + S("2012-12-24t15:30z"), + S("2012-12-24t16:30+01:00"), + S("2012-12-24t16:30+0100"), + S("2012-12-24t14:30-01:00"), + S("2012-12-24t14:30-0100"), + S("2012-12-24t15:30:00Z"), + S("2012-12-24t15:30:00z"), + S("2012-12-24t16:30:00+01:00"), + S("2012-12-24t16:30:00+0100"), + S("2012-12-24t14:30:00-01:00"), + S("2012-12-24t14:30:00-0100"), + S("2012-12-24t15:30:00.123456Z"), + S("2012-12-24t15:30:00.123456z"), + S("2012-12-24t16:30:00.123456+01:00"), + S("2012-12-24t14:30:00.123456-01:00"), + S("2012-12-24 16:30 +01:00"), + S("2012-12-24 14:30 -01:00"), + S("2012-12-24 15:30 UTC"), + S("2012-12-24 16:30 UTC+1"), + S("2012-12-24 16:30 UTC+01"), + S("2012-12-24 16:30 UTC+0100"), + S("2012-12-24 16:30 UTC+01:00"), + S("2012-12-24 14:30 UTC-1"), + S("2012-12-24 14:30 UTC-01"), + S("2012-12-24 14:30 UTC-01:00"), + S("2012-12-24 14:30 UTC-0100"), + S("2012-12-24 15:30 GMT"), + S("2012-12-24 16:30 GMT+1"), + S("2012-12-24 16:30 GMT+01"), + S("2012-12-24 16:30 GMT+0100"), + S("2012-12-24 16:30 GMT+01:00"), + S("2012-12-24 14:30 GMT-1"), + S("2012-12-24 14:30 GMT-01"), + S("2012-12-24 14:30 GMT-01:00"), + S("2012-12-24 14:30 GMT-0100"), + S("2012-12-24 14:30 -01:00"), + S("2012-12-24 16:30:00 +01:00"), + S("2012-12-24 14:30:00 -01:00"), + S("2012-12-24 16:30:00.123456 +01:00"), + S("2012-12-24 14:30:00.123456 -01:00"), + S("2012-12-24 15:30:00.123456 -00:00"), + S("20121224T1630+01:00"), + S("2012-12-24T1630+01:00"), + S("20121224T16:30+01"), + S("20121224T16:30 +01"), +}; +#undef S + +#define DIM(a) (sizeof(a) / sizeof(a[0])) + +// p5-time/moment/src/moment_parse.c: parse_string_lenient() +static int +parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np, + int64_t *op) +{ + size_t n; + dt_t dt; + char c; + int sod, nanosecond, offset; + + n = dt_parse_iso_date(str, len, &dt); + if (!n || n == len) + return 1; + + c = str[n++]; + if (!(c == 'T' || c == 't' || c == ' ')) + return 1; + + str += n; + len -= n; + + n = dt_parse_iso_time(str, len, &sod, &nanosecond); + if (!n || n == len) + return 1; + + if (str[n] == ' ') + n++; + + str += n; + len -= n; + + n = dt_parse_iso_zone_lenient(str, len, &offset); + if (!n || n != len) + return 1; + + *sp = ((int64_t)dt_rdn(dt) - 719163) * 86400 + sod - offset * 60; + *np = nanosecond; + *op = offset; + + return 0; +} + +static void datetime_test(void) +{ + size_t index; + int64_t secs_expected; + int64_t nanosecs; + int64_t ofs; + + plan(132); + parse_datetime(sample, sizeof(sample) - 1, + &secs_expected, &nanosecs, &ofs); + + for (index = 0; index < DIM(tests); index++) { + int64_t secs; + int rc = parse_datetime(tests[index].sz, tests[index].len, + &secs, &nanosecs, &ofs); + is(rc, 0, "correct parse_datetime return value for '%s'", + tests[index].sz); + is(secs, secs_expected, "correct parse_datetime output " + "seconds for '%s", tests[index].sz); + } +} + +int +main(void) +{ + plan(1); + datetime_test(); + + return check_plan(); +} diff --git a/test/unit/datetime.result b/test/unit/datetime.result new file mode 100644 index 000000000..5cd68dea5 --- /dev/null +++ b/test/unit/datetime.result @@ -0,0 +1,135 @@ +1..1 + 1..132 + ok 1 - correct parse_datetime return value for '2012-12-24 15:30Z' + ok 2 - correct parse_datetime output seconds for '2012-12-24 15:30Z + ok 3 - correct parse_datetime return value for '2012-12-24 15:30z' + ok 4 - correct parse_datetime output seconds for '2012-12-24 15:30z + ok 5 - correct parse_datetime return value for '2012-12-24 16:30+01:00' + ok 6 - correct parse_datetime output seconds for '2012-12-24 16:30+01:00 + ok 7 - correct parse_datetime return value for '2012-12-24 16:30+0100' + ok 8 - correct parse_datetime output seconds for '2012-12-24 16:30+0100 + ok 9 - correct parse_datetime return value for '2012-12-24 16:30+01' + ok 10 - correct parse_datetime output seconds for '2012-12-24 16:30+01 + ok 11 - correct parse_datetime return value for '2012-12-24 14:30-01:00' + ok 12 - correct parse_datetime output seconds for '2012-12-24 14:30-01:00 + ok 13 - correct parse_datetime return value for '2012-12-24 14:30-0100' + ok 14 - correct parse_datetime output seconds for '2012-12-24 14:30-0100 + ok 15 - correct parse_datetime return value for '2012-12-24 14:30-01' + ok 16 - correct parse_datetime output seconds for '2012-12-24 14:30-01 + ok 17 - correct parse_datetime return value for '2012-12-24 15:30:00Z' + ok 18 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z + ok 19 - correct parse_datetime return value for '2012-12-24 15:30:00z' + ok 20 - correct parse_datetime output seconds for '2012-12-24 15:30:00z + ok 21 - correct parse_datetime return value for '2012-12-24 16:30:00+01:00' + ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01:00 + ok 23 - correct parse_datetime return value for '2012-12-24 16:30:00+0100' + ok 24 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100 + ok 25 - correct parse_datetime return value for '2012-12-24 14:30:00-01:00' + ok 26 - correct parse_datetime output seconds for '2012-12-24 14:30:00-01:00 + ok 27 - correct parse_datetime return value for '2012-12-24 14:30:00-0100' + ok 28 - correct parse_datetime output seconds for '2012-12-24 14:30:00-0100 + ok 29 - correct parse_datetime return value for '2012-12-24 15:30:00.123456Z' + ok 30 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z + ok 31 - correct parse_datetime return value for '2012-12-24 15:30:00.123456z' + ok 32 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z + ok 33 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01:00' + ok 34 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01:00 + ok 35 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01' + ok 36 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01 + ok 37 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01:00' + ok 38 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01:00 + ok 39 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01' + ok 40 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01 + ok 41 - correct parse_datetime return value for '2012-12-24t15:30Z' + ok 42 - correct parse_datetime output seconds for '2012-12-24t15:30Z + ok 43 - correct parse_datetime return value for '2012-12-24t15:30z' + ok 44 - correct parse_datetime output seconds for '2012-12-24t15:30z + ok 45 - correct parse_datetime return value for '2012-12-24t16:30+01:00' + ok 46 - correct parse_datetime output seconds for '2012-12-24t16:30+01:00 + ok 47 - correct parse_datetime return value for '2012-12-24t16:30+0100' + ok 48 - correct parse_datetime output seconds for '2012-12-24t16:30+0100 + ok 49 - correct parse_datetime return value for '2012-12-24t14:30-01:00' + ok 50 - correct parse_datetime output seconds for '2012-12-24t14:30-01:00 + ok 51 - correct parse_datetime return value for '2012-12-24t14:30-0100' + ok 52 - correct parse_datetime output seconds for '2012-12-24t14:30-0100 + ok 53 - correct parse_datetime return value for '2012-12-24t15:30:00Z' + ok 54 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z + ok 55 - correct parse_datetime return value for '2012-12-24t15:30:00z' + ok 56 - correct parse_datetime output seconds for '2012-12-24t15:30:00z + ok 57 - correct parse_datetime return value for '2012-12-24t16:30:00+01:00' + ok 58 - correct parse_datetime output seconds for '2012-12-24t16:30:00+01:00 + ok 59 - correct parse_datetime return value for '2012-12-24t16:30:00+0100' + ok 60 - correct parse_datetime output seconds for '2012-12-24t16:30:00+0100 + ok 61 - correct parse_datetime return value for '2012-12-24t14:30:00-01:00' + ok 62 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01:00 + ok 63 - correct parse_datetime return value for '2012-12-24t14:30:00-0100' + ok 64 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100 + ok 65 - correct parse_datetime return value for '2012-12-24t15:30:00.123456Z' + ok 66 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z + ok 67 - correct parse_datetime return value for '2012-12-24t15:30:00.123456z' + ok 68 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z + ok 69 - correct parse_datetime return value for '2012-12-24t16:30:00.123456+01:00' + ok 70 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01:00 + ok 71 - correct parse_datetime return value for '2012-12-24t14:30:00.123456-01:00' + ok 72 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-01:00 + ok 73 - correct parse_datetime return value for '2012-12-24 16:30 +01:00' + ok 74 - correct parse_datetime output seconds for '2012-12-24 16:30 +01:00 + ok 75 - correct parse_datetime return value for '2012-12-24 14:30 -01:00' + ok 76 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00 + ok 77 - correct parse_datetime return value for '2012-12-24 15:30 UTC' + ok 78 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC + ok 79 - correct parse_datetime return value for '2012-12-24 16:30 UTC+1' + ok 80 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1 + ok 81 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01' + ok 82 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01 + ok 83 - correct parse_datetime return value for '2012-12-24 16:30 UTC+0100' + ok 84 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100 + ok 85 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01:00' + ok 86 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01:00 + ok 87 - correct parse_datetime return value for '2012-12-24 14:30 UTC-1' + ok 88 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1 + ok 89 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01' + ok 90 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01 + ok 91 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01:00' + ok 92 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01:00 + ok 93 - correct parse_datetime return value for '2012-12-24 14:30 UTC-0100' + ok 94 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-0100 + ok 95 - correct parse_datetime return value for '2012-12-24 15:30 GMT' + ok 96 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT + ok 97 - correct parse_datetime return value for '2012-12-24 16:30 GMT+1' + ok 98 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+1 + ok 99 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01' + ok 100 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01 + ok 101 - correct parse_datetime return value for '2012-12-24 16:30 GMT+0100' + ok 102 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100 + ok 103 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01:00' + ok 104 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01:00 + ok 105 - correct parse_datetime return value for '2012-12-24 14:30 GMT-1' + ok 106 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1 + ok 107 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01' + ok 108 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01 + ok 109 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01:00' + ok 110 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01:00 + ok 111 - correct parse_datetime return value for '2012-12-24 14:30 GMT-0100' + ok 112 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-0100 + ok 113 - correct parse_datetime return value for '2012-12-24 14:30 -01:00' + ok 114 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00 + ok 115 - correct parse_datetime return value for '2012-12-24 16:30:00 +01:00' + ok 116 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01:00 + ok 117 - correct parse_datetime return value for '2012-12-24 14:30:00 -01:00' + ok 118 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01:00 + ok 119 - correct parse_datetime return value for '2012-12-24 16:30:00.123456 +01:00' + ok 120 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +01:00 + ok 121 - correct parse_datetime return value for '2012-12-24 14:30:00.123456 -01:00' + ok 122 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00 + ok 123 - correct parse_datetime return value for '2012-12-24 15:30:00.123456 -00:00' + ok 124 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00:00 + ok 125 - correct parse_datetime return value for '20121224T1630+01:00' + ok 126 - correct parse_datetime output seconds for '20121224T1630+01:00 + ok 127 - correct parse_datetime return value for '2012-12-24T1630+01:00' + ok 128 - correct parse_datetime output seconds for '2012-12-24T1630+01:00 + ok 129 - correct parse_datetime return value for '20121224T16:30+01' + ok 130 - correct parse_datetime output seconds for '20121224T16:30+01 + ok 131 - correct parse_datetime return value for '20121224T16:30 +01' + ok 132 - correct parse_datetime output seconds for '20121224T16:30 +01 +ok 1 - subtests -- 2.29.2
next prev parent reply other threads:[~2021-07-15 8:19 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 ` Timur Safin via Tarantool-patches [this message] 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 ` [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support Timur Safin via Tarantool-patches 2021-07-15 8:18 ` [Tarantool-patches] [RFC PATCH 13/13] lua: complete " 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=36385495b5da7608828ab3da3e592a4a25421220.1626335241.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 02/13] lua: built-in module datetime' \ /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