[Tarantool-patches] [PATCH resend v2 02/11] lua: built-in module datetime

Oleg Babin olegrok at tarantool.org
Thu Jul 29 21:55:33 MSK 2021


Thanks for your patch!


Some places from prevous review are still not fixed for some reasons.


Please be careful with our Lua style guide I ponted some obvious violations.

Also it would be great to analyze module functions with our memprof. I 
think there are some

places that could be optimized.


On 28.07.2021 13:34, Timur Safin via Tarantool-patches wrote:
> * created a new Tarantool built-in module `datetime`;
> * register cdef types for this module;
> * export some `dt_*` functions from `c-dt` library;
> * lua implementationis of `asctime` and `strftime`;
> * datetime parsing unit tests, with and withput timezones;
> * c test for reversible strftime roundtrip;
>
> Part of #5941
> ---
>   src/CMakeLists.txt        |   2 +
>   src/exports.h             |  21 ++
>   src/lib/core/datetime.h   |  61 ++++
>   src/lua/datetime.lua      | 581 ++++++++++++++++++++++++++++++++++++++
>   src/lua/init.c            |   4 +-
>   src/lua/utils.c           |  27 ++
>   src/lua/utils.h           |  12 +
>   test/unit/CMakeLists.txt  |   2 +
>   test/unit/datetime.c      | 221 +++++++++++++++
>   test/unit/datetime.result | 358 +++++++++++++++++++++++
>   10 files changed, 1288 insertions(+), 1 deletion(-)
>   create mode 100644 src/lib/core/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..e0499e57f 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)
> 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..403bf1c64
> --- /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 datetime_t {
> +	int64_t secs;	///< seconds since epoch
> +	int32_t nsec;	///< nanoseconds if any
> +	int32_t offset; ///< offset in minutes from GMT
> +};
> +
> +/**
> + * Date/time delta structure
> + */
> +struct datetime_interval_t {
> +	int64_t secs; ///< relative seconds delta
> +	int32_t nsec; ///< nanoseconds delta
> +};
> +
> +#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..0996ca5a2
> --- /dev/null
> +++ b/src/lua/datetime.lua
> @@ -0,0 +1,581 @@
> +local ffi = require('ffi')

Do we have any benchmarks that shows that FFI is faster than Lua C API?

Or it's just easy way to implement such module.


> +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

You've aleady declared cdt as ffi.C. There is a redefinition here. Is it 
really needed.

I don't see anybody use "native" for ffi.C. There are two ways - 
"builtin" and "C".

I suppose to choose one of them.

> +
> +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

To be honest it's not completely clear that such value means until

I visited 
https://github.com/chansen/c-dt/blob/21b8cd1fcb984386b7d4552c16fdd03fafab2b6a/dt_config.h#L50.

I think some comment is needed here.


> +
> +
> +local datetime_t = ffi.typeof('struct datetime_t')
> +local interval_t = ffi.typeof('struct datetime_interval_t')
> +
> +local function interval_new()
> +    local interval = 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 = adjusted_secs(lhs)
> +    local s2 = adjusted_secs(rhs)
> +    local d = interval_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 = interval_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
> +

As in the previous review it will fail on attempt to compare with not 
datetime value.

tarantool> datetime.new() == newproxy()
---
- error: 'builtin/datetime.lua:222: bad argument #1 to ''is_datetime'' 
(C type expected,
     got userdata)'
...


Expected false.


