[Tarantool-patches] [PATCH v3 2/9] lua: built-in module datetime
Safin Timur
tsafin at tarantool.org
Fri Aug 6 03:26:43 MSK 2021
These files for unit-test have been moved to the patch 1/9 build: add
Christian Hansen c-dt to the build
> test/unit/CMakeLists.txt | 2 +
> test/unit/datetime.c | 221 +++++++++++
> test/unit/datetime.result | 358 ++++++++++++++++++
On 02.08.2021 3:40, Timur Safin 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 | 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
>
More information about the Tarantool-patches
mailing list