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 5D17A6EC55; Fri, 30 Jul 2021 22:00:39 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 5D17A6EC55 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1627671639; bh=MsbBSRpAj7uYli/eZHraPjkVkjKaUN0GDd4Qu4szKjs=; h=To:Cc:References:In-Reply-To:Date:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=Zcbhf8PUnmCwgl/4fXac6ha+hBQUfA0mkiUpIkhQivJuNAaJNEkSlm3E9C9XyQzYt 2ZL4TNL3AbkLv2aKar9Pm6t/SWQ4dT9YJqUZuPsnXM3Uq4TvAPCyQ7jsDB/mHhFvMl knNunOF4k9RixWjCyVvVUHPWJv51s+Pzt9cgKbOw= Received: from smtp51.i.mail.ru (smtp51.i.mail.ru [94.100.177.111]) (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 04A446EC55 for ; Fri, 30 Jul 2021 22:00:37 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 04A446EC55 Received: by smtp51.i.mail.ru with esmtpa (envelope-from ) id 1m9XkK-0002Dd-Q1; Fri, 30 Jul 2021 22:00:37 +0300 To: "'Oleg Babin'" , Cc: References: <50175cbd00c57e3bc8eed222951a551f4bb7effb.1627468002.git.tsafin@tarantool.org> <2f63968b-490f-9abf-ab36-325553bd7609@tarantool.org> In-Reply-To: <2f63968b-490f-9abf-ab36-325553bd7609@tarantool.org> Date: Fri, 30 Jul 2021 22:00:36 +0300 Message-ID: <040c01d78575$2e9dcb80$8bd96280$@tarantool.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Mailer: Microsoft Outlook 16.0 Thread-Index: AQNJI0ot4YBfKbZyj2tYNprdOv6c+gGi40uBAjeWjIuoWQbToA== Content-Language: ru X-4EC0790: 10 X-7564579A: EEAE043A70213CC8 X-77F55803: 4F1203BC0FB41BD941C43E597735A9C3FDAB68B812060C77E97CF8617D978122182A05F53808504021249739F957336B84164ED57CB625A41E6483CCEF4E49FF15BD186471BF95DF X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE742D9BD90C58D50E0EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F790063748D05F5E01EE6C998638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D88F6F5F3890F9B10F36264AD5D6174BE4117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BAA867293B0326636D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EE91ADC097FE2C3A088F49F126DDB898E8D8FC6C240DEA7642DBF02ECDB25306B2B78CF848AE20165D0A6AB1C7CE11FEE3251EFD5447B32ED6C0837EA9F3D19764C4224003CC836476EA7A3FFF5B025636E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F7900637B8F435DEDE9E76EBEFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-B7AD71C0: AC4F5C86D027EB782CDD5689AFBDA7A213B5FB47DCBC3458F0AFF96BAACF4158235E5A14AD4A4A4625E192CAD1D9E79DB53CE84373687089504A46C6672099B5 X-C1DE0DAB: 0D63561A33F958A55702924EAFF910E6729C377699A72C04893F8102D54BCEC3D59269BC5F550898D99A6476B3ADF6B47008B74DF8BB9EF7333BD3B22AA88B938A852937E12ACA751B940EDA0DFB0535410CA545F18667F91A7EA1CDA0B5A7A0 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D34B5900AD87B4159A460766226834C2179747AE9A0D569FA65EEAE8B167ED8A267C764E17C8634571E1D7E09C32AA3244C5A7B06B079088B669AEC44EB15A382EA33C9DC155518937FFACE5A9C96DEB163 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojWBddABnKmoLFCc39Ag7BCg== X-Mailru-Sender: B5B6A6EBBD94DAD8DF9DB9650A8885C829E921A68D9A0C66985244EF865236D408E827A8DCDADF6A1EC9E4A2C82A33BC8C24925A86E657CE0C70AEE3C9A96FBAB3D7EE8ED63280BE112434F685709FCF0DA7A0AF5A3A8387 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH resend v2 02/11] lua: built-in module datetime 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 Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Hello Oleg, Thanks for quick review! : From: Oleg Babin : Subject: Re: [Tarantool-patches] [PATCH resend v2 02/11] lua: built-in : module datetime :=20 : Thanks for your patch! :=20 :=20 : Some places from prevous review are still not fixed for some reasons. Yup, that was oversight due to misunderstanding. Hopefully now I=20 Understand it better. :=20 :=20 : Please be careful with our Lua style guide I ponted some obvious = violations. :=20 : Also it would be great to analyze module functions with our memprof. I : think there are some places that could be optimized. Hmm, hmm, probably a good idea. (Assuming using memprof is not a=20 rocket science for such relatively newbie in LuaJIT as myself) ... :=20 : > diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua : > new file mode 100644 : > index 000000000..0996ca5a2 : > --- /dev/null : > +++ b/src/lua/datetime.lua : > @@ -0,0 +1,581 @@ : > +local ffi =3D require('ffi') :=20 : Do we have any benchmarks that shows that FFI is faster than Lua C = API? :=20 : Or it's just easy way to implement such module. Ok, here is a selection list which I've seen: - decimal is in LuaC; - uuid is in ffi; It looked equally acceptable and I've selected the one which is more convenient - via ffi. Now, you've pointed me to the probable performance issues we have with ffi, which are not there with LuaC. I believe they have been fixed with that Mike fix for NYI in structure allocations (after Sergos dirty fix) and once we will land this patch to our local fork those performance issues for structures=20 allocations will gone. In any case, 1st version of module is not yet a proper moment to start to worry about performance, more rather about clarity and quality of = API. We need to return to benchmarking of this code soon after code freeze lifted. :=20 :=20 : > +local cdt =3D ffi.C : > + : > +ffi.cdef [[ : > + : > + typedef int dt_t; : > + : > + // dt_core.h : > + typedef enum { : > + DT_MON =3D 1, : > + DT_MONDAY =3D 1, : > + DT_TUE =3D 2, : > + DT_TUESDAY =3D 2, : > + DT_WED =3D 3, : > + DT_WEDNESDAY =3D 3, : > + DT_THU =3D 4, : > + DT_THURSDAY =3D 4, : > + DT_FRI =3D 5, : > + DT_FRIDAY =3D 5, : > + DT_SAT =3D 6, : > + DT_SATURDAY =3D 6, : > + DT_SUN =3D 7, : > + DT_SUNDAY =3D 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); : > + : > + // : > + 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; : > + : > + : > + // : > + 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 = 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. */ : > + }; : > + : > + // : > + /* 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 =3D ffi.C :=20 : You've aleady declared cdt as ffi.C. There is a redefinition here. Is = it : really needed. Thanks for observation - that was copy-paste oversight from external = module code (tsafin/c-dt-ffi), which is not making much sense if module is = built-in. :=20 : I don't see anybody use "native" for ffi.C. There are two ways - : "builtin" and "C". 'builtin` is better, yes.=20 :=20 : I suppose to choose one of them. :=20 : > + : > +local SECS_PER_DAY =3D 86400 : > +local NANOS_PER_SEC =3D 1000000000LL : > + : > +-- c-dt/dt_config.h : > + : > +-- Unix, January 1, 1970, Thursday : > +local DT_EPOCH_1970_OFFSET =3D 719163LL :=20 : To be honest it's not completely clear that such value means until :=20 : I visited : https://github.com/chansen/c- : dt/blob/21b8cd1fcb984386b7d4552c16fdd03fafab2b6a/dt_config.h#L50. :=20 : I think some comment is needed here. Yeah, having explained this same topic to Vlad I now realize it looks a little bit confusing :) :=20 :=20 : > + : > + : > +local datetime_t =3D ffi.typeof('struct datetime_t') : > +local interval_t =3D ffi.typeof('struct datetime_interval_t') : > + : > +local function interval_new() : > + local interval =3D ffi.new(interval_t) : > + return interval : > +end : > + : > +local function adjusted_secs(dt) : > + return dt.secs - dt.offset * 60 : > +end : > + : > +local function datetime_sub(lhs, rhs) : > + local s1 =3D adjusted_secs(lhs) : > + local s2 =3D adjusted_secs(rhs) : > + local d =3D interval_new() : > + d.secs =3D s2 - s1 : > + d.nsec =3D rhs.nsec - lhs.nsec : > + if d.nsec < 0 then : > + d.secs =3D d.secs - 1 : > + d.nsec =3D d.nsec + NANOS_PER_SEC : > + end : > + return d : > +end : > + : > +local function datetime_add(lhs, rhs) : > + local s1 =3D adjusted_secs(lhs) : > + local s2 =3D adjusted_secs(rhs) : > + local d =3D interval_new() : > + d.secs =3D s2 + s1 : > + d.nsec =3D rhs.nsec + lhs.nsec : > + if d.nsec >=3D NANOS_PER_SEC then : > + d.secs =3D d.secs + 1 : > + d.nsec =3D 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 =3D=3D nil then : > + return false : > + end : > + return (lhs.secs =3D=3D rhs.secs) and (lhs.nsec =3D=3D = rhs.nsec) : > +end : > + :=20 : As in the previous review it will fail on attempt to compare with not : datetime value. :=20 : tarantool> datetime.new() =3D=3D newproxy() : --- : - error: 'builtin/datetime.lua:222: bad argument #1 to ''is_datetime'' : (C type expected, : got userdata)' : ... :=20 :=20 : Expected false. Understood. Finally! Here is the code with which I've ended up now: @@ -292,30 +295,32 @@ local function interval_seconds_new(s) return o end =20 -local function datetime_eq(lhs, rhs) - check_date_interval(lhs, "datetime:__eq(date or interval)") - check_date_interval(rhs, "datetime:__eq(date or interval)") - return (lhs.secs =3D=3D rhs.secs) and (lhs.nsec =3D=3D rhs.nsec) +local function datetime_cmp(lhs, rhs) + if not is_date_interval(lhs) or + not is_date_interval(rhs) then + return nil + end + return (lhs.secs - rhs.secs) or (lhs.nsec - rhs.nsec) end =20 +local function datetime_eq(lhs, rhs) + local rc =3D datetime_cmp(lhs, rhs)=20 + return rc ~=3D nil and rc =3D=3D 0 or false +end =20 local function datetime_lt(lhs, rhs) - check_date_interval(lhs, "datetime:__lt(date or interval)") - check_date_interval(rhs, "datetime:__lt(date or interval)") - return (lhs.secs < rhs.secs) or - (lhs.secs =3D=3D rhs.secs and lhs.nsec < rhs.nsec) + local rc =3D datetime_cmp(lhs, rhs)=20 + return rc ~=3D nil and rc < 0 or false end =20 local function datetime_le(lhs, rhs) - check_date_interval(lhs, "datetime:__le(date or interval)") - check_date_interval(rhs, "datetime:__le(date or interval)") - return (lhs.secs <=3D rhs.secs) or - (lhs.secs =3D=3D rhs.secs and lhs.nsec <=3D rhs.nsec) + local rc =3D datetime_cmp(lhs, rhs)=20 + return rc ~=3D nil and rc <=3D 0 or false end :=20 :=20 : > + : > +local function datetime_lt(lhs, rhs) : > + return (lhs.secs < rhs.secs) or : > + (lhs.secs =3D=3D rhs.secs and lhs.nsec < rhs.nsec) : > +end : > + : > +local function datetime_le(lhs, rhs) : > + return (lhs.secs <=3D rhs.secs) or : > + (lhs.secs =3D=3D rhs.secs and lhs.nsec <=3D rhs.nsec) : > +end : > + : > +local function datetime_serialize(self) : > + -- Allow YAML, MsgPack and JSON to dump objects with sockets : > + return { secs =3D self.secs, nsec =3D self.nsec, tz =3D = self.offset } : > +end : > + : > +local function interval_serialize(self) : > + -- Allow YAML and JSON to dump objects with sockets : > + return { secs =3D self.secs, nsec =3D self.nsec } : > +end : > + : > +local datetime_mt =3D { : > + -- __tostring =3D datetime_tostring, : > + __serialize =3D datetime_serialize, : > + __eq =3D datetime_eq, : > + __lt =3D datetime_lt, : > + __le =3D datetime_le, : > + __sub =3D datetime_sub, : > + __add =3D datetime_add, : > + : > + nanoseconds =3D function(self) : > + return tonumber(self.secs*NANOS_PER_SEC + self.nsec) : I think there should be a space before and after "*". Here and below. : > + end, : > + microseconds =3D function(self) : > + return tonumber(self.secs*1e6 + self.nsec*1e3) : > + end, : > + seconds =3D function(self) : > + return tonumber(self.secs + self.nsec*1e3) : > + end, : > + minutes =3D function(self) : > + return tonumber((self._ticks/(1e6*60))%60) : > + end, : > + hours =3D function(self) : > + return tonumber(self._ticks/(1e6*60*60)) : > + end, : > + : I think this empty line could be removed. : > +} : > + : > +local interval_mt =3D { : > + -- __tostring =3D interval_tostring, : > + __serialize =3D interval_serialize, : > + __eq =3D datetime_eq, : > + __lt =3D datetime_lt, : > + __le =3D datetime_le, : > +} : > + : > +local function datetime_new_raw(secs, nsec, offset) : > + local dt_obj =3D ffi.new(datetime_t) : > + dt_obj.secs =3D secs : > + dt_obj.nsec =3D nsec : > + dt_obj.offset =3D offset : > + return dt_obj : > +end : > + : > +local function local_rd(o) : > + return math.floor(tonumber(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 =3D dt ~=3D nil and (cdt.dt_rdn(dt) - = DT_EPOCH_1970_OFFSET) : * SECS_PER_DAY or 0 : > + local spV =3D sp ~=3D nil and sp or 0 : > + local fpV =3D fp ~=3D nil and fp or 0 : > + local ofsV =3D offset ~=3D 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 =3D=3D nil then : > + return datetime_new_raw(0, 0, 0) : > + end : > + local secs =3D 0 : > + local nsec =3D 0 : > + local offset =3D 0 : > + local easy_way =3D false : What does "easy_way" mean? As I've already tried to explain it to Vlad elsewhere: easy_way means direct usage of secs/nsec/offset attributed passed for creation of a=20 new datetime object, and not parsing of human-readable year/month/day/ hour/minute/seconds attributes it has to deal with otherwise. : > + local y, M, d, ymd : > + y, M, d, ymd =3D 0, 0, 0, false : > + : > + local h, m, s, frac, hms : > + h, m, s, frac, hms =3D 0, 0, 0, 0, false : > + : > + local dt =3D 0 : > + : > + for key, value in pairs(o) do : > + local handlers =3D { :=20 : Here you recreate handlers for each iteration of the loop. I think it : should be reworked. Yes, let me check how better will be if this handlers array will be = invariant in the loop, and not created each iteration. I still prefer this tag to function approach because of a better = clarity, but I need to verify in benchmark whether it provides acceptable = performance. I do not expect thought that this attribute object creation approach = would become a bootleneck in any case and might be on a critical path anywhere (I'd = rather expect=20 it for datetime objects created after parsing of massive bunch of log = text),=20 but need to look into timings we would have with invariant table. :=20 : Currenlty it's quite slow I think even if-elseif-else branches will = work : faster without :=20 : creating redundant GC objects. Will see ... :=20 : > + secs =3D function(v) : > + secs =3D v : > + easy_way =3D true : > + end, : > + : > + nsec =3D function(v) : > + nsec =3D v : > + easy_way =3D true : > + end, : > + : > + offset =3D function (v) : > + offset =3D v : > + easy_way =3D true : > + end, : > + : > + year =3D function(v) : > + assert(v > 0 and v < 10000) : Still there are some assertions that yield unclear error messages. They have been already reworked in a later patches of patchset.=20 [Probably better to squash them all for datetime.lua, yes] : > + y =3D v : > + ymd =3D true : > + end, : > + : > + month =3D function(v) : > + assert(v > 0 and v < 12 ) : > + M =3D v : > + ymd =3D true : > + end, : > + : > + day =3D function(v) : > + assert(v > 0 and v < 32) : > + d =3D v : > + ymd =3D true : > + end, : > + : > + hour =3D function(v) : > + assert(v >=3D 0 and v < 24) : > + h =3D v : > + hms =3D true : > + end, : > + : > + minute =3D function(v) : > + assert(v >=3D 0 and v < 60) : > + m =3D v : > + hms =3D true : > + end, : > + : > + second =3D function(v) : > + assert(v >=3D 0 and v < 61) : > + frac =3D v % 1 : > + if frac then : > + s =3D v - (v % 1) : > + else : > + s =3D v : > + end : > + hms =3D true : > + end, : > + : > + -- tz offset in minutes : > + tz =3D function(v) : > + assert(v >=3D 0 and v <=3D 720) : > + offset =3D 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 =3D dt + cdt.dt_from_ymd(y, M, d) : > + end : > + : > + -- .hour, .minute, .second : > + if hms then : > + secs =3D 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 =3D ffi.new('dt_t[1]') : > + local rc =3D 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 =3D ffi.new('int[1]') : > + local fp =3D ffi.new('int[1]') : > + local rc =3D 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 : > + =C2=B1hh N/A : > + =C2=B1hhmm =C2=B1hh:mm : > +]] : > +local function parse_zone(str) : > + local offset =3D ffi.new('int[1]') : > + local len =3D cdt.dt_parse_iso_zone_lenient(str, #str, offset) : > + return len > 0 and mk_timestamp(nil, nil, nil, offset[0]) or = nil, : tonumber(len) : > +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 =3D ffi.new('dt_t[1]') : > + local len =3D #str : > + local n =3D cdt.dt_parse_iso_date(str, len, dt) : > + local dt_ =3D dt[0] : > + if n =3D=3D 0 or len =3D=3D n then : > + return mk_timestamp(dt_) : > + end : > + : > + str =3D str:sub(tonumber(n) + 1) : > + : > + local ch =3D str:sub(1,1) : > + if ch ~=3D 't' and ch ~=3D 'T' and ch ~=3D ' ' then : > + return mk_timestamp(dt_) : > + end : > + : > + str =3D str:sub(2) : > + len =3D #str : > + : > + local sp =3D ffi.new('int[1]') : > + local fp =3D ffi.new('int[1]') : > + local n =3D cdt.dt_parse_iso_time(str, len, sp, fp) : > + if n =3D=3D 0 then : > + return mk_timestamp(dt_) : > + end : > + local sp_ =3D sp[0] : > + local fp_ =3D fp[0] : > + if len =3D=3D n then : > + return mk_timestamp(dt_, sp_, fp_) : > + end : > + : > + str =3D str:sub(tonumber(n) + 1) : > + : > + if str:sub(1,1) =3D=3D ' ' then : > + str =3D str:sub(2) : > + end : > + : > + len =3D #str : > + : > + local offset =3D ffi.new('int[1]') : > + n =3D cdt.dt_parse_iso_zone_lenient(str, len, offset) : > + if n =3D=3D 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 =3D=3D nil or type(o) =3D=3D 'table' then : > + return datetime_new(o) : > + elseif type(o) =3D=3D 'string' then : > + return parse_str(o) : > + end : > +end : > + : > +local function local_now() : > + local p_tv =3D ffi.new ' struct timeval [1] ' :=20 : This line doesn't conform our code-style. Please wrap argument into : brackets. Yes, sorry. But for consistency with surrounding code I've put it inside of parentheses. :=20 : The same for such places below. :=20 : > + local rc =3D native.gettimeofday(p_tv, nil) : > + assert(rc =3D=3D 0) : > + : > + local secs =3D p_tv[0].tv_sec : > + local nsec =3D p_tv[0].tv_usec * 1000 : > + : > + local p_time =3D ffi.new 'time_t[1]' : > + local p_tm =3D ffi.new 'struct tm[1]' : > + native.time(p_time) : > + native.localtime_r(p_time, p_tm) : > + -- local dt =3D cdt.dt_from_struct_tm(p_tm) : > + local ofs =3D p_tm[0].tm_gmtoff / 60 -- convert seconds to = minutes : > + : > + return datetime_new_raw(secs, nsec, ofs) -- FIXME : Do you plan to fix it before merge? Thanks for spot! Done. : > +end : > + : > +local function datetime_to_tm_ptr(o) : > + local p_tm =3D ffi.new 'struct tm[1]' : > + assert(ffi.typeof(o) =3D=3D datetime_t) : > + -- dt_to_struct_tm() fills only date data : > + cdt.dt_to_struct_tm(local_dt(o), p_tm) : > + : > + -- calculate the smaller data (hour, minute, : > + -- seconds) using datetime seconds value : > + local seconds_of_day =3D o.secs % 86400 : > + local hour =3D (seconds_of_day / 3600) % 24 : > + local minute =3D (seconds_of_day / 60) % 60 : > + p_tm[0].tm_sec =3D seconds_of_day % 60 : > + p_tm[0].tm_min =3D minute : > + p_tm[0].tm_hour =3D hour : > + : > + p_tm[0].tm_gmtoff =3D o.offset * 60 : > + : > + return p_tm : > +end : > + : > +local function asctime(o) : > + assert(ffi.typeof(o) =3D=3D datetime_t) : > + local p_tm =3D datetime_to_tm_ptr(o) : > + return ffi.string(native.asctime(p_tm)) : > +end : > + : > +local function ctime(o) : > + assert(ffi.typeof(o) =3D=3D datetime_t) : > + local p_time =3D ffi.new 'time_t[1]' : > + p_time[0] =3D o.secs : > + return ffi.string(native.ctime(p_time)) : > +end : > + : > +local function strftime(fmt, o) : > + assert(ffi.typeof(o) =3D=3D datetime_t) : > + local sz =3D 50 : Why 50? Good question, for ISO-8601 formats, for normal date range=20 (from 0000 till 9999 years) it's more like closer to 40, but=20 well, there will be various timezones and all crazy kinds of=20 input format combinations, so we need to either be precautious=20 with larger sizes allocations. Or probably rely on glibc convention (which is apparently not in=20 POSIX/SUSV) with 2 passes approach: 1. call with NULL so it will return size of required buffer; 2. then, after allocation, with the adjusted allocated buffer; https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Ti= me.html https://pubs.opengroup.org/onlinepubs/000095399/functions/strftime.html Something like that (beware of inline patch): ---------------------------------------------- local function strftime(fmt, o) check_date(o, "datetime.strftime(fmt, date)") - local sz =3D 50 - local buff =3D ffi.new('char[?]', sz) local p_tm =3D datetime_to_tm_ptr(o) - native.strftime(buff, sz, fmt, p_tm) + local sz =3D builtin.strftime(nil, 1024, fmt, p_tm) + local buff =3D ffi.new('char[?]', sz + 1) + builtin.strftime(buff, sz, fmt, p_tm) return ffi.string(buff) end ---------------------------------------------- : > + local buff =3D ffi.new('char[?]', sz) : > + local p_tm =3D datetime_to_tm_ptr(o) : > + 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(interval_t, interval_mt) : > +ffi.metatype(datetime_t, datetime_mt) : > + : > +return setmetatable( : > + { : > + datetime =3D datetime_new, : > + interval =3D interval_new, : > + : > + parse =3D parse_str, : > + parse_date =3D parse_date, : > + parse_time =3D parse_time, : > + parse_zone =3D parse_zone, : > + fmt =3D datetime_fmt, : > + : > + now =3D local_now, : > + -- strptime =3D strptime; : It should be dropped if you don't need it. : > + strftime =3D strftime, : > + asctime =3D asctime, : > + ctime =3D ctime, : > + }, { : > + __call =3D function(self, ...) return datetime_from(...) = end : > + } : > +) Thanks, Timur