From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id E231A741EA; Fri, 6 Aug 2021 03:26:46 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org E231A741EA DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1628209607; bh=sn/VPoC711Y6mcU/j06nntaO6jFzz6YbyVJZas2BylY=; h=To:References:Date:In-Reply-To:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=qd6Zzon/oKC8+iowBsD+kWPyX1uElmJ6amDE2egCUd+GPm8Rj1k5Oudgls9lyhDgD PhJ9I4hOoU5qMor431MM+SMTHv7VetgoF4a+CndjkCyZ828SbVUCtFkPHPqdUNlyqr 7/o2ah2m24JyqFI/FgyHXlC1T0SiA7q0CevwUSts= Received: from smtp56.i.mail.ru (smtp56.i.mail.ru [217.69.128.36]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id EAEB2741EA for ; Fri, 6 Aug 2021 03:26:45 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org EAEB2741EA Received: by smtp56.i.mail.ru with esmtpa (envelope-from ) id 1mBnhE-0000zb-2K; Fri, 06 Aug 2021 03:26:44 +0300 To: v.shpilevoy@tarantool.org, olegrok@tarantool.org References: Message-ID: <6108648c-ea70-346b-2cbc-df46c1d050b5@tarantool.org> Date: Fri, 6 Aug 2021 03:26:43 +0300 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Thunderbird/78.12.0 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8; format=flowed Content-Language: en-US Content-Transfer-Encoding: 7bit X-7564579A: B8F34718100C35BD X-77F55803: 4F1203BC0FB41BD92087353F0EC44DD9D5AC6413C25DCF08CC98B8FCC5CD86F3182A05F53808504051F9024EAB5ADED8CDF05420BDD870EADD0C604C9B7AF197EBAA674B1BB0148E X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7BC08626EA5717D14EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F790063786D6A7D810B9582D8638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D8222CB277D1663F781ABDDCEE31786F08117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BAA867293B0326636D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EECCD848CCB6FE560CBFC1D8BB4B114D1ED8FC6C240DEA7642DBF02ECDB25306B2B78CF848AE20165D0A6AB1C7CE11FEE3FA486DC37A503D0B03F1AB874ED89028C4224003CC836476EA7A3FFF5B025636E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F7900637A7EFCB0EB5ACB161EFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-B7AD71C0: AC4F5C86D027EB782CDD5689AFBDA7A213B5FB47DCBC3458F0AFF96BAACF4158235E5A14AD4A4A4625E192CAD1D9E79D94463893BF8742D07E1F596F6F2F8FE8 X-C1DE0DAB: 0D63561A33F958A5D1810C264DFF4EA44054D701C556DBADCE4409AB2906F5E7D59269BC5F550898D99A6476B3ADF6B47008B74DF8BB9EF7333BD3B22AA88B938A852937E12ACA7501A9DF589746230F410CA545F18667F91A7EA1CDA0B5A7A0 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D348CDF4129B2EA8560095A6FE249D5831F082066A45C8BA913F1BDFA894B4034FEFB1C649A0E2136351D7E09C32AA3244CD23178876C65F0BC01F71DCD7605F1AD55E75C8D0ED9F6EE83B48618A63566E0 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojFhlvmGwdUwRWpxz68zJrVg== X-Mailru-Sender: B5B6A6EBBD94DAD81091F6801AA03C01F2713A28F7F9F98CD55CFBC49CAAD1390C3A0EABECC822735C2808D6142752370A8ED71B308007E3DC85537438B7E1A423D748DE48713E689437F6177E88F7363CDA0F3B3F5B9367 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH v3 2/9] lua: built-in module datetime X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Safin Timur via Tarantool-patches Reply-To: Safin Timur Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" 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 ``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 > + * 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 > +#include > +#include > +#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); > + > + // > + typedef long __kernel_long_t; > + typedef unsigned long __kernel_ulong_t; > + // /usr/include/x86_64-linux-gnu/bits/types/time_t.h > + typedef long time_t; > + > + > + // > + typedef __kernel_long_t __kernel_time_t; > + typedef __kernel_long_t __kernel_suseconds_t; > + > + struct timespec { > + __kernel_time_t tv_sec; /* seconds */ > + long tv_nsec; /* nanoseconds */ > + }; > + > + struct timeval { > + __kernel_time_t tv_sec; /* seconds */ > + __kernel_suseconds_t tv_usec; /* microseconds */ > + }; > + > + struct timezone { > + int tz_minuteswest; /* minutes west of Greenwich */ > + int tz_dsttime; /* type of dst correction */ > + }; > + > + // /usr/include/x86_64-linux-gnu/sys/time.h > + typedef struct timezone * __timezone_ptr_t; > + > + /* Get the current time of day and timezone information, > + putting it into *TV and *TZ. If TZ is NULL, *TZ is not filled. > + Returns 0 on success, -1 on errors. > + > + NOTE: This form of timezone information is obsolete. > + Use the functions and variables declared in instead. */ > + int gettimeofday (struct timeval *__tv, struct timezone * __tz); > + > + // /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h > + /* ISO C `broken-down time' structure. */ > + struct tm > + { > + int tm_sec; /* Seconds. [0-60] (1 leap second) */ > + int tm_min; /* Minutes. [0-59] */ > + int tm_hour; /* Hours. [0-23] */ > + int tm_mday; /* Day. [1-31] */ > + int tm_mon; /* Month. [0-11] */ > + int tm_year; /* Year - 1900. */ > + int tm_wday; /* Day of week. [0-6] */ > + int tm_yday; /* Days in year.[0-365] */ > + int tm_isdst; /* DST. [-1/0/1]*/ > + > + long int tm_gmtoff; /* Seconds east of UTC. */ > + const char *tm_zone;/* Timezone abbreviation. */ > + }; > + > + // > + /* Return the current time and put it in *TIMER if TIMER is not NULL. */ > + time_t time (time_t *__timer); > + > + /* Format TP into S according to FORMAT. > + Write no more than MAXSIZE characters and return the number > + of characters written, or 0 if it would exceed MAXSIZE. */ > + size_t strftime (char * __s, size_t __maxsize, const char * __format, > + const struct tm * __tp); > + > + /* Parse S according to FORMAT and store binary time information in TP. > + The return value is a pointer to the first unparsed character in S. */ > + char *strptime (const char * __s, const char * __fmt, struct tm *__tp); > + > + /* Return the `struct tm' representation of *TIMER in UTC, > + using *TP to store the result. */ > + struct tm *gmtime_r (const time_t * __timer, struct tm * __tp); > + > + /* Return the `struct tm' representation of *TIMER in local time, > + using *TP to store the result. */ > + struct tm *localtime_r (const time_t * __timer, struct tm * __tp); > + > + /* Return a string of the form "Day Mon dd hh:mm:ss yyyy\n" > + that is the representation of TP in this format. */ > + char *asctime (const struct tm *__tp); > + > + /* Equivalent to `asctime (localtime (timer))'. */ > + char *ctime (const time_t *__timer); > + > +]] > + > +local 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 > +#include > +#include > +#include > + > +#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 >