> +
> +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 interval_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)
I think there should be a space before and after "*". Here and below.
> +    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,
> +
I think this empty line could be removed.
> +}
> +
> +local interval_mt = {
> +    -- __tostring = interval_tostring,
> +    __serialize = interval_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(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 = 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
What does "easy_way" mean?
> +    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 = {

Here you recreate handlers for each iteration of the loop. I think it 
should be reworked.

Currenlty it's quite slow I think even if-elseif-else branches will work 
faster without

creating redundant GC objects.

> +            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)
Still there are some assertions that yield unclear error messages.
> +                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 len = 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 = 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_lenient(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] '

This line doesn't conform our code-style. Please wrap argument into 
brackets.

The same for such places below.

> +    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
Do you plan to fix it before merge?
> +end
> +
> +local function datetime_to_tm_ptr(o)
> +    local p_tm = ffi.new 'struct tm[1]'
> +    assert(ffi.typeof(o) == 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 = o.secs % 86400
> +    local hour = (seconds_of_day / 3600) % 24
> +    local minute = (seconds_of_day / 60) % 60
> +    p_tm[0].tm_sec = seconds_of_day % 60
> +    p_tm[0].tm_min = minute
> +    p_tm[0].tm_hour = hour
> +
> +    p_tm[0].tm_gmtoff = o.offset * 60
> +
> +    return p_tm
> +end
> +
> +local function asctime(o)
> +    assert(ffi.typeof(o) == datetime_t)
> +    local p_tm = datetime_to_tm_ptr(o)
> +    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
Why 50?
> +    local buff = ffi.new('char[?]', sz)
> +    local p_tm = 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 = datetime_new,
> +        interval = interval_new,
> +
> +        parse = parse_str,
> +        parse_date = parse_date,
> +        parse_time = parse_time,
> +        parse_zone = parse_zone,
> +        fmt = datetime_fmt,
> +
> +        now = local_now,
> +    -- strptime = strptime;
It should be dropped if you don't need it.
> +        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..127e935d7 100644
> --- a/src/lua/init.c
> +++ b/src/lua/init.c
> @@ -129,7 +129,8 @@ extern char strict_lua[],
>   	parse_lua[],
>   	process_lua[],
>   	humanize_lua[],
> -	memprof_lua[]
> +	memprof_lua[],
> +	datetime_lua[]
>   ;
>   
>   static const char *lua_modules[] = {
> @@ -184,6 +185,7 @@ static const char *lua_modules[] = {
>   	"memprof.process", process_lua,
>   	"memprof.humanize", humanize_lua,
>   	"memprof", memprof_lua,
> +	"datetime", datetime_lua,
>   	NULL
>   };
>   
> diff --git a/src/lua/utils.c b/src/lua/utils.c
> index 34cec0eed..6aeb18cfe 100644
> --- a/src/lua/utils.c
> +++ b/src/lua/utils.c
> @@ -47,6 +47,9 @@ static uint32_t CTID_STRUCT_IBUF_PTR;
>   static uint32_t CTID_CHAR_PTR;
>   static uint32_t CTID_CONST_CHAR_PTR;
>   uint32_t CTID_UUID;
> +uint32_t CTID_DATETIME = 0;
> +uint32_t CTID_INTERVAL = 0;
> +
>   
>   void *
>   luaL_pushcdata(struct lua_State *L, uint32_t ctypeid)
> @@ -107,6 +110,12 @@ luaL_pushuuid(struct lua_State *L)
>   	return luaL_pushcdata(L, CTID_UUID);
>   }
>   
> +struct datetime_t *
> +luaL_pushdatetime(struct lua_State *L)
> +{
> +	return luaL_pushcdata(L, CTID_DATETIME);
> +}
> +
>   int
>   luaL_iscdata(struct lua_State *L, int idx)
>   {
> @@ -712,6 +721,24 @@ tarantool_lua_utils_init(struct lua_State *L)
>   	CTID_UUID = luaL_ctypeid(L, "struct tt_uuid");
>   	assert(CTID_UUID != 0);
>   
> +	rc = luaL_cdef(L, "struct datetime_t {"
> +			  "int64_t secs;"
> +			  "int32_t nsec;"
> +			  "int32_t offset;"
> +			  "};");
> +	assert(rc == 0);
> +	(void) rc;
> +	CTID_DATETIME = luaL_ctypeid(L, "struct datetime_t");
> +	assert(CTID_DATETIME != 0);
> +	rc = luaL_cdef(L, "struct datetime_interval_t {"
> +			  "int64_t secs;"
> +			  "int32_t nsec;"
> +			  "};");
> +	assert(rc == 0);
> +	(void) rc;
> +	CTID_INTERVAL = luaL_ctypeid(L, "struct datetime_interval_t");
> +	assert(CTID_INTERVAL != 0);
> +
>   	lua_pushcfunction(L, luaT_newthread_wrapper);
>   	luaT_newthread_ref = luaL_ref(L, LUA_REGISTRYINDEX);
>   	return 0;
> diff --git a/src/lua/utils.h b/src/lua/utils.h
> index 947d9240b..afd41c75b 100644
> --- a/src/lua/utils.h
> +++ b/src/lua/utils.h
> @@ -59,6 +59,7 @@ struct lua_State;
>   struct ibuf;
>   typedef struct ibuf box_ibuf_t;
>   struct tt_uuid;
> +struct datetime_t;
>   
>   /**
>    * Single global lua_State shared by core and modules.
> @@ -69,10 +70,21 @@ struct tt_uuid;
>   extern struct lua_State *tarantool_L;
>   
>   extern uint32_t CTID_UUID;
> +extern uint32_t CTID_DATETIME;
> +extern uint32_t CTID_INTERVAL;
>   
>   struct tt_uuid *
>   luaL_pushuuid(struct lua_State *L);
>   
> +/**
> + * @brief Push cdata of a datetime_t type onto the stack.
> + * @param L Lua State
> + * @sa luaL_pushcdata
> + * @return memory associated with this datetime_t data
> + */
> +struct datetime_t *
> +luaL_pushdatetime(struct lua_State *L);
> +
>   /** \cond public */
>   
>   /**
> 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..b6f568c03
> --- /dev/null
> +++ b/test/unit/datetime.c
> @@ -0,0 +1,221 @@
> +#include "dt.h"
> +#include <assert.h>
> +#include <stdint.h>
> +#include <string.h>
> +#include <time.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 15:30"),
> +	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 15:30:00"),
> +	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 15:30:00.123456"),
> +	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-24t15:30"),
> +	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-24t15:30:00"),
> +	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, int32_t *np,
> +	       int32_t *op)
> +{
> +	size_t n;
> +	dt_t dt;
> +	char c;
> +	int sod = 0, nanosecond = 0, offset = 0;
> +
> +	n = dt_parse_iso_date(str, len, &dt);
> +	if (!n)
> +		return 1;
> +	if (n == len)
> +		goto exit;
> +
> +	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)
> +		return 1;
> +	if (n == len)
> +		goto exit;
> +
> +	if (str[n] == ' ')
> +	n++;
> +
> +	str += n;
> +	len -= n;
> +
> +	n = dt_parse_iso_zone_lenient(str, len, &offset);
> +	if (!n || n != len)
> +		return 1;
> +
> +exit:
> +	*sp = ((int64_t)dt_rdn(dt) - 719163) * 86400 + sod - offset * 60;
> +	*np = nanosecond;
> +	*op = offset;
> +
> +	return 0;
> +}
> +
> +// avoid introducing external datetime.h dependency
> +// - just copy paste it for today
> +#define SECS_PER_DAY      86400
> +#define NANOS_PER_SEC     1000000000
> +#define DT_EPOCH_1970_OFFSET 719163
> +
> +
> +struct datetime_t {
> +	int64_t secs;
> +	int32_t nsec;
> +	int32_t offset;
> +};
> +
> +static int
> +local_rd(const struct datetime_t * dt) {
> +	return (int)(dt->secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET;
> +}
> +
> +static int
> +local_dt(const struct datetime_t * dt) {
> +	return dt_from_rdn(local_rd(dt));
> +}
> +
> +
> +struct tm*
> +datetime_to_tm(struct datetime_t * dt)
> +{
> +	static struct tm tm;
> +
> +	memset(&tm, 0, sizeof(tm));
> +	dt_to_struct_tm(local_dt(dt), &tm);
> +
> +	int seconds_of_day = dt->secs % 86400;
> +	tm.tm_hour = (seconds_of_day / 3600) % 24;
> +	tm.tm_min = (seconds_of_day / 60) % 60;
> +	tm.tm_sec = seconds_of_day % 60;
> +
> +	return &tm;
> +}
> +
> +static void datetime_test(void)
> +{
> +	size_t index;
> +	int64_t secs_expected;
> +	int32_t nanosecs;
> +	int32_t ofs;
> +
> +	plan(355);
> +	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);
> +
> +		// check that stringized literal produces the same date
> +		// time fields
> +		static char buff[40];
> +		struct datetime_t dt = {secs, nanosecs, ofs};
> +		// datetime_to_tm returns time in GMT zone
> +		struct tm * p_tm = datetime_to_tm(&dt);
> +		size_t len = strftime(buff, sizeof buff, "%F %T%z", p_tm);
> +		ok(len > 0, "strftime");
> +		rc = parse_datetime(buff, len, &dt.secs, &dt.nsec, &dt.offset);
> +		is(rc, 0, "correct parse_datetime return value for '%s'", buff);
> +		is(secs, dt.secs,
> +		   "reversible seconds via strftime for '%s", buff);
> +	}
> +}
> +
> +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..f106fa769
> --- /dev/null
> +++ b/test/unit/datetime.result
> @@ -0,0 +1,358 @@
> +1..1
> +    1..355
> +    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 - strftime
> +    ok 4 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 5 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 6 - correct parse_datetime return value for '2012-12-24 15:30z'
> +    ok 7 - correct parse_datetime output seconds for '2012-12-24 15:30z
> +    ok 8 - strftime
> +    ok 9 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 10 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 11 - correct parse_datetime return value for '2012-12-24 15:30'
> +    ok 12 - correct parse_datetime output seconds for '2012-12-24 15:30
> +    ok 13 - strftime
> +    ok 14 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 15 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 16 - correct parse_datetime return value for '2012-12-24 16:30+01:00'
> +    ok 17 - correct parse_datetime output seconds for '2012-12-24 16:30+01:00
> +    ok 18 - strftime
> +    ok 19 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 20 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 21 - correct parse_datetime return value for '2012-12-24 16:30+0100'
> +    ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30+0100
> +    ok 23 - strftime
> +    ok 24 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 25 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 26 - correct parse_datetime return value for '2012-12-24 16:30+01'
> +    ok 27 - correct parse_datetime output seconds for '2012-12-24 16:30+01
> +    ok 28 - strftime
> +    ok 29 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 30 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 31 - correct parse_datetime return value for '2012-12-24 14:30-01:00'
> +    ok 32 - correct parse_datetime output seconds for '2012-12-24 14:30-01:00
> +    ok 33 - strftime
> +    ok 34 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 35 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 36 - correct parse_datetime return value for '2012-12-24 14:30-0100'
> +    ok 37 - correct parse_datetime output seconds for '2012-12-24 14:30-0100
> +    ok 38 - strftime
> +    ok 39 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 40 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 41 - correct parse_datetime return value for '2012-12-24 14:30-01'
> +    ok 42 - correct parse_datetime output seconds for '2012-12-24 14:30-01
> +    ok 43 - strftime
> +    ok 44 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 45 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 46 - correct parse_datetime return value for '2012-12-24 15:30:00Z'
> +    ok 47 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z
> +    ok 48 - strftime
> +    ok 49 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 50 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 51 - correct parse_datetime return value for '2012-12-24 15:30:00z'
> +    ok 52 - correct parse_datetime output seconds for '2012-12-24 15:30:00z
> +    ok 53 - strftime
> +    ok 54 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 55 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 56 - correct parse_datetime return value for '2012-12-24 15:30:00'
> +    ok 57 - correct parse_datetime output seconds for '2012-12-24 15:30:00
> +    ok 58 - strftime
> +    ok 59 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 60 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 61 - correct parse_datetime return value for '2012-12-24 16:30:00+01:00'
> +    ok 62 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01:00
> +    ok 63 - strftime
> +    ok 64 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 65 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 66 - correct parse_datetime return value for '2012-12-24 16:30:00+0100'
> +    ok 67 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100
> +    ok 68 - strftime
> +    ok 69 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 70 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 71 - correct parse_datetime return value for '2012-12-24 14:30:00-01:00'
> +    ok 72 - correct parse_datetime output seconds for '2012-12-24 14:30:00-01:00
> +    ok 73 - strftime
> +    ok 74 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 75 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 76 - correct parse_datetime return value for '2012-12-24 14:30:00-0100'
> +    ok 77 - correct parse_datetime output seconds for '2012-12-24 14:30:00-0100
> +    ok 78 - strftime
> +    ok 79 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 80 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 81 - correct parse_datetime return value for '2012-12-24 15:30:00.123456Z'
> +    ok 82 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z
> +    ok 83 - strftime
> +    ok 84 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 85 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 86 - correct parse_datetime return value for '2012-12-24 15:30:00.123456z'
> +    ok 87 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z
> +    ok 88 - strftime
> +    ok 89 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 90 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 91 - correct parse_datetime return value for '2012-12-24 15:30:00.123456'
> +    ok 92 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456
> +    ok 93 - strftime
> +    ok 94 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 95 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 96 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01:00'
> +    ok 97 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01:00
> +    ok 98 - strftime
> +    ok 99 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 100 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 101 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01'
> +    ok 102 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01
> +    ok 103 - strftime
> +    ok 104 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 105 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 106 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01:00'
> +    ok 107 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01:00
> +    ok 108 - strftime
> +    ok 109 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 110 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 111 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01'
> +    ok 112 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01
> +    ok 113 - strftime
> +    ok 114 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 115 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 116 - correct parse_datetime return value for '2012-12-24t15:30Z'
> +    ok 117 - correct parse_datetime output seconds for '2012-12-24t15:30Z
> +    ok 118 - strftime
> +    ok 119 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 120 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 121 - correct parse_datetime return value for '2012-12-24t15:30z'
> +    ok 122 - correct parse_datetime output seconds for '2012-12-24t15:30z
> +    ok 123 - strftime
> +    ok 124 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 125 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 126 - correct parse_datetime return value for '2012-12-24t15:30'
> +    ok 127 - correct parse_datetime output seconds for '2012-12-24t15:30
> +    ok 128 - strftime
> +    ok 129 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 130 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 131 - correct parse_datetime return value for '2012-12-24t16:30+01:00'
> +    ok 132 - correct parse_datetime output seconds for '2012-12-24t16:30+01:00
> +    ok 133 - strftime
> +    ok 134 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 135 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 136 - correct parse_datetime return value for '2012-12-24t16:30+0100'
> +    ok 137 - correct parse_datetime output seconds for '2012-12-24t16:30+0100
> +    ok 138 - strftime
> +    ok 139 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 140 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 141 - correct parse_datetime return value for '2012-12-24t14:30-01:00'
> +    ok 142 - correct parse_datetime output seconds for '2012-12-24t14:30-01:00
> +    ok 143 - strftime
> +    ok 144 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 145 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 146 - correct parse_datetime return value for '2012-12-24t14:30-0100'
> +    ok 147 - correct parse_datetime output seconds for '2012-12-24t14:30-0100
> +    ok 148 - strftime
> +    ok 149 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 150 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 151 - correct parse_datetime return value for '2012-12-24t15:30:00Z'
> +    ok 152 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z
> +    ok 153 - strftime
> +    ok 154 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 155 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 156 - correct parse_datetime return value for '2012-12-24t15:30:00z'
> +    ok 157 - correct parse_datetime output seconds for '2012-12-24t15:30:00z
> +    ok 158 - strftime
> +    ok 159 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 160 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 161 - correct parse_datetime return value for '2012-12-24t15:30:00'
> +    ok 162 - correct parse_datetime output seconds for '2012-12-24t15:30:00
> +    ok 163 - strftime
> +    ok 164 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 165 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 166 - correct parse_datetime return value for '2012-12-24t16:30:00+01:00'
> +    ok 167 - correct parse_datetime output seconds for '2012-12-24t16:30:00+01:00
> +    ok 168 - strftime
> +    ok 169 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 170 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 171 - correct parse_datetime return value for '2012-12-24t16:30:00+0100'
> +    ok 172 - correct parse_datetime output seconds for '2012-12-24t16:30:00+0100
> +    ok 173 - strftime
> +    ok 174 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 175 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 176 - correct parse_datetime return value for '2012-12-24t14:30:00-01:00'
> +    ok 177 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01:00
> +    ok 178 - strftime
> +    ok 179 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 180 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 181 - correct parse_datetime return value for '2012-12-24t14:30:00-0100'
> +    ok 182 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100
> +    ok 183 - strftime
> +    ok 184 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 185 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 186 - correct parse_datetime return value for '2012-12-24t15:30:00.123456Z'
> +    ok 187 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z
> +    ok 188 - strftime
> +    ok 189 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 190 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 191 - correct parse_datetime return value for '2012-12-24t15:30:00.123456z'
> +    ok 192 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z
> +    ok 193 - strftime
> +    ok 194 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 195 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 196 - correct parse_datetime return value for '2012-12-24t16:30:00.123456+01:00'
> +    ok 197 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01:00
> +    ok 198 - strftime
> +    ok 199 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 200 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 201 - correct parse_datetime return value for '2012-12-24t14:30:00.123456-01:00'
> +    ok 202 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-01:00
> +    ok 203 - strftime
> +    ok 204 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 205 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 206 - correct parse_datetime return value for '2012-12-24 16:30 +01:00'
> +    ok 207 - correct parse_datetime output seconds for '2012-12-24 16:30 +01:00
> +    ok 208 - strftime
> +    ok 209 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 210 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 211 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
> +    ok 212 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
> +    ok 213 - strftime
> +    ok 214 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 215 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 216 - correct parse_datetime return value for '2012-12-24 15:30 UTC'
> +    ok 217 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC
> +    ok 218 - strftime
> +    ok 219 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 220 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 221 - correct parse_datetime return value for '2012-12-24 16:30 UTC+1'
> +    ok 222 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1
> +    ok 223 - strftime
> +    ok 224 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 225 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 226 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01'
> +    ok 227 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01
> +    ok 228 - strftime
> +    ok 229 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 230 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 231 - correct parse_datetime return value for '2012-12-24 16:30 UTC+0100'
> +    ok 232 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100
> +    ok 233 - strftime
> +    ok 234 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 235 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 236 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01:00'
> +    ok 237 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01:00
> +    ok 238 - strftime
> +    ok 239 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 240 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 241 - correct parse_datetime return value for '2012-12-24 14:30 UTC-1'
> +    ok 242 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1
> +    ok 243 - strftime
> +    ok 244 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 245 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 246 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01'
> +    ok 247 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01
> +    ok 248 - strftime
> +    ok 249 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 250 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 251 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01:00'
> +    ok 252 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01:00
> +    ok 253 - strftime
> +    ok 254 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 255 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 256 - correct parse_datetime return value for '2012-12-24 14:30 UTC-0100'
> +    ok 257 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-0100
> +    ok 258 - strftime
> +    ok 259 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 260 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 261 - correct parse_datetime return value for '2012-12-24 15:30 GMT'
> +    ok 262 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT
> +    ok 263 - strftime
> +    ok 264 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 265 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 266 - correct parse_datetime return value for '2012-12-24 16:30 GMT+1'
> +    ok 267 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+1
> +    ok 268 - strftime
> +    ok 269 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 270 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 271 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01'
> +    ok 272 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01
> +    ok 273 - strftime
> +    ok 274 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 275 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 276 - correct parse_datetime return value for '2012-12-24 16:30 GMT+0100'
> +    ok 277 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100
> +    ok 278 - strftime
> +    ok 279 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 280 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 281 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01:00'
> +    ok 282 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01:00
> +    ok 283 - strftime
> +    ok 284 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 285 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 286 - correct parse_datetime return value for '2012-12-24 14:30 GMT-1'
> +    ok 287 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1
> +    ok 288 - strftime
> +    ok 289 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 290 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 291 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01'
> +    ok 292 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01
> +    ok 293 - strftime
> +    ok 294 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 295 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 296 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01:00'
> +    ok 297 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01:00
> +    ok 298 - strftime
> +    ok 299 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 300 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 301 - correct parse_datetime return value for '2012-12-24 14:30 GMT-0100'
> +    ok 302 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-0100
> +    ok 303 - strftime
> +    ok 304 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 305 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 306 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
> +    ok 307 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
> +    ok 308 - strftime
> +    ok 309 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 310 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 311 - correct parse_datetime return value for '2012-12-24 16:30:00 +01:00'
> +    ok 312 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01:00
> +    ok 313 - strftime
> +    ok 314 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 315 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 316 - correct parse_datetime return value for '2012-12-24 14:30:00 -01:00'
> +    ok 317 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01:00
> +    ok 318 - strftime
> +    ok 319 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 320 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 321 - correct parse_datetime return value for '2012-12-24 16:30:00.123456 +01:00'
> +    ok 322 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +01:00
> +    ok 323 - strftime
> +    ok 324 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 325 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 326 - correct parse_datetime return value for '2012-12-24 14:30:00.123456 -01:00'
> +    ok 327 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00
> +    ok 328 - strftime
> +    ok 329 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 330 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 331 - correct parse_datetime return value for '2012-12-24 15:30:00.123456 -00:00'
> +    ok 332 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00:00
> +    ok 333 - strftime
> +    ok 334 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 335 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 336 - correct parse_datetime return value for '20121224T1630+01:00'
> +    ok 337 - correct parse_datetime output seconds for '20121224T1630+01:00
> +    ok 338 - strftime
> +    ok 339 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 340 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 341 - correct parse_datetime return value for '2012-12-24T1630+01:00'
> +    ok 342 - correct parse_datetime output seconds for '2012-12-24T1630+01:00
> +    ok 343 - strftime
> +    ok 344 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 345 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 346 - correct parse_datetime return value for '20121224T16:30+01'
> +    ok 347 - correct parse_datetime output seconds for '20121224T16:30+01
> +    ok 348 - strftime
> +    ok 349 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 350 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +    ok 351 - correct parse_datetime return value for '20121224T16:30 +01'
> +    ok 352 - correct parse_datetime output seconds for '20121224T16:30 +01
> +    ok 353 - strftime
> +    ok 354 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
> +    ok 355 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
> +ok 1 - subtests


More information about the Tarantool-patches mailing list