[Tarantool-patches] [PATCH v3 2/9] lua: built-in module datetime

Timur Safin tsafin at tarantool.org
Mon Aug 2 03:40:58 MSK 2021


* 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   | 112 ++++++
 src/lua/datetime.lua      | 756 ++++++++++++++++++++++++++++++++++++++
 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, 1514 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..2ec314369 100644
--- a/src/exports.h
+++ b/src/exports.h
@@ -218,6 +218,27 @@ EXPORT(curl_version)
 EXPORT(curl_version_info)
 #endif /* EXPORT_LIBCURL_SYMBOLS */
 EXPORT(decimal_unpack)
+EXPORT(dt_dow)
+EXPORT(dt_from_rdn)
+EXPORT(dt_from_struct_tm)
+EXPORT(dt_from_yd)
+EXPORT(dt_from_ymd)
+EXPORT(dt_from_yqd)
+EXPORT(dt_from_ywd)
+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_rdn)
+EXPORT(dt_to_struct_tm)
+EXPORT(dt_to_yd)
+EXPORT(dt_to_ymd)
+EXPORT(dt_to_yqd)
+EXPORT(dt_to_ywd)
 EXPORT(error_ref)
 EXPORT(error_set_prev)
 EXPORT(error_unpack_unsafe)
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
new file mode 100644
index 000000000..84d6642ab
--- /dev/null
+++ b/src/lib/core/datetime.h
@@ -0,0 +1,112 @@
+#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 <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "c-dt/dt.h"
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif /* defined(__cplusplus) */
+
+/**
+ * Full datetime structure representing moments
+ * since Unix Epoch (1970-01-01).
+ * Time is kept normalized to UTC, time-zone offset
+ * is informative only.
+ */
+struct datetime_t {
+	int64_t secs;	/**< seconds since epoch */
+	int32_t nsec;	/**< nanoseconds if any */
+	int32_t offset; /**< offset in minutes from UTC */
+};
+
+/**
+ * Date/time interval structure
+ */
+struct datetime_interval_t {
+	int64_t secs; /**< relative seconds delta */
+	int32_t nsec; /**< nanoseconds delta */
+};
+
+int
+datetime_compare(const struct datetime_t * lhs,
+		 const struct datetime_t * rhs);
+
+
+struct datetime_t *
+datetime_unpack(const char **data, uint32_t len, struct datetime_t *date);
+
+/**
+ * Pack datetime_t data to the MessagePack buffer.
+ */
+char *
+datetime_pack(char *data, const struct datetime_t *date);
+
+/**
+ * Calculate size of MessagePack buffer for datetime_t data.
+ */
+uint32_t
+mp_sizeof_datetime(const struct datetime_t *date);
+
+/**
+ * Decode data from MessagePack buffer to datetime_t structure.
+ */
+struct datetime_t *
+mp_decode_datetime(const char **data, struct datetime_t *date);
+
+/**
+ * Encode datetime_t structure to the MessagePack buffer.
+ */
+char *
+mp_encode_datetime(char *data, const struct datetime_t *date);
+
+/**
+ * Convert datetime to string using default format
+ * @param date source datetime value
+ * @param buf output character buffer
+ * @param len size ofoutput buffer
+ */
+int
+datetime_to_string(const struct datetime_t * date, char *buf, uint32_t len);
+
+int
+mp_snprint_datetime(char *buf, int size, const char **data, uint32_t len);
+
+int
+mp_fprint_datetime(FILE *file, const char **data, uint32_t len);
+
+#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..870edc541
--- /dev/null
+++ b/src/lua/datetime.lua
@@ -0,0 +1,756 @@
+local ffi = require('ffi')
+
+ffi.cdef [[
+
+    /*
+    `c-dt` library functions handles properly both positive and negative `dt`
+    values, where `dt` is a number of dates since Rata Die date (0001-01-01).
+
+    For better compactness of our typical data in MessagePack stream we shift
+    root of our time to the Unix Epoch date (1970-01-01), thus our 0 is
+    actually dt = 719163.
+
+    So here is a simple formula how convert our epoch-based seconds to dt values
+        dt = (secs / 86400) + 719163
+    Where 719163 is an offset of Unix Epoch (1970-01-01) since Rata Die
+    (0001-01-01) in dates.
+
+    */
+    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 builtin = 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 datetime_t')
+local interval_t = ffi.typeof('struct datetime_interval_t')
+
+local function is_interval(o)
+    return type(o) == 'cdata' and ffi.istype(interval_t, o)
+end
+
+local function is_datetime(o)
+    return type(o) == 'cdata' and ffi.istype(datetime_t, o)
+end
+
+local function is_date_interval(o)
+    return is_datetime(o) or is_interval(o)
+end
+
+local function interval_new()
+    local interval = ffi.new(interval_t)
+    return interval
+end
+
+local function check_date(o, message, lvl)
+    if lvl == nil then
+        lvl = 2
+    end
+    if not is_datetime(o) then
+        return error(("%s: expected datetime, but received %s"):
+                     format(message, o), lvl)
+    end
+end
+
+local function check_date_interval(o, message, lvl)
+    if lvl == nil then
+        lvl = 2
+    end
+    if not (is_datetime(o) or is_interval(o)) then
+        return error(("%s: expected datetime or interval, but received %s"):
+                     format(message, o), lvl)
+    end
+end
+
+local function check_interval(o, message, lvl)
+    if lvl == nil then
+        lvl = 2
+    end
+    if not is_interval(o) then
+        return error(("%s: expected interval, but received %s"):
+                     format(message, o), lvl)
+    end
+end
+
+local function check_str(s, message, lvl)
+    if lvl == nil then
+        lvl = 2
+    end
+    if not type(s) == 'string' then
+        return error(("%s: expected string, but received %s"):
+                     format(message, s), lvl)
+    end
+end
+
+local function datetime_cmp(lhs, rhs)
+    if not is_date_interval(lhs) or
+       not is_date_interval(rhs) then
+       return nil
+    end
+    local sdiff = lhs.secs - rhs.secs
+    return sdiff ~= 0 and sdiff or (lhs.nsec - rhs.nsec)
+end
+
+local function datetime_eq(lhs, rhs)
+    local rc = datetime_cmp(lhs, rhs)
+    return rc ~= nil and rc == 0 or false
+end
+
+local function datetime_lt(lhs, rhs)
+    local rc = datetime_cmp(lhs, rhs)
+    return rc ~= nil and rc < 0 or false
+end
+
+local function datetime_le(lhs, rhs)
+    local rc = datetime_cmp(lhs, rhs)
+    return rc ~= nil and rc <= 0 or false
+end
+
+local function datetime_serialize(self)
+    -- Allow YAML, MsgPack and JSON to dump objects with sockets
+    return { secs = self.secs, nsec = self.nsec, offset = 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 function local_rd(o)
+    return math.floor(tonumber(o.secs / SECS_PER_DAY)) + DT_EPOCH_1970_OFFSET
+end
+
+local function local_dt(o)
+    return builtin.dt_from_rdn(local_rd(o))
+end
+
+local function _normalize_nsec(secs, nsec)
+    if nsec < 0 then
+        secs = secs - 1
+        nsec = nsec + NANOS_PER_SEC
+    elseif nsec >= NANOS_PER_SEC then
+        secs = secs + 1
+        nsec = nsec - NANOS_PER_SEC
+    end
+    return secs, nsec
+end
+
+local function check_range(v, range, txt)
+    assert(#range == 2)
+    if not (v >= range[1] and v <= range[2]) then
+        error(('value %d of %s is out of allowed range [%d, %d]'):
+              format(v, txt, range[1], range[2]))
+    end
+end
+
+local datetime_index_handlers = {
+    unixtime = function(self)
+        return self.secs
+    end,
+
+    timestamp = function(self)
+        return tonumber(self.secs) + self.nsec / 1e9
+    end,
+
+    nanoseconds = function(self)
+        return self.secs * 1e9 + self.nsec
+    end,
+
+    microseconds = function(self)
+        return self.secs * 1e6 + self.nsec / 1e3
+    end,
+
+    milliseconds = function(self)
+        return self.secs * 1e3 + self.nsec / 1e6
+    end,
+
+    seconds = function(self)
+        return tonumber(self.secs) + self.nsec / 1e9
+    end,
+
+    minutes = function(self)
+        return (tonumber(self.secs) + self.nsec / 1e9) / 60
+    end,
+
+    hours = function(self)
+        return (tonumber(self.secs) + self.nsec / 1e9) / (60 * 60)
+    end,
+
+    days = function(self)
+        return (tonumber(self.secs) + self.nsec / 1e9) / (60 * 60) / 24
+    end,
+}
+
+local datetime_index = function(self, key)
+    return datetime_index_handlers[key] ~= nil and
+           datetime_index_handlers[key](self) or nil
+end
+
+local datetime_newindex_handlers = {
+    unixtime = function(self, value)
+        self.secs = value
+        self.nsec, self.offset = 0, 0
+    end,
+
+    timestamp = function(self, value)
+        local secs, frac = math.modf(value)
+        self.secs = secs
+        self.nsec = frac * 1e9
+        self.offset = 0
+    end,
+}
+
+local function datetime_newindex(self, key, value)
+    if datetime_newindex_handlers[key] ~= nil then
+        datetime_newindex_handlers[key](self, value)
+    end
+end
+
+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 mk_timestamp(dt, sp, fp, offset)
+    local epochV = dt ~= nil and (builtin.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 = 0
+    local M = 0
+    local d = 0
+    local ymd = false
+
+    local h = 0
+    local m = 0
+    local s = 0
+    local frac = 0
+    local hms = false
+
+    local dt = 0
+
+    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(k, v)
+            check_range(v, {1, 9999}, k)
+            y = v
+            ymd = true
+        end,
+
+        month = function(k, v)
+            check_range(v, {1, 12}, k)
+            M = v
+            ymd = true
+        end,
+
+        day = function(k, v)
+            check_range(v, {1, 31}, k)
+            d = v
+            ymd = true
+        end,
+
+        hour = function(k, v)
+            check_range(v, {0, 23}, k)
+            h = v
+            hms = true
+        end,
+
+        minute = function(k, v)
+            check_range(v, {0, 59}, k)
+            m = v
+            hms = true
+        end,
+
+        second = function(k, v)
+            check_range(v, {0, 60}, k)
+            s, frac = math.modf(v)
+            frac = frac * 1e9 -- convert fraction to nanoseconds
+            hms = true
+        end,
+
+        -- tz offset in minutes
+        tz = function(k, v)
+            check_range(v, {0, 720}, k)
+            offset = v
+        end
+    }
+    for key, value in pairs(o) do
+        handlers[key](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 + builtin.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
+
+local function date_first(lhs, rhs)
+    if is_datetime(lhs) then
+        return lhs, rhs
+    else
+        return rhs, lhs
+    end
+end
+
+local function error_incompatible(name)
+    error(("datetime:%s() - incompatible type of arguments"):
+          format(name), 3)
+end
+
+local function datetime_sub(lhs, rhs)
+    check_date_interval(lhs, "operator -")
+    local d, s = lhs, rhs
+    local left_t = ffi.typeof(d)
+    local right_t = ffi.typeof(s)
+    local o
+
+    if left_t == datetime_t then
+        -- left is date, right is date or generic interval
+        if (right_t == datetime_t or right_t == interval_t) then
+            o = right_t == datetime_t and interval_new() or datetime_new()
+            o.secs, o.nsec = _normalize_nsec(lhs.secs - rhs.secs,
+                                            lhs.nsec - rhs.nsec)
+            return o
+        else
+            error_incompatible("operator -")
+        end
+    -- both left and right are generic intervals
+    elseif left_t == interval_t and right_t == interval_t then
+        o = interval_new()
+        o.secs, o.nsec = _normalize_nsec(lhs.secs - rhs.secs,
+                                        lhs.nsec - rhs.nsec)
+        return o
+    else
+        error_incompatible("operator -")
+    end
+end
+
+local function datetime_add(lhs, rhs)
+    local d, s = date_first(lhs, rhs)
+
+    check_date_interval(d, "operator +")
+    check_interval(s, "operator +")
+    local left_t = ffi.typeof(d)
+    local right_t = ffi.typeof(s)
+    local o
+
+    -- left is date, right is date or interval
+    if left_t == datetime_t and right_t == interval_t then
+        o = datetime_new()
+        o.secs, o.nsec = _normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
+        return o
+    -- both left and right are generic intervals
+    elseif left_t == interval_t and right_t == interval_t then
+        o = interval_new()
+        o.secs, o.nsec = _normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
+        return o
+    else
+        error_incompatible("operator +")
+    end
+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)
+    check_str("datetime.parse_date()")
+    local dt = ffi.new('dt_t[1]')
+    local len = builtin.dt_parse_iso_date(str, #str, dt)
+    return len > 0 and mk_timestamp(dt[0]) or nil, tonumber(len)
+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)
+    check_str("datetime.parse_time()")
+    local sp = ffi.new('int[1]')
+    local fp = ffi.new('int[1]')
+    local len = builtin.dt_parse_iso_time(str, #str, sp, fp)
+    return len > 0 and mk_timestamp(nil, sp[0], fp[0]) or nil,
+           tonumber(len)
+end
+
+--[[
+    Basic    Extended
+    Z        N/A
+    +hh      N/A
+    -hh      N/A
+    +hhmm    +hh:mm
+    -hhmm    -hh:mm
+]]
+local function parse_zone(str)
+    check_str("datetime.parse_zone()")
+    local offset = ffi.new('int[1]')
+    local len = builtin.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)
+    check_str("datetime.parse()")
+    local dt = ffi.new('dt_t[1]')
+    local len = #str
+    local n = builtin.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:match('[Tt ]') == nil 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 = builtin.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 = builtin.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(o)
+    end
+end
+
+local function local_now()
+    local p_tv = ffi.new('struct timeval [1]')
+    local rc = builtin.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]')
+    builtin.time(p_time)
+    builtin.localtime_r(p_time, p_tm)
+    local ofs = p_tm[0].tm_gmtoff / 60 -- convert seconds to minutes
+
+    return datetime_new_raw(secs, nsec, ofs)
+end
+
+local function datetime_to_tm_ptr(o)
+    assert(is_datetime(o))
+    local p_tm = ffi.new('struct tm[1]')
+    -- dt_to_struct_tm() fills only date data
+    builtin.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)
+    check_date(o, "datetime:asctime()")
+
+    local p_tm = datetime_to_tm_ptr(o)
+    return ffi.string(builtin.asctime(p_tm))
+end
+
+local function ctime(o)
+    check_date(o, "datetime:ctime()")
+    local p_time = ffi.new('time_t[1]')
+    p_time[0] = o.secs
+    return ffi.string(builtin.ctime(p_time))
+end
+
+local function strftime(fmt, o)
+    check_date(o, "datetime.strftime()")
+    local p_tm = datetime_to_tm_ptr(o)
+    local sz = builtin.strftime(nil, 1024, fmt, p_tm) + 1
+    local buff = ffi.new('char[?]', sz)
+    builtin.strftime(buff, sz, fmt, p_tm)
+    return ffi.string(buff)
+end
+
+local datetime_mt = {
+    __serialize = datetime_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+    __newindex = datetime_newindex,
+}
+
+local interval_mt = {
+    __serialize = interval_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
+
+ffi.metatype(interval_t, interval_mt)
+ffi.metatype(datetime_t, datetime_mt)
+
+return setmetatable(
+    {
+        new         = datetime_new,
+        interval    = interval_new,
+
+        parse       = parse,
+        parse_date  = parse_date,
+        parse_time  = parse_time,
+        parse_zone  = parse_zone,
+
+        now         = local_now,
+        strftime    = strftime,
+        asctime     = asctime,
+        ctime       = ctime,
+
+        is_datetime = is_datetime,
+        is_interval = is_interval,
+    }, {
+        __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 ebc19206c..611044b6f 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -47,6 +47,9 @@ static uint32_t CTID_STRUCT_IBUF_PTR;
 uint32_t CTID_CHAR_PTR;
 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 969edca45..bf56b1bb5 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.
@@ -71,10 +72,21 @@ extern struct lua_State *tarantool_L;
 extern uint32_t CTID_CHAR_PTR;
 extern uint32_t CTID_CONST_CHAR_PTR;
 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
-- 
2.29.2



More information about the Tarantool-patches mailing list