* [Tarantool-patches] [PATCH 1/n] build, lua: built-in module datetime
2021-09-10 17:50 [Tarantool-patches] [PATCH 0/n] Datetime module implementation, stage #1 Timur Safin via Tarantool-patches
@ 2021-09-10 17:50 ` Timur Safin via Tarantool-patches
2021-09-14 21:53 ` Safin Timur via Tarantool-patches
2021-09-14 22:45 ` [Tarantool-patches] [PATCH 0/n] Datetime module implementation, stage #1 Vladislav Shpilevoy via Tarantool-patches
1 sibling, 1 reply; 4+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-09-10 17:50 UTC (permalink / raw)
To: v.shpilevoy; +Cc: tarantool-patches
Introduce new builtin Tarantool module `datetime.lua` with
partial proxying to `c-dt` library for datetime parsing and
arithmetics.
New third_party module - c-dt
-----------------------------
* Integrated chansen/c-dt parser as 3rd party module to the
Tarantool cmake build process;
* Points to tarantool/c-dt instead of original chansen/c-dt to
have easier build integration, because there are additional
changes, which have provided cmake support and have established
symbols renaming facilities (similar to those we see in xxhash
or icu).
* took special care that generated build artifacts not override
in-source files, but use different build/ directory.
New built-in module `datetime`
------------------------------
* created a new Tarantool built-in module `datetime`;
* register new cdef types for this module;
* reexported some `dt_*` functions from `c-dt` library, but using
prefix `tnt_dt_*` to avoid possible name clashes.
* `strftime` implemented as a simple ffi wrappers in the Tarantool
kernel code.
**TODO** to setablish %f format which we want for nanoseconds
display we plan to slightly modify Olson strftime implementation
and thus bundle it with kernel code. Not today, though.
* display datetime
- introduced output routine for converting datetime
to their default output (ISO-8601) format. This routine used
by tostring() if given datatime object;
* simplified interfaces
- totable() export table values similar to os.date('*t')
- set() provide unified interface to set values using
the same set of attributes as in totable()
Example,
```
local dt = datetime.new {
nsec = 123456789,
usec = 123456,
msec = 123,
sec = 19,
min = 29,
hour = 18,
day = 20,
month = 8,
year = 2021,
timestamp = 1629476485.123,
tzoffset = 180,
}
local t = dt:totable()
--[[
t.nsec
t.sec
t.min
t.hour
t.day
t.month
t.year
t.isdst
t.wday
t.yday
--]]
dt:format() -- 2021-08-21T14:53:34.032Z
dt:format('%Y-%m-%dT%H:%M:%S') -- 2021-08-21T14:53:34
dt:set {
nsec = 123456789,
usec = 123456,
msec = 123,
sec = 19,
min = 29,
hour = 18,
day = 20,
month = 8,
year = 2021,
tzoffset = 180,
}
dt:set {
timestamp = 1629476485.124,
tzoffset = 180,
}
```
Coverage is
builtin/datetime.lua 238 43 84.70%
Part of #5941
@TarantoolBot document
Title: Introduced new built-in `datetime` module
`datetime` module has been introduced, which allows to create
and modify timestamp objects.
Please refer to https://hackmd.io/@Mons/S1Vfc_axK#Datetime-in-Tarantool
for more detailed description of module API.
Part of #5941
---
.gitmodules | 3 +
CMakeLists.txt | 8 +
cmake/BuildCDT.cmake | 10 +
| 37 ++
src/CMakeLists.txt | 5 +-
src/lib/core/CMakeLists.txt | 1 +
src/lib/core/datetime.c | 121 +++++++
src/lib/core/datetime.h | 94 +++++
src/lua/datetime.lua | 623 +++++++++++++++++++++++++++++++++
src/lua/init.c | 4 +-
src/lua/utils.c | 19 +
src/lua/utils.h | 11 +
test/app-tap/datetime.test.lua | 203 +++++++++++
test/unit/CMakeLists.txt | 2 +
test/unit/datetime.c | 261 ++++++++++++++
test/unit/datetime.result | 358 +++++++++++++++++++
third_party/c-dt | 1 +
17 files changed, 1759 insertions(+), 2 deletions(-)
create mode 100644 cmake/BuildCDT.cmake
create mode 100644 src/lib/core/datetime.c
create mode 100644 src/lib/core/datetime.h
create mode 100644 src/lua/datetime.lua
create mode 100755 test/app-tap/datetime.test.lua
create mode 100644 test/unit/datetime.c
create mode 100644 test/unit/datetime.result
create mode 160000 third_party/c-dt
diff --git a/.gitmodules b/.gitmodules
index f2f91ee72..aa3fbae4e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -43,3 +43,6 @@
[submodule "third_party/xxHash"]
path = third_party/xxHash
url = https://github.com/tarantool/xxHash
+[submodule "third_party/c-dt"]
+ path = third_party/c-dt
+ url = https://github.com/tarantool/c-dt.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e25b81eac..8037c30a7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -571,6 +571,14 @@ endif()
# zstd
#
+#
+# Christian Hansen c-dt
+#
+
+include(BuildCDT)
+libccdt_build()
+add_dependencies(build_bundled_libs cdt)
+
#
# Third-Party misc
#
diff --git a/cmake/BuildCDT.cmake b/cmake/BuildCDT.cmake
new file mode 100644
index 000000000..80b26c64a
--- /dev/null
+++ b/cmake/BuildCDT.cmake
@@ -0,0 +1,10 @@
+macro(libccdt_build)
+ set(LIBCDT_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/third_party/c-dt/)
+ set(LIBCDT_LIBRARIES cdt)
+
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/third_party/c-dt/build/)
+ add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/c-dt
+ ${CMAKE_CURRENT_BINARY_DIR}/third_party/c-dt/build/)
+ set_target_properties(cdt PROPERTIES COMPILE_FLAGS "-DDT_NAMESPACE=tnt_")
+ add_definitions("-DDT_NAMESPACE=tnt_")
+endmacro()
--git a/extra/exports b/extra/exports
index 9eaba1282..1416a6b3f 100644
--- a/extra/exports
+++ b/extra/exports
@@ -148,6 +148,10 @@ csv_feed
csv_iterator_create
csv_next
csv_setopt
+datetime_now
+datetime_strftime
+datetime_to_string
+datetime_unpack
decimal_from_string
decimal_unpack
error_ref
@@ -447,6 +451,39 @@ title_set_status
title_update
tnt_default_cert_dir_paths
tnt_default_cert_file_paths
+tnt_dt_add_months
+tnt_dt_add_quarters
+tnt_dt_add_years
+tnt_dt_days_in_month
+tnt_dt_days_in_quarter
+tnt_dt_days_in_year
+tnt_dt_dom
+tnt_dt_dow
+tnt_dt_doy
+tnt_dt_from_rdn
+tnt_dt_from_struct_tm
+tnt_dt_from_yd
+tnt_dt_from_ymd
+tnt_dt_from_yqd
+tnt_dt_from_ywd
+tnt_dt_leap_year
+tnt_dt_month
+tnt_dt_parse_iso_date
+tnt_dt_parse_iso_time
+tnt_dt_parse_iso_time_basic
+tnt_dt_parse_iso_time_extended
+tnt_dt_parse_iso_zone
+tnt_dt_parse_iso_zone_basic
+tnt_dt_parse_iso_zone_extended
+tnt_dt_parse_iso_zone_lenient
+tnt_dt_rdn
+tnt_dt_to_struct_tm
+tnt_dt_to_yd
+tnt_dt_to_ymd
+tnt_dt_to_yqd
+tnt_dt_to_ywd
+tnt_dt_weeks_in_year
+tnt_dt_year
tnt_iconv
tnt_iconv_close
tnt_iconv_open
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index adb03b3f4..4473ff1da 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)
@@ -193,7 +195,8 @@ target_link_libraries(server core coll http_parser bit uri uuid swim swim_udp
# Rule of thumb: if exporting a symbol from a static library, list the
# library here.
set (reexport_libraries server core misc bitset csv swim swim_udp swim_ev
- shutdown ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} ${CURL_LIBRARIES} ${XXHASH_LIBRARIES})
+ shutdown ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES}
+ ${CURL_LIBRARIES} ${XXHASH_LIBRARIES} ${LIBCDT_LIBRARIES})
set (common_libraries
${reexport_libraries}
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 2cd4d0b4f..8bc776b82 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -30,6 +30,7 @@ set(core_sources
decimal.c
mp_decimal.c
cord_buf.c
+ datetime.c
)
if (TARGET_OS_NETBSD)
diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c
new file mode 100644
index 000000000..4f2b59bd8
--- /dev/null
+++ b/src/lib/core/datetime.c
@@ -0,0 +1,121 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <string.h>
+#include <time.h>
+
+#include "trivia/util.h"
+#include "datetime.h"
+
+/*
+ * Given the seconds from Epoch (1970-01-01) we calculate date
+ * since Rata Die (0001-01-01).
+ * DT_EPOCH_1970_OFFSET is the distance in days from Rata Die to Epoch.
+ */
+static int
+local_dt(int64_t secs)
+{
+ return dt_from_rdn((int)(secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET);
+}
+
+static struct tm *
+datetime_to_tm(const struct datetime *date)
+{
+ static struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ int64_t secs = date->epoch;
+ dt_to_struct_tm(local_dt(secs), &tm);
+
+ int seconds_of_day = (int64_t)date->epoch % SECS_PER_DAY;
+ 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;
+}
+
+void
+datetime_now(struct datetime *now)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ now->epoch = tv.tv_sec;
+ now->nsec = tv.tv_usec * 1000;
+
+ time_t now_seconds;
+ time(&now_seconds);
+ struct tm tm;
+ localtime_r(&now_seconds, &tm);
+ now->tzoffset = tm.tm_gmtoff / 60;
+}
+
+size_t
+datetime_strftime(char *buf, uint32_t len, const char *fmt,
+ const struct datetime *date)
+{
+ struct tm *p_tm = datetime_to_tm(date);
+ return strftime(buf, len, fmt, p_tm);
+}
+
+
+/* NB! buf may be NULL, and we should handle it gracefully, returning
+ * calculated length of output string
+ */
+int
+datetime_to_string(char *buf, int len, const struct datetime *date)
+{
+ int offset = date->tzoffset;
+ /* for negative offsets around Epoch date we could get
+ * negative secs value, which should be attributed to
+ * 1969-12-31, not 1970-01-01, thus we first shift
+ * epoch to Rata Die then divide by seconds per day,
+ * not in reverse
+ */
+ int64_t rd_seconds = (int64_t)date->epoch + offset * 60 +
+ SECS_EPOCH_1970_OFFSET;
+ int rd_number = rd_seconds / SECS_PER_DAY;
+ assert(rd_number <= INT_MAX);
+ assert(rd_number >= INT_MIN);
+ dt_t dt = dt_from_rdn(rd_number);
+
+ int year, month, day, second, nanosec, sign;
+ dt_to_ymd(dt, &year, &month, &day);
+
+ int hour = (rd_seconds / 3600) % 24;
+ int minute = (rd_seconds / 60) % 60;
+ second = rd_seconds % 60;
+ nanosec = date->nsec;
+
+ int sz = 0;
+ SNPRINT(sz, snprintf, buf, len, "%04d-%02d-%02dT%02d:%02d:%02d",
+ year, month, day, hour, minute, second);
+ if (nanosec != 0) {
+ if ((nanosec % 1000000) == 0)
+ SNPRINT(sz, snprintf, buf, len, ".%03d",
+ nanosec / 1000000);
+ else if ((nanosec % 1000) == 0)
+ SNPRINT(sz, snprintf, buf, len, ".%06d",
+ nanosec / 1000);
+ else
+ SNPRINT(sz, snprintf, buf, len, ".%09d", nanosec);
+ }
+ if (offset == 0) {
+ SNPRINT(sz, snprintf, buf, len, "Z");
+ } else {
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ } else {
+ sign = '+';
+ }
+ SNPRINT(sz, snprintf, buf, len, "%c%02d:%02d", sign,
+ offset / 60, offset % 60);
+ }
+ return sz;
+}
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
new file mode 100644
index 000000000..7fa2c14a6
--- /dev/null
+++ b/src/lib/core/datetime.h
@@ -0,0 +1,94 @@
+#pragma once
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "c-dt/dt.h"
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif /* defined(__cplusplus) */
+
+/**
+ * We count dates since so called "Rata Die" date
+ * January 1, 0001, Monday (as Day 1).
+ * But datetime structure keeps seconds since
+ * Unix "Epoch" date:
+ * Unix, January 1, 1970, Thursday
+ *
+ * The difference between Epoch (1970-01-01)
+ * and Rata Die (0001-01-01) is 719163 days.
+ */
+
+#ifndef SECS_PER_DAY
+#define SECS_PER_DAY 86400
+#define DT_EPOCH_1970_OFFSET 719163
+#endif
+
+#define SECS_EPOCH_1970_OFFSET \
+ ((int64_t)DT_EPOCH_1970_OFFSET * SECS_PER_DAY)
+/**
+ * datetime structure keeps number of seconds since
+ * Unix Epoch.
+ * Time is normalized by UTC, so time-zone offset
+ * is informative only.
+ */
+struct datetime {
+ /** Seconds since Epoch. */
+ double epoch;
+ /** Nanoseconds, if any. */
+ int32_t nsec;
+ /** Offset in minutes from UTC. */
+ int16_t tzoffset;
+ /** Olson timezone id */
+ int16_t tzindex;
+};
+
+/**
+ * Required size of datetime_to_string string buffer
+ */
+#define DT_TO_STRING_BUFSIZE 48
+
+/*
+ * Compare arguments of a datetime type
+ * @param lhs left datetime argument
+ * @param rhs right datetime argument
+ * @retval < 0 if lhs less than rhs
+ * @retval = 0 if lhs and rhs equal
+ * @retval > 0 if lhs greater than rhs
+ */
+int
+datetime_compare(const struct datetime *lhs, const struct datetime *rhs);
+
+/**
+ * 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(char *buf, int len, const struct datetime *date);
+
+/**
+ * Convert datetime to string using default format provided
+ * Wrapper around standard strftime() function
+ * @param date source datetime value
+ * @param fmt format
+ * @param buf output buffer
+ * @param len size of output buffer
+ */
+size_t
+datetime_strftime(char *buf, uint32_t len, const char *fmt,
+ const struct datetime *date);
+
+void
+datetime_now(struct datetime *now);
+
+#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..f63386716
--- /dev/null
+++ b/src/lua/datetime.lua
@@ -0,0 +1,623 @@
+local ffi = require('ffi')
+
+--[[
+ `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.
+]]
+
+ffi.cdef [[
+
+/* dt_core.h definitions */
+typedef int dt_t;
+
+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 tnt_dt_from_rdn (int n);
+dt_t tnt_dt_from_ymd (int y, int m, int d);
+void tnt_dt_to_ymd (dt_t dt, int *y, int *m, int *d);
+void tnt_dt_to_yqd (dt_t dt, int *y, int *q, int *d);
+void tnt_dt_to_ywd (dt_t dt, int *y, int *w, int *d);
+
+int tnt_dt_rdn (dt_t dt);
+dt_dow_t tnt_dt_dow (dt_t dt);
+
+/* dt_util.h */
+bool tnt_dt_leap_year (int y);
+int tnt_dt_days_in_year (int y);
+int tnt_dt_days_in_quarter (int y, int q);
+int tnt_dt_days_in_month (int y, int m);
+int tnt_dt_weeks_in_year (int y);
+
+/* dt_accessor.h */
+
+int tnt_dt_year (dt_t dt);
+int tnt_dt_month (dt_t dt);
+int tnt_dt_doy (dt_t dt);
+int tnt_dt_dom (dt_t dt);
+
+/* dt_arithmetic.h definitions */
+
+typedef enum {
+ DT_EXCESS,
+ DT_LIMIT,
+ DT_SNAP
+} dt_adjust_t;
+
+dt_t tnt_dt_add_years (dt_t dt, int delta, dt_adjust_t adjust);
+dt_t tnt_dt_add_quarters (dt_t dt, int delta, dt_adjust_t adjust);
+dt_t tnt_dt_add_months (dt_t dt, int delta, dt_adjust_t adjust);
+
+/* dt_parse_iso.h definitions */
+
+size_t tnt_dt_parse_iso_date (const char *str, size_t len, dt_t *dt);
+size_t tnt_dt_parse_iso_time (const char *str, size_t len, int *sod, int *nsec);
+size_t tnt_dt_parse_iso_zone_lenient(const char *str, size_t len, int *offset);
+
+/* Tarantool functions - datetime.c */
+
+int datetime_to_string(char *buf, int len, const struct datetime * date);
+size_t datetime_strftime(char *buf, uint32_t len, const char *fmt,
+ const struct datetime *date);
+void datetime_now(struct datetime *now);
+
+]]
+
+local builtin = ffi.C
+local math_modf = math.modf
+local math_floor = math.floor
+
+local SECS_PER_DAY = 86400
+local NANOS_PER_SEC = 1000000000
+
+-- c-dt/dt_config.h
+
+-- Unix, January 1, 1970, Thursday
+local DT_EPOCH_1970_OFFSET = 719163
+
+
+local datetime_t = ffi.typeof('struct datetime')
+
+local function is_datetime(o)
+ return ffi.istype(datetime_t, o)
+end
+
+local function check_date(o, message)
+ if not is_datetime(o) then
+ return error(("%s: expected datetime, but received %s"):
+ format(message, o), 2)
+ end
+end
+
+local function check_table(o, message)
+ if type(o) ~= 'table' then
+ return error(("%s: expected table %s"):format(message, o), 2)
+ end
+end
+
+local function check_str(s, message)
+ if not type(s) == 'string' then
+ return error(("%s: expected string, but received %s"):
+ format(message, s), 2)
+ end
+end
+
+-- range may be of a form of pair {begin, end} or
+-- tuple {begin, end, negative}
+-- negative is a special value (so far) used for days only
+local function check_range(v, range, txt)
+ local len = #range
+ assert(len == 2 or len == 3)
+
+ local left, right, neg = unpack(range)
+ if neg == v or (v >= left and v <= right) then
+ return
+ end
+
+ if neg == nil then
+ error(('value %d of %s is out of allowed range [%d, %d]'):
+ format(v, txt, left, right), 2)
+ else
+ error(('value %d of %s is out of allowed range [%d, %d..%d]'):
+ format(v, txt, neg, left, right), 2)
+ end
+end
+
+local function nyi(msg)
+ local text = 'Not yet implemented'
+ if msg ~= nil then
+ text = ("%s : '%s'"):format(text, msg)
+ end
+ error(text, 3)
+end
+
+-- offset if __seconds__ of 1970-01-01 from 0000-01-01
+local SECS_EPOCH_OFFSET = (DT_EPOCH_1970_OFFSET * SECS_PER_DAY)
+
+-- convert from epoch related time to Rata Die related
+local function local_rd(secs)
+ return math_floor((secs + SECS_EPOCH_OFFSET) / SECS_PER_DAY)
+end
+
+-- convert UTC seconds to local seconds, adjusting by timezone
+local function local_secs(obj)
+ return obj.epoch + obj.tzoffset * 60
+end
+
+local function utc_secs(epoch, tzoffset)
+ return epoch - tzoffset * 60
+end
+
+-- get epoch seconds, shift to the local timezone
+-- adjust from 1970-related to 0000-related time
+-- then return dt in those coordinates (number of days
+-- since Rata Die date)
+local function local_dt(obj)
+ return builtin.tnt_dt_from_rdn(local_rd(local_secs(obj)))
+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 datetime_cmp(lhs, rhs)
+ if not is_datetime(lhs) or not is_datetime(rhs) then
+ return nil
+ end
+ local sdiff = lhs.epoch - rhs.epoch
+ 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
+end
+
+local function datetime_lt(lhs, rhs)
+ local rc = datetime_cmp(lhs, rhs)
+ return rc == nil and error('incompatible types for comparison', 2) or
+ rc < 0
+end
+
+local function datetime_le(lhs, rhs)
+ local rc = datetime_cmp(lhs, rhs)
+ return rc == nil and error('incompatible types for comparison', 2) or
+ rc <= 0
+end
+
+local function datetime_serialize(self)
+ return { epoch = self.epoch, nsec = self.nsec,
+ tzoffset = self.tzoffset, tzindex = 0 }
+end
+
+local parse_zone
+
+local function datetime_new_raw(epoch, nsec, tzoffset, tzindex)
+ local dt_obj = ffi.new(datetime_t)
+ dt_obj.epoch = epoch
+ dt_obj.nsec = nsec
+ dt_obj.tzoffset = tzoffset or 0
+ dt_obj.tzindex = tzindex or 0
+ return dt_obj
+end
+
+local function datetime_new_dt(dt, secs, fraction, offset)
+ local epochV = dt ~= nil and (builtin.tnt_dt_rdn(dt) - DT_EPOCH_1970_OFFSET) *
+ SECS_PER_DAY or 0
+ local secsV = secs or 0
+ local fracV = fraction or 0
+ local ofsV = offset or 0
+ return datetime_new_raw(epochV + secsV - ofsV * 60, fracV, ofsV)
+end
+
+local function get_timezone(offset)
+ if type(offset) == 'number' then
+ return offset
+ elseif type(offset) == 'string' then
+ return parse_zone(offset)
+ end
+end
+
+-- create datetime given attribute values from obj
+local function datetime_new(obj)
+ if obj == nil or type(obj) ~= 'table' then
+ return datetime_new_raw(0, 0, 0)
+ end
+
+ local ymd = false
+ local hms = false
+ local dt = DT_EPOCH_1970_OFFSET -- default is 1970-01-01
+
+ local y = obj.year
+ if y ~= nil then
+ check_range(y, {1, 9999}, 'year')
+ ymd = true
+ end
+ local M = obj.month
+ if M ~= nil then
+ check_range(M, {1, 12}, 'month')
+ ymd = true
+ end
+ local d = obj.day
+ if d ~= nil then
+ check_range(d, {1, 31, -1}, 'day')
+ ymd = true
+ end
+ local h = obj.hour
+ if h ~= nil then
+ check_range(h, {0, 23}, 'hour')
+ hms = true
+ end
+ local m = obj.min
+ if m ~= nil then
+ check_range(m, {0, 59}, 'min')
+ hms = true
+ end
+ local s = obj.sec
+ if s ~= nil then
+ check_range(s, {0, 60}, 'sec')
+ hms = true
+ end
+ local nsec, usec, msec = obj.nsec, obj.usec, obj.msec
+ -- if there are separate nsec, usec, or msec provided then
+ -- timestamp should be integer
+ local int_ts = nsec ~= nil or usec ~= nil or msec ~= nil
+
+ local ts = obj.timestamp
+ local fraction
+ if ts ~= nil then
+ s, fraction = math_modf(ts)
+ if not int_ts then
+ nsec = fraction * 1e9
+ end
+ hms = true
+ end
+
+ local offset = obj.tzoffset
+ if offset ~= nil then
+ offset = get_timezone(offset)
+ check_range(offset, {-720, 720}, offset)
+ end
+
+ if obj.tz ~= nil then
+ nyi('tz')
+ end
+
+ -- .year, .month, .day
+ if ymd then
+ y = y or 1970
+ M = M or 1
+ if d ~= nil and d < 0 then
+ d = builtin.tnt_dt_days_in_month(y, M)
+ end
+ dt = builtin.tnt_dt_from_ymd(y, M, d)
+ end
+
+ -- .hour, .minute, .second
+ local secs = 0
+ if hms then
+ secs = (h or 0) * 3600 + (m or 0) * 60 + (s or 0)
+ end
+
+ return datetime_new_dt(dt, secs, nsec, offset or 0)
+end
+
+local sz = 48
+local buff = ffi.new('char[?]', sz)
+
+--[[
+ Convert to text datetime values
+
+ - datetime will use ISO-8601 format:
+ 1970-01-01T00:00Z
+ 2021-08-18T16:57:08.981725+03:00
+]]
+local function datetime_tostring(self)
+ check_date(self, 'datetime.tostring()')
+
+ local len = builtin.datetime_to_string(buff, sz, self)
+ assert(len < sz)
+ return ffi.string(buff)
+end
+
+--[[
+ Basic Extended
+ Z N/A
+ +hh N/A
+ -hh N/A
+ +hhmm +hh:mm
+ -hhmm -hh:mm
+
+ Returns pair of constructed datetime object, and length of string
+ which has been accepted by parser.
+]]
+parse_zone = function(str)
+ check_str("datetime.parse_zone()")
+ local offset = ffi.new('int[1]')
+ local len = builtin.tnt_dt_parse_iso_zone_lenient(str, #str, offset)
+ if len == 0 then
+ error(('invalid time-zone format %s'):format(str), 3)
+ end
+ return offset[0]
+end
+
+--[[
+ Dispatch function to create datetime from string or table.
+ Creates default timeobject (pointing to Epoch date) if
+ called without arguments.
+]]
+local function datetime_from(o)
+ if o == nil or type(o) == 'table' then
+ return datetime_new(o)
+ end
+end
+
+--[[
+ Create datetime object representing current time using microseconds
+ platform timer and local timezone information.
+]]
+local function local_now()
+ local d = datetime_new_raw(0, 0, 0)
+ builtin.datetime_now(d)
+ return d
+end
+
+--[[
+ Return table in os.date('*t') format, but with timezone
+ and nanoseconds
+]]
+local function datetime_totable(self)
+ local secs = local_secs(self) -- hour:minute should be in local timezone
+ local dt = local_dt(self)
+
+ return {
+ year = builtin.tnt_dt_year(dt),
+ month = builtin.tnt_dt_month(dt),
+ yday = builtin.tnt_dt_doy(dt),
+ day = builtin.tnt_dt_dom(dt),
+ wday = (ffi.cast('int32_t', builtin.tnt_dt_dow(dt)) + 1) % 7,
+ hour = math_floor((secs / 3600) % 24),
+ min = math_floor((secs / 60) % 60),
+ sec = secs % 60,
+ isdst = false, -- FIXME - after we introduced timezone/DST support
+ nsec = self.nsec,
+ tzoffset = self.tzoffset,
+ -- tz = "MSK", -- FIXME - after we introduced timezone support
+ }
+end
+
+local function datetime_update_dt(self, dt, new_offset)
+ local epoch = local_secs(self)
+ local secs_day = epoch % SECS_PER_DAY
+ epoch = (builtin.tnt_dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY + secs_day
+ self.epoch = utc_secs(epoch, new_offset)
+end
+
+local function datetime_ymd_update(self, y, M, d, new_offset)
+ if d < 0 then
+ d = builtin.tnt_dt_days_in_month(y, M)
+ end
+ if d > 28 then
+ local day_in_month = builtin.tnt_dt_days_in_month(y, M)
+ if d > day_in_month then
+ error(('invalid number of days %d in month %d for %d'):
+ format(d, M, y), 3)
+ end
+ end
+ local dt = builtin.tnt_dt_from_ymd(y or 0, M or 1, d or 1)
+ datetime_update_dt(self, dt, new_offset)
+end
+
+local function datetime_hms_update(self, h, m, s, new_offset)
+ local epoch = local_secs(self)
+ local secs_day = epoch - (epoch % SECS_PER_DAY)
+ self.epoch = utc_secs(secs_day + h * 3600 + m * 60 + s, new_offset)
+end
+
+local function bool2int(b)
+ return b and 1 or 0
+end
+
+local function datetime_set(self, obj)
+ check_table(obj, "datetime.set()")
+
+ local ymd = false
+ local hms = false
+
+ local dt = local_dt(self)
+ local y0 = ffi.new('int[1]')
+ local M0 = ffi.new('int[1]')
+ local d0 = ffi.new('int[1]')
+ builtin.tnt_dt_to_ymd(dt, y0, M0, d0)
+ y0, M0, d0 = y0[0], M0[0], d0[0]
+
+ local y = obj.year
+ if y ~= nil then
+ check_range(y, {1, 9999}, 'year')
+ ymd = true
+ end
+ local M = obj.month
+ if M ~= nil then
+ check_range(M, {1, 12}, 'month')
+ ymd = true
+ end
+ local d = obj.day
+ if d ~= nil then
+ check_range(d, {1, 31, -1}, 'day')
+ ymd = true
+ end
+
+ local lsecs = local_secs(self)
+ local h0 = math_floor(lsecs / (24 * 60)) % 24
+ local m0 = math_floor(lsecs / 60) % 60
+ local sec0 = lsecs % 60
+
+ local h = obj.hour
+ if h ~= nil then
+ check_range(h, {0, 23}, 'hour')
+ hms = true
+ end
+ local m = obj.min
+ if m ~= nil then
+ check_range(m, {0, 59}, 'min')
+ hms = true
+ end
+ local sec = obj.sec
+ if sec ~= nil then
+ check_range(sec, {0, 60}, 'sec')
+ hms = true
+ end
+
+ local nsec, usec, msec = obj.nsec, obj.usec, obj.msec
+ local count_usec = bool2int(nsec ~= nil) + bool2int(usec ~= nil) +
+ bool2int(msec ~= nil)
+ if count_usec > 1 then
+ error('only one of nsec, usec or msecs may defined simultaneously', 2)
+ end
+ if usec ~= nil then
+ nsec = usec * 1e3
+ elseif msec ~= nil then
+ nsec = msec * 1e6
+ end
+
+ local ts = obj.timestamp
+ if ts ~= nil then
+ local sec_int, fraction
+ sec_int, fraction = math_modf(ts)
+ -- if there is one of nsec, usec, msec provided
+ -- then ignore fraction in timestamp
+ -- otherwise - use nsec, usec, or msec
+ if count_usec == 0 then
+ nsec = fraction * 1e9
+ end
+
+ self.secs = sec_int
+ self.nsec = nsec
+
+ return self
+ end
+
+ local offset0 = self.tzoffset
+ local offset = obj.tzoffset
+ if offset ~= nil then
+ offset = get_timezone(offset)
+ check_range(offset, {-720, 720}, 'tzoffset')
+ -- self.tzoffset = offset
+ end
+
+ if obj.tz ~= nil then
+ nyi('tz')
+ end
+
+ -- .year, .month, .day
+ if ymd then
+ datetime_ymd_update(self, y or y0, M or M0, d or d0, offset or offset0)
+ end
+
+ -- .hour, .minute, .second
+ if hms then
+ datetime_hms_update(self, h or h0, m or m0, sec or sec0, offset or offset0)
+ end
+
+ self.epoch, self.nsec = normalize_nsec(self.epoch, self.nsec)
+
+ if offset ~= nil then
+ self.tzoffset = offset
+ end
+
+ return self
+end
+
+local strfmt_sz = 128
+local strfmt_buff = ffi.new('char[?]', strfmt_sz)
+
+local function datetime_strftime(self, fmt)
+ check_date(self, "datetime.strftime()")
+ builtin.datetime_strftime(strfmt_buff, strfmt_sz, fmt, self)
+ return ffi.string(strfmt_buff)
+end
+
+local function datetime_format(self, fmt)
+ if fmt ~= nil then
+ return datetime_strftime(self, fmt)
+ else
+ return datetime_tostring(self)
+ end
+end
+
+ffi.metatype(datetime_t, {
+ __tostring = datetime_tostring,
+ __serialize = datetime_serialize,
+ __eq = datetime_eq,
+ __lt = datetime_lt,
+ __le = datetime_le,
+ __index = {
+ epoch = function(self) return self.epoch end,
+ timestamp = function(self) return self.epoch + self.nsec / 1e9 end,
+
+ nsec = function(self) return self.nsec end,
+ usec = function(self) return self.nsec / 1e3 end,
+ msec = function(self) return self.nsec / 1e6 end,
+
+ dt = function(self) return local_dt(self) end,
+ year = function(self) return builtin.tnt_dt_year(local_dt(self)) end,
+ month = function(self) return builtin.tnt_dt_month(local_dt(self)) end,
+
+ yday = function(self) return builtin.tnt_dt_doy(local_dt(self)) end,
+ wday = function(self)
+ return ffi.cast('int32_t', builtin.tnt_dt_dow(local_dt(self)))
+ end,
+ day = function(self)
+ return builtin.tnt_dt_dom(local_dt(self))
+ end,
+ hour = function(self) return math_floor((local_secs(self) / 3600) % 24) end,
+ minute = function(self) return math_floor((local_secs(self) / 60) % 60) end,
+ second = function(self) return self.epoch % 60 end,
+
+ format = datetime_format,
+ totable = datetime_totable,
+ set = datetime_set,
+ }
+})
+
+return setmetatable(
+ {
+ new = datetime_new,
+
+ now = local_now,
+
+ is_datetime = is_datetime,
+ }, {
+ __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 c71cd4857..b25656a1a 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -48,6 +48,8 @@ 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;
+
void *
luaL_pushcdata(struct lua_State *L, uint32_t ctypeid)
@@ -120,6 +122,12 @@ luaL_pushuuidstr(struct lua_State *L, const struct tt_uuid *uuid)
lua_pushlstring(L, str, UUID_STR_LEN);
}
+struct datetime *
+luaL_pushdatetime(struct lua_State *L)
+{
+ return luaL_pushcdata(L, CTID_DATETIME);
+}
+
int
luaL_iscdata(struct lua_State *L, int idx)
{
@@ -725,6 +733,17 @@ 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 {"
+ "double epoch;"
+ "int32_t nsec;"
+ "int16_t tzoffset;"
+ "int16_t tzindex;"
+ "};");
+ assert(rc == 0);
+ (void) rc;
+ CTID_DATETIME = luaL_ctypeid(L, "struct datetime");
+ assert(CTID_DATETIME != 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 45070b778..9c08947cc 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;
/**
* Single global lua_State shared by core and modules.
@@ -71,6 +72,7 @@ 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;
struct tt_uuid *
luaL_pushuuid(struct lua_State *L);
@@ -78,6 +80,15 @@ luaL_pushuuid(struct lua_State *L);
void
luaL_pushuuidstr(struct lua_State *L, const struct tt_uuid *uuid);
+/**
+ * @brief Push cdata of a datetime type onto the stack.
+ * @param L Lua State
+ * @sa luaL_pushcdata
+ * @return memory associated with this datetime data
+ */
+struct datetime *
+luaL_pushdatetime(struct lua_State *L);
+
/** \cond public */
/**
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
new file mode 100755
index 000000000..4740c800d
--- /dev/null
+++ b/test/app-tap/datetime.test.lua
@@ -0,0 +1,203 @@
+#!/usr/bin/env tarantool
+
+local tap = require('tap')
+local test = tap.test("errno")
+local date = require('datetime')
+
+test:plan(7)
+
+local function assert_raises(test, error_msg, func, ...)
+ local ok, err = pcall(func, ...)
+ local err_tail = err:gsub("^.+:%d+: ", "")
+ return test:ok(not ok and err_tail == error_msg,
+ ('"%s" received, "%s" expected'):format(err_tail, error_msg))
+end
+
+test:test("Default date creation", function(test)
+ test:plan(9)
+ -- check empty arguments
+ local T1 = date.new()
+ test:is(T1.epoch, 0, "T.epoch ==0")
+ test:is(T1.nsec, 0, "T.nsec == 0")
+ test:is(T1.tzoffset, 0, "T.tzoffset == 0")
+ test:is(tostring(T1), "1970-01-01T00:00:00Z", "tostring(T1)")
+ -- check empty table
+ local T2 = date.new{}
+ test:is(T2.epoch, 0, "T.epoch ==0")
+ test:is(T2.nsec, 0, "T.nsec == 0")
+ test:is(T2.tzoffset, 0, "T.tzoffset == 0")
+ test:is(tostring(T2), "1970-01-01T00:00:00Z", "tostring(T2)")
+ -- check their equivalence
+ test:is(T1, T2, "T1 == T2")
+end)
+
+test:test("Datetime string formatting", function(test)
+ test:plan(6)
+ local t = date()
+ test:is(t.epoch, 0, ('t.epoch == %d'):format(tonumber(t.epoch)))
+ test:is(t.nsec, 0, ('t.nsec == %d'):format(t.nsec))
+ test:is(t.tzoffset, 0, ('t.tzoffset == %d'):format(t.tzoffset))
+ test:is(t:format('%d/%m/%Y'), '01/01/1970', '%s: format #1')
+ test:is(t:format('%A %d. %B %Y'), 'Thursday 01. January 1970', 'format #2')
+ test:is(t:format('%FT%T%z'), '1970-01-01T00:00:00+0000', 'format #3')
+end)
+
+test:test("totable{}", function(test)
+ test:plan(9)
+ local exp = {sec = 0, min = 0, wday = 5, day = 1,
+ nsec = 0, isdst = false, yday = 1,
+ tzoffset = 0, month = 1, year = 1970, hour = 0}
+ local T = date.new()
+ local TT = T:totable()
+ test:is_deeply(TT, exp, "date:totable()")
+
+ local D = os.date('*t')
+ TT = date.new(D):totable()
+ local keys = {
+ 'sec', 'min', 'wday', 'day', 'yday', 'month', 'year', 'hour'
+ }
+ for _, key in pairs(keys) do
+ test:is(TT[key], D[key], ("[%s]: %s == %s"):format(key, TT[key], D[key]))
+ end
+end)
+
+test:test("Time :set{} operations", function(test)
+ test:plan(8)
+
+ local T = date.new{ year = 2021, month = 8, day = 31,
+ hour = 0, min = 31, sec = 11, tzoffset = '+0300'}
+ test:is(tostring(T), '2021-08-31T00:31:11+03:00', 'initial')
+ test:is(tostring(T:set{ year = 2020 }), '2020-08-31T00:31:11+03:00', '2020 year')
+ test:is(tostring(T:set{ month = 11, day = 30 }), '2020-11-30T00:31:11+03:00', 'month = 11, day = 30')
+ test:is(tostring(T:set{ day = 9 }), '2020-11-09T00:31:11+03:00', 'day 9')
+ test:is(tostring(T:set{ hour = 6 }), '2020-11-09T06:31:11+03:00', 'hour 6')
+ test:is(tostring(T:set{ min = 12, sec = 23 }), '2020-11-09T04:12:23+03:00', 'min 12, sec 23')
+ test:is(tostring(T:set{ tzoffset = -8*60 }), '2020-11-08T17:12:23-08:00', 'offset -0800' )
+ test:is(tostring(T:set{ tzoffset = '+0800' }), '2020-11-09T09:12:23+08:00', 'offset +0800' )
+end)
+
+local function range_check_error(name, value, range)
+ return ('value %s of %s is out of allowed range [%d, %d]'):
+ format(value, name, range[1], range[2])
+end
+
+local function range_check_3_error(v)
+ return ('value %d of %s is out of allowed range [%d, %d..%d]'):
+ format(v, 'day', -1, 1, 31)
+end
+
+test:test("Time invalid :set{} operations", function(test)
+ test:plan(17)
+
+ local T = date.new{}
+
+ assert_raises(test, range_check_error('year', 10000, {1, 9999}),
+ function() T:set{ year = 10000} end)
+ assert_raises(test, range_check_error('year', -10, {1, 9999}),
+ function() T:set{ year = -10} end)
+
+ assert_raises(test, range_check_error('month', 20, {1, 12}),
+ function() T:set{ month = 20} end)
+ assert_raises(test, range_check_error('month', 0, {1, 12}),
+ function() T:set{ month = 0} end)
+ assert_raises(test, range_check_error('month', -20, {1, 12}),
+ function() T:set{ month = -20} end)
+
+ assert_raises(test, range_check_3_error(40),
+ function() T:set{ day = 40} end)
+ assert_raises(test, range_check_3_error(0),
+ function() T:set{ day = 0} end)
+ assert_raises(test, range_check_3_error(-10),
+ function() T:set{ day = -10} end)
+
+ assert_raises(test, range_check_error('hour', 31, {0, 23}),
+ function() T:set{ hour = 31} end)
+ assert_raises(test, range_check_error('hour', -1, {0, 23}),
+ function() T:set{ hour = -1} end)
+
+ assert_raises(test, range_check_error('min', 60, {0, 59}),
+ function() T:set{ min = 60} end)
+ assert_raises(test, range_check_error('min', -1, {0, 59}),
+ function() T:set{ min = -1} end)
+
+ assert_raises(test, range_check_error('sec', 61, {0, 60}),
+ function() T:set{ sec = 61} end)
+ assert_raises(test, range_check_error('sec', -1, {0, 60}),
+ function() T:set{ sec = -1} end)
+
+ local only1 = 'only one of nsec, usec or msecs may defined simultaneously'
+ assert_raises(test, only1, function()
+ T:set{ nsec = 123456, usec = 123}
+ end)
+ assert_raises(test, only1, function()
+ T:set{ nsec = 123456, msec = 123}
+ end)
+ assert_raises(test, only1, function()
+ T:set{ nsec = 123456, usec = 1234, msec = 123}
+ end)
+end)
+
+local function invalid_tz_fmt_error(val)
+ return ('invalid time-zone format %s'):format(val)
+end
+
+test:test("Time invalid tzoffset in :set{} operations", function(test)
+ test:plan(10)
+
+ local T = date.new{}
+ local bad_strings = {
+ 'bogus',
+ '0100',
+ '+-0100',
+ '+25:00',
+ '+99:00',
+ '-99:00',
+ }
+ for _, val in ipairs(bad_strings) do
+ assert_raises(test, invalid_tz_fmt_error(val),
+ function() T:set{ tzoffset = val } end)
+ end
+
+ local bad_numbers = {
+ 800,
+ -800,
+ 10000,
+ -10000,
+ }
+ for _, val in ipairs(bad_numbers) do
+ assert_raises(test, range_check_error('tzoffset', val, {-720, 720}),
+ function() T:set{ tzoffset = val } end)
+ end
+end)
+
+
+test:test("Time :set{day = -1} operations", function(test)
+ test:plan(14)
+ local tests = {
+ {{ year = 2000, month = 3, day = -1}, '2000-03-31T00:00:00Z'},
+ {{ year = 2000, month = 2, day = -1}, '2000-02-29T00:00:00Z'},
+ {{ year = 2001, month = 2, day = -1}, '2001-02-28T00:00:00Z'},
+ {{ year = 1900, month = 2, day = -1}, '1900-02-28T00:00:00Z'},
+ {{ year = 1904, month = 2, day = -1}, '1904-02-29T00:00:00Z'},
+ }
+ local T
+ for _, row in ipairs(tests) do
+ local args, str = unpack(row)
+ T = date.new(args)
+ test:is(tostring(T), str, ('checking -1 with %s'):format(str))
+ end
+ assert_raises(test, range_check_3_error(0), function() T = date.new{day = 0} end)
+ assert_raises(test, range_check_3_error(-2), function() T = date.new{day = -2} end)
+ assert_raises(test, range_check_3_error(-10), function() T = date.new{day = -10} end)
+
+ T = date.new{ year = 1904, month = 2, day = -1 }
+ test:is(tostring(T), '1904-02-29T00:00:00Z', 'base before :set{}')
+ test:is(tostring(T:set{month = 3, day = 2}), '1904-03-02T00:00:00Z', '2 March')
+ test:is(tostring(T:set{day = -1}), '1904-03-31T00:00:00Z', '31 March')
+
+ assert_raises(test, range_check_3_error(0), function() T:set{day = 0} end)
+ assert_raises(test, range_check_3_error(-2), function() T:set{day = -2} end)
+ assert_raises(test, range_check_3_error(-10), function() T:set{day = -10} end)
+end)
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index fc9b8abd2..5662e89f2 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -58,6 +58,8 @@ add_executable(random.test random.c core_test_utils.c)
target_link_libraries(random.test core unit)
add_executable(xmalloc.test xmalloc.c core_test_utils.c)
target_link_libraries(xmalloc.test core unit)
+add_executable(datetime.test datetime.c)
+target_link_libraries(datetime.test cdt core 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..f289e66dd
--- /dev/null
+++ b/test/unit/datetime.c
@@ -0,0 +1,261 @@
+#include "dt.h"
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#include "unit.h"
+#include "datetime.h"
+#include "trivia/util.h"
+
+static const char sample[] = "2012-12-24T15:30Z";
+
+#define S(s) {s, sizeof(s) - 1}
+struct {
+ const char *str;
+ 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
+
+static int
+parse_datetime(const char *str, size_t len, int64_t *secs_p,
+ int32_t *nanosecs_p, int32_t *offset_p)
+{
+ size_t n;
+ dt_t dt;
+ char c;
+ int sec_of_day = 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, &sec_of_day, &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:
+ *secs_p = ((int64_t)dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY +
+ sec_of_day - offset * 60;
+ *nanosecs_p = nanosecond;
+ *offset_p = offset;
+
+ return 0;
+}
+
+static int
+local_rd(const struct datetime *dt)
+{
+ return (int)((int64_t)dt->epoch / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET;
+}
+
+static int
+local_dt(const struct datetime *dt)
+{
+ return dt_from_rdn(local_rd(dt));
+}
+
+struct tm *
+datetime_to_tm(struct datetime *dt)
+{
+ static struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ dt_to_struct_tm(local_dt(dt), &tm);
+
+ int seconds_of_day = (int64_t)dt->epoch % 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 offset;
+
+ plan(355);
+ parse_datetime(sample, sizeof(sample) - 1,
+ &secs_expected, &nanosecs, &offset);
+
+ for (index = 0; index < lengthof(tests); index++) {
+ int64_t secs;
+ int rc = parse_datetime(tests[index].str, tests[index].len,
+ &secs, &nanosecs, &offset);
+ is(rc, 0, "correct parse_datetime return value for '%s'",
+ tests[index].str);
+ is(secs, secs_expected, "correct parse_datetime output "
+ "seconds for '%s",
+ tests[index].str);
+
+ /*
+ * check that stringized literal produces the same date
+ * time fields
+ */
+ static char buff[40];
+ struct datetime dt = {secs, nanosecs, offset, 0};
+ /* 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", p_tm);
+ ok(len > 0, "strftime");
+ int64_t parsed_secs;
+ int32_t parsed_nsecs, parsed_ofs;
+ rc = parse_datetime(buff, len, &parsed_secs, &parsed_nsecs, &parsed_ofs);
+ is(rc, 0, "correct parse_datetime return value for '%s'", buff);
+ is(secs, parsed_secs,
+ "reversible seconds via strftime for '%s", buff);
+ }
+ check_plan();
+}
+
+
+static void
+tostring_datetime_test(void)
+{
+ static struct {
+ const char *string;
+ int64_t secs;
+ uint32_t nsec;
+ uint32_t offset;
+ } tests[] = {
+ {"1970-01-01T02:00+02:00", 0, 0, 120},
+ {"1970-01-01T01:30+01:30", 0, 0, 90},
+ {"1970-01-01T01:00+01:00", 0, 0, 60},
+ {"1970-01-01T00:01+00:01", 0, 0, 1},
+ {"1970-01-01T00:00Z", 0, 0, 0},
+ {"1969-12-31T23:59-00:01", 0, 0, -1},
+ {"1969-12-31T23:00-01:00", 0, 0, -60},
+ {"1969-12-31T22:30-01:30", 0, 0, -90},
+ {"1969-12-31T22:00-02:00", 0, 0, -120},
+ {"1970-01-01T00:00:00.123456789Z", 0, 123456789, 0},
+ {"1970-01-01T00:00:00.123456Z", 0, 123456000, 0},
+ {"1970-01-01T00:00:00.123Z", 0, 123000000, 0},
+ {"1973-11-29T21:33:09Z", 123456789, 0, 0},
+ {"2013-10-28T17:51:56Z", 1382982716, 0, 0},
+ {"9999-12-31T23:59:59Z", 253402300799, 0, 0},
+ };
+ size_t index;
+
+ plan(15);
+ for (index = 0; index < lengthof(tests); index++) {
+ struct datetime date = {
+ tests[index].secs,
+ tests[index].nsec,
+ tests[index].offset,
+ 0
+ };
+ char buf[48];
+ datetime_to_string(buf, sizeof(buf), &date);
+ is(strcmp(buf, tests[index].string), 0,
+ "string '%s' expected, received '%s'",
+ tests[index].string, buf);
+ }
+ check_plan();
+}
+
+int
+main(void)
+{
+ plan(2);
+ datetime_test();
+ tostring_datetime_test();
+
+ return check_plan();
+}
diff --git a/test/unit/datetime.result b/test/unit/datetime.result
new file mode 100644
index 000000000..33997d9df
--- /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'
+ ok 5 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 10 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 15 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 20 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 25 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 30 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 35 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 40 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 45 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 50 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 55 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 60 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 65 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 70 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 75 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 80 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 85 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 90 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 95 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 100 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 105 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 110 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 115 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 120 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 125 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 130 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 135 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 140 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 145 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 150 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 155 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 160 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 165 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 170 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 175 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 180 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 185 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 190 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 195 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 200 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 205 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 210 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 215 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 220 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 225 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 230 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 235 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 240 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 245 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 250 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 255 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 260 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 265 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 270 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 275 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 280 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 285 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 290 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 295 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 300 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 305 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 310 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 315 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 320 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 325 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 330 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 335 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 340 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 345 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 350 - reversible seconds via strftime for '2012-12-24 15:30:00
+ 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'
+ ok 355 - reversible seconds via strftime for '2012-12-24 15:30:00
+ok 1 - subtests
diff --git a/third_party/c-dt b/third_party/c-dt
new file mode 160000
index 000000000..cbb3fc27c
--- /dev/null
+++ b/third_party/c-dt
@@ -0,0 +1 @@
+Subproject commit cbb3fc27c104aa7703b01a4108ce7871e1a28a1c
--
2.29.2
^ permalink raw reply [flat|nested] 4+ messages in thread