Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
@ 2021-07-15  8:18 Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
                   ` (14 more replies)
  0 siblings, 15 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=UTF-8, Size: 12758 bytes --]

Here is the preliminary RFC version of a patchset, which is implementing
datetime lua support in box, their messagepack, yaml, json serialization
support, and a set of c and lua tests.

* It's heavily influenced by Sci-Lua lua-time module implementation
  https://github.com/stepelu/lua-time 
  e.g. you could find very similar approach for handling of operations
  with year and months periods (which should be handled differently than
  usual seconds, days periods).

* But internally it actually uses Christina Hanson' c-dt module
  https://github.com/chansen/c-dt 
  (though it has been modified slightly for cleaner integration
  into cmake build process)

This is preliminary version of patchset, with unoptimal patches divisions
(like you could see patches with rename refactorings), but, due to an amount
of code in patchset, and limited time have, I send patchset as RFC today,
expecting to receive initial feedback for various parts of a series.

Prior plan was to split series into one, which introduces datetime module api,
and another one with datetime messagepack serialization implementation.
But here they left as part of a same series. Sorry, for invonvenience - I'll
reshuffle order according to feedback provided.

The problem, which was stopping such simple division - that `datetime.lua`
module uses `datetime_to_string()` ffi function for stringization of a
datetime object. And this function has been introduced as part of
messagepack/yaml/json serialization support. Yes, intra-dependendencies
between parts looks suboptimal, and I'm very open to suggestions how to
divide it properly.

Module API
==========

To simplify review of a code in patchset below you could find preliminary
documentation of a datetime api module. Eventually we may put it
as .rst file in documentation repository (though, personally, I'd prefer
to have all modules api committed to the same repo as code, but I digress)

Datetime module
===============

This module allows to parse date/time stamps, manipulate with them, and
generate textual representation. Parsing of date constants will handle
all ISO-8601 formats, and deal with some extensions.

For internal date representation it uses cdata structures of a form:

```c++
struct t_datetime {
	int secs;
	int nsecs;
	int offset;
};
```

Where:

-   secs is the (signed) number of seconds since epoch 1970-01-01T00:00Z;
-   nsecs is number of nanoseconds since beginning of day;
-   offset is the timezone offset (in minutes);

`datetime()` – create datetime object
-------------------------------------

Create date/time object using either string literals, or initialization
object. When given string literals it behaves like wrapper around
parse() method.

When there is initialization object it could create date/time object
using these attributes below:

| **Easy way** |                                                                                                 |
|--------------|-------------------------------------------------------------------------------------------------|
| secs         | Seconds since epoch                                                                             |
| nsec         | Nanoseconds since midnight                                                                      |
| offset       | Time-zone offset in minutes                                                                     |
| **YMD part** |                                                                                                 |
| year         | Year in range \[1..9999\]                                                                       |
| month        | Month in range \[1..12\]                                                                        |
| day          | Day in month in range \[1..31\]                                                                 |
| **HMS part** |                                                                                                 |
| hour         | Hour in range \[0..23\]                                                                         |
| minute       | Minute in range \[0..59\]                                                                       |
| second       | Seconds in range \[0..60\]. It allows to have fraction part in which case it goes to nsec field |
| tz           | Timezone offset (in minutes) for HMS part                                                       |

Example:

```lua

datetime = require `datetime`

d = datetime{secs = 0, nsec = 0, offset = 0}
d = datetime(‘1970-01-01’)
d = datetime{year = 1970, month = 1, day = 1}
```

`delta()` – create time duration object
---------------------------------------

TBD

`parse()` – parse full ISO-8601 string
--------------------------------------

Parse full length date/time literal, which may be in ISO-8601 format of
any of extended formats supported by `parse_date()`, `parse_time()` or
`parse_timezone()`

It deals with date/time string in format

`date ([Tt ] time ([ ] time_zone)? )?`

Where time or `time_zone` parts may be omitted.

Example:

```lua

datetime = require `datetime`

d = datetime.parse(`1970-01-01`)
d = datetime.parse(`1970-01-01T00:00:00Z`)
d = datetime.parse(`1970-01-01T02:00:00+02:00`)
```

`parse_date()` – parse ISO-8601 date literal
--------------------------------------------

Parse date string literal, return partial date object which has
precision of up-to date period of time.

A number of standard ISO-8601 formats supported, plus there are some
relaxed formats which are of frequently use:

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

`parse_time()` – parse ISO-8601 time literal
--------------------------------------------

Parse time string literal, return partial date/time object, which
defines time period inside of single date.

A number of standard ISO-8601 formats supported, plus there are some
relaxed formats which are of frequently use:

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

`parse_zone()` – parse ISO-8601 time zone
-----------------------------------------

Parse time-zone string literal, return partial date/time object, which
defines timezone offset in minutes sing GMT.

A number of standard ISO-8601 formats supported, plus there are some
relaxed formats which are of frequently use:

| Basic | Extended |
|-------|----------|
| Z     | N/A      |
| ±hh   | N/A      |
| ±hhmm | ±hh:mm   |

`tostring()` – convert datetime object to string
------------------------------------------------

Return string representation (probably compact if there are some parts
missing) of a date-time objects passed

`now()` – return current date/time
----------------------------------

`now()` returns local date and time object. It will use local time-zone
and nanosecond precision.

`strftime()` – convert date object to string using format
---------------------------------------------------------

`strftime()` is the FFI wrapper around strftime() function in LIBC. It
supports all the same flags which supports strftime() from host OS.

See
<https://pubs.opengroup.org/onlinepubs/000095399/functions/strftime.html>
for more details.

`asctime()` – convert date object to string using asctime predefined format
---------------------------------------------------------------------------

`asctime()` is the FFI wrapper over `asctime_r()` from a host libc. asctime
returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`

<https://pubs.opengroup.org/onlinepubs/009695399/functions/asctime.html>

`ctime()` – convert local time to string using ctime() predefined format
------------------------------------------------------------------------

`ctime()` is the FFI wrapper over `ctime_r()` in the host libc. ctime
returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`

<https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html>

The difference of `ctime()` and `asctime()` is that former is returning
local time zone formatted, while the latter will deal with GMT.

Examples:

```
tarantool> date = require 'datetime'
---
...
tarantool> T = date.now()
---
...
tarantool> T
---
- 2021-07-14T01:36:48.554105+03:00
...
tarantool> date.asctime(T)
---
- 'Tue Jul 13 22:36:48 2021
'
...
tarantool> date.ctime(T)
---
- 'Wed Jul 14 01:36:48 2021
'
...
```

Date attribute accessors
------------------------

|                |                                                                 |
|----------------|-----------------------------------------------------------------|
| `timestamp`    | Calculate timestamp with seconds and nanoseconds parts combined |
| `nanoseconds`  | Number of nanoseconds in time part                              |
| `microseconds` | Number of microseconds in time part                             |
| `milliseconds` | Number of milliseconds in time part                             |
| `seconds`      | Alias to timestamp                                              |
| `minutes`      | Number of minutes in time part                                  |
| `hours`        | Number of hours in time part                                    |
| `days`         | Number of days in time part                                     |

```
tarantool> d = date{year = 1970, month = 1, day = 1, hour = 0, minute = 10, second=10}
tarantool> d.secs
---
- 610
...
tarantool> d.nsec
---
- 0
...
tarantool> d.offset
---
- 0
...
tarantool> d.nanoseconds
---
- 610000000000
...
tarantool> d.milliseconds
---
- 610000
...
tarantool> d.hours
---
- 0.16944444444444
...
tarantool> d.minutes
---
- 10.166666666667
...
```

Date arithmetic
---------------

TBD


https://github.com/tarantool/tarantool/issues/5941
https://github.com/tarantool/tarantool/issues/5946
https://github.com/tarantool/tarantool/tree/tsafin/gh-5941-datetime-V2



Timur Safin (13):
  build: add Christian Hansen c-dt to the build
  lua: built-in module datetime
  test: datetime test
  test: datetime string formatting
  box: add messagepack support for datetime
  lua: positive/negative cases in datetime test
  lua: asctime and strfime fixed
  box, lua: renamed t_datetime_tz structure to datetime_t
  lua: calculated attributes for date
  lua: tostring formatization in datetime.lua
  test: allow relaxed date format without tz
  lua: initial time duration support
  lua: complete time duration support

 .gitmodules                       |   3 +
 CMakeLists.txt                    |   8 +
 cmake/BuildCDT.cmake              |   6 +
 src/CMakeLists.txt                |   4 +
 src/box/field_def.c               |  34 +-
 src/box/field_def.h               |   1 +
 src/box/msgpack.c                 |   7 +-
 src/box/tuple_compare.cc          |  24 +
 src/exports.h                     |  26 +
 src/lib/core/CMakeLists.txt       |   4 +-
 src/lib/core/datetime.h           |  96 ++++
 src/lib/core/mp_datetime.c        | 232 ++++++++
 src/lib/core/mp_extension_types.h |   1 +
 src/lib/mpstream/mpstream.c       |  11 +
 src/lib/mpstream/mpstream.h       |   4 +
 src/lua/datetime.c                |  70 +++
 src/lua/datetime.h                |  53 ++
 src/lua/datetime.lua              | 868 ++++++++++++++++++++++++++++++
 src/lua/init.c                    |   6 +-
 src/lua/msgpack.c                 |  12 +
 src/lua/msgpackffi.lua            |   8 +
 src/lua/serializer.c              |   4 +
 src/lua/serializer.h              |   2 +
 src/lua/utils.c                   |   1 -
 test/app-tap/datetime.test.lua    | 266 +++++++++
 test/unit/CMakeLists.txt          |   2 +
 test/unit/datetime.c              | 220 ++++++++
 test/unit/datetime.result         | 358 ++++++++++++
 third_party/c-dt                  |   1 +
 third_party/lua-cjson/lua_cjson.c |   8 +
 third_party/lua-yaml/lyaml.cc     |   6 +-
 31 files changed, 2326 insertions(+), 20 deletions(-)
 create mode 100644 cmake/BuildCDT.cmake
 create mode 100644 src/lib/core/datetime.h
 create mode 100644 src/lib/core/mp_datetime.c
 create mode 100644 src/lua/datetime.c
 create mode 100644 src/lua/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

-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 02/13] lua: built-in module datetime Timur Safin via Tarantool-patches
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* Integrated chansen/c-dt parser as 3rd party module to the
  Tarantool cmake build process.
* Points to tsafin/c-dt instead iof original chansen/c-dt to
  have easier build integration, because there is additional
  commit which integrated cmake support
---
 .gitmodules          | 3 +++
 CMakeLists.txt       | 8 ++++++++
 cmake/BuildCDT.cmake | 6 ++++++
 src/CMakeLists.txt   | 1 +
 third_party/c-dt     | 1 +
 5 files changed, 19 insertions(+)
 create mode 100644 cmake/BuildCDT.cmake
 create mode 160000 third_party/c-dt

diff --git a/.gitmodules b/.gitmodules
index f2f91ee72..56b236b14 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/tsafin/c-dt.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f9e1a7f79..81ee92933 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -571,6 +571,14 @@ endif()
 # zstd
 #
 
+#
+# Chritian Hanson 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..a19abb35c
--- /dev/null
+++ b/cmake/BuildCDT.cmake
@@ -0,0 +1,6 @@
+macro(libccdt_build)
+    set(LIBCDT_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/third_party/c-dt/)
+    set(LIBCDT_LIBRARIES cdt)
+
+    add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/c-dt)
+endmacro()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c0e272bd9..ef6a295d5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -202,6 +202,7 @@ set (common_libraries
     ${ICONV_LIBRARIES}
     ${OPENSSL_LIBRARIES}
     ${XXHASH_LIBRARIES}
+    ${LIBCDT_LIBRARIES}
 )
 
 if (TARGET_OS_LINUX OR TARGET_OS_DEBIAN_FREEBSD)
diff --git a/third_party/c-dt b/third_party/c-dt
new file mode 160000
index 000000000..8b61c4ea0
--- /dev/null
+++ b/third_party/c-dt
@@ -0,0 +1 @@
+Subproject commit 8b61c4ea006efefc3a068f8df4a156bf5c725c89
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 02/13] lua: built-in module datetime
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 03/13] test: datetime test Timur Safin via Tarantool-patches
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* Added a new Tarantool built-in module `datetime`;
* Register cdef types for this module;
* Export some dt_* functions from c-dt library;
* Datetime parse unit tests;
---
 src/CMakeLists.txt        |   3 +
 src/exports.h             |  21 ++
 src/lib/core/datetime.h   |  61 +++++
 src/lua/datetime.c        |  64 +++++
 src/lua/datetime.h        |  53 ++++
 src/lua/datetime.lua      | 564 ++++++++++++++++++++++++++++++++++++++
 src/lua/init.c            |   6 +-
 test/unit/CMakeLists.txt  |   2 +
 test/unit/datetime.c      | 157 +++++++++++
 test/unit/datetime.result | 135 +++++++++
 10 files changed, 1065 insertions(+), 1 deletion(-)
 create mode 100644 src/lib/core/datetime.h
 create mode 100644 src/lua/datetime.c
 create mode 100644 src/lua/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..9d3da10d9 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)
@@ -136,6 +138,7 @@ set (server_sources
      lua/string.c
      lua/swim.c
      lua/decimal.c
+     lua/datetime.c
      ${lua_sources}
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
diff --git a/src/exports.h b/src/exports.h
index 5bb3e6a2b..db40c03a4 100644
--- a/src/exports.h
+++ b/src/exports.h
@@ -531,3 +531,24 @@ EXPORT(uri_format)
 EXPORT(uri_parse)
 EXPORT(uuid_nil)
 EXPORT(uuid_unpack)
+EXPORT(dt_from_rdn)
+EXPORT(dt_from_yd)
+EXPORT(dt_from_ymd)
+EXPORT(dt_from_yqd)
+EXPORT(dt_from_ywd)
+EXPORT(dt_to_yd)
+EXPORT(dt_to_ymd)
+EXPORT(dt_to_yqd)
+EXPORT(dt_to_ywd)
+EXPORT(dt_rdn)
+EXPORT(dt_dow)
+EXPORT(dt_parse_iso_date)
+EXPORT(dt_parse_iso_time)
+EXPORT(dt_parse_iso_time_basic)
+EXPORT(dt_parse_iso_time_extended)
+EXPORT(dt_parse_iso_zone)
+EXPORT(dt_parse_iso_zone_basic)
+EXPORT(dt_parse_iso_zone_extended)
+EXPORT(dt_parse_iso_zone_lenient)
+EXPORT(dt_from_struct_tm)
+EXPORT(dt_to_struct_tm)
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
new file mode 100644
index 000000000..837ed346c
--- /dev/null
+++ b/src/lib/core/datetime.h
@@ -0,0 +1,61 @@
+#pragma once
+/*
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <c-dt/dt_core.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * datetime structure consisting of:
+ */
+struct t_datetime_tz {
+	int secs;	///< seconds since epoch
+	int nsec;	///< nanoseconds if any
+	int offset;	///< offset in minutes from GMT
+};
+
+/**
+ * Date/time delta structure
+ */
+struct t_datetime_duration {
+	int secs;	///< relative seconds delta
+	int nsec;	///< nanoseconds delta
+};
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
diff --git a/src/lua/datetime.c b/src/lua/datetime.c
new file mode 100644
index 000000000..37a8bc020
--- /dev/null
+++ b/src/lua/datetime.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "datetime.h"
+
+#include <assert.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+uint32_t CTID_DATETIME_TZ = 0;
+uint32_t CTID_DURATION = 0;
+
+void
+tarantool_lua_datetime_init(struct lua_State *L)
+{
+	int rc = luaL_cdef(L, "struct t_datetime_tz {"
+				"int secs;"
+				"int nsec;"
+				"int offset;"
+			  "};");
+	assert(rc == 0);
+	(void) rc;
+	CTID_DATETIME_TZ = luaL_ctypeid(L, "struct t_datetime_tz");
+	assert(CTID_DATETIME_TZ != 0);
+
+
+	rc = luaL_cdef(L, "struct t_datetime_duration {"
+				"int secs;"
+				"int nsec;"
+			  "};");
+	assert(rc == 0);
+	(void) rc;
+	CTID_DURATION = luaL_ctypeid(L, "struct t_datetime_duration");
+	assert(CTID_DURATION != 0);
+}
diff --git a/src/lua/datetime.h b/src/lua/datetime.h
new file mode 100644
index 000000000..d290a6d13
--- /dev/null
+++ b/src/lua/datetime.h
@@ -0,0 +1,53 @@
+#pragma once
+/*
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "lib/core/datetime.h"
+#include "utils.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+extern uint32_t CTID_DATETIME_TZ;
+extern uint32_t CTID_DURATION;
+
+struct lua_State;
+
+struct t_datetime_tz*
+lua_pushdatetime(struct lua_State *L);
+
+void
+tarantool_lua_datetime_init(struct lua_State *L);
+
+#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..cffa38cd5
--- /dev/null
+++ b/src/lua/datetime.lua
@@ -0,0 +1,564 @@
+local ffi = require('ffi')
+local cdt = ffi.C
+
+ffi.cdef [[
+
+    typedef int dt_t;
+
+    // dt_core.h
+    typedef enum {
+        DT_MON       = 1,
+        DT_MONDAY    = 1,
+        DT_TUE       = 2,
+        DT_TUESDAY   = 2,
+        DT_WED       = 3,
+        DT_WEDNESDAY = 3,
+        DT_THU       = 4,
+        DT_THURSDAY  = 4,
+        DT_FRI       = 5,
+        DT_FRIDAY    = 5,
+        DT_SAT       = 6,
+        DT_SATURDAY  = 6,
+        DT_SUN       = 7,
+        DT_SUNDAY    = 7,
+    } dt_dow_t;
+
+    dt_t     dt_from_rdn     (int n);
+    dt_t     dt_from_yd      (int y, int d);
+    dt_t     dt_from_ymd     (int y, int m, int d);
+    dt_t     dt_from_yqd     (int y, int q, int d);
+    dt_t     dt_from_ywd     (int y, int w, int d);
+
+    void     dt_to_yd        (dt_t dt, int *y, int *d);
+    void     dt_to_ymd       (dt_t dt, int *y, int *m, int *d);
+    void     dt_to_yqd       (dt_t dt, int *y, int *q, int *d);
+    void     dt_to_ywd       (dt_t dt, int *y, int *w, int *d);
+
+    int      dt_rdn          (dt_t dt);
+    dt_dow_t dt_dow          (dt_t dt);
+
+    // dt_parse_iso.h
+    size_t dt_parse_iso_date          (const char *str, size_t len, dt_t *dt);
+
+    size_t dt_parse_iso_time          (const char *str, size_t len, int *sod, int *nsec);
+    size_t dt_parse_iso_time_basic    (const char *str, size_t len, int *sod, int *nsec);
+    size_t dt_parse_iso_time_extended (const char *str, size_t len, int *sod, int *nsec);
+
+    size_t dt_parse_iso_zone          (const char *str, size_t len, int *offset);
+    size_t dt_parse_iso_zone_basic    (const char *str, size_t len, int *offset);
+    size_t dt_parse_iso_zone_extended (const char *str, size_t len, int *offset);
+    size_t dt_parse_iso_zone_lenient  (const char *str, size_t len, int *offset);
+
+    // dt_tm.h
+    dt_t    dt_from_struct_tm  (const struct tm *tm);
+    void    dt_to_struct_tm    (dt_t dt, struct tm *tm);
+
+    // <asm-generic/posix_types.h>
+    typedef long            __kernel_long_t;
+    typedef unsigned long   __kernel_ulong_t;
+    // /usr/include/x86_64-linux-gnu/bits/types/time_t.h
+    typedef long            time_t;
+
+
+    // <time.h>
+    typedef __kernel_long_t	__kernel_time_t;
+    typedef __kernel_long_t	__kernel_suseconds_t;
+
+    struct timespec {
+        __kernel_time_t	        tv_sec;     /* seconds */
+        long                    tv_nsec;    /* nanoseconds */
+    };
+
+    struct timeval {
+        __kernel_time_t	        tv_sec;	    /* seconds */
+        __kernel_suseconds_t    tv_usec;    /* microseconds */
+    };
+
+    struct timezone {
+        int	tz_minuteswest;     /* minutes west of Greenwich */
+        int	tz_dsttime;	        /* type of dst correction */
+    };
+
+    // /usr/include/x86_64-linux-gnu/sys/time.h
+    typedef struct timezone * __timezone_ptr_t;
+
+    /* Get the current time of day and timezone information,
+       putting it into *TV and *TZ.  If TZ is NULL, *TZ is not filled.
+       Returns 0 on success, -1 on errors.
+
+       NOTE: This form of timezone information is obsolete.
+       Use the functions and variables declared in <time.h> instead.  */
+    int gettimeofday (struct timeval *__tv, struct timezone * __tz);
+
+    // /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h
+    /* ISO C `broken-down time' structure.  */
+    struct tm
+    {
+        int tm_sec;	        /* Seconds.	[0-60] (1 leap second) */
+        int tm_min;	        /* Minutes.	[0-59] */
+        int tm_hour;        /* Hours.	[0-23] */
+        int tm_mday;        /* Day.		[1-31] */
+        int tm_mon;	        /* Month.	[0-11] */
+        int tm_year;        /* Year	- 1900.  */
+        int tm_wday;        /* Day of week.	[0-6] */
+        int tm_yday;        /* Days in year.[0-365]	*/
+        int tm_isdst;       /* DST.		[-1/0/1]*/
+
+        long int tm_gmtoff; /* Seconds east of UTC.  */
+        const char *tm_zone;/* Timezone abbreviation.  */
+    };
+
+    // <time.h>
+    /* Return the current time and put it in *TIMER if TIMER is not NULL.  */
+    time_t time (time_t *__timer);
+
+    /* Format TP into S according to FORMAT.
+    Write no more than MAXSIZE characters and return the number
+    of characters written, or 0 if it would exceed MAXSIZE.  */
+    size_t strftime (char * __s, size_t __maxsize, const char * __format,
+                     const struct tm * __tp);
+
+    /* Parse S according to FORMAT and store binary time information in TP.
+    The return value is a pointer to the first unparsed character in S.  */
+    char *strptime (const char * __s, const char * __fmt, struct tm *__tp);
+
+    /* Return the `struct tm' representation of *TIMER in UTC,
+    using *TP to store the result.  */
+    struct tm *gmtime_r (const time_t * __timer, struct tm * __tp);
+
+    /* Return the `struct tm' representation of *TIMER in local time,
+    using *TP to store the result.  */
+    struct tm *localtime_r (const time_t * __timer, struct tm * __tp);
+
+    /* Return a string of the form "Day Mon dd hh:mm:ss yyyy\n"
+    that is the representation of TP in this format.  */
+    char *asctime (const struct tm *__tp);
+
+    /* Equivalent to `asctime (localtime (timer))'.  */
+    char *ctime (const time_t *__timer);
+
+]]
+
+local native = ffi.C
+
+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 t_datetime_tz')
+local duration_t = ffi.typeof('struct t_datetime_duration')
+
+local function duration_new()
+    local delta = ffi.new(duration_t)
+    return delta
+end
+
+local function adjusted_secs(dt)
+    return dt.secs - dt.offset * 60
+end
+
+local function datetime_sub(lhs, rhs)
+    local s1 = adjusted_secs(lhs)
+    local s2 = adjusted_secs(rhs)
+    local d = duration_new()
+    d.secs = s2 - s1
+    d.nsec = rhs.nsec - lhs.nsec
+    if d.nsec < 0 then
+        d.secs = d.secs - 1
+        d.nsec = d.nsec + NANOS_PER_SEC
+    end
+    return d
+end
+
+local function datetime_add(lhs, rhs)
+    local s1 = adjusted_secs(lhs)
+    local s2 = adjusted_secs(rhs)
+    local d = duration_new()
+    d.secs = s2 + s1
+    d.nsec = rhs.nsec + lhs.nsec
+    if d.nsec >= NANOS_PER_SEC then
+        d.secs = d.secs + 1
+        d.nsec = d.nsec - NANOS_PER_SEC
+    end
+    return d
+end
+
+local function datetime_eq(lhs, rhs)
+    -- we usually don't need to check nullness
+    -- but older tarantool console will call us checking for equality to nil
+    if rhs == nil then
+        return false
+    end
+    return (lhs.secs == rhs.secs) and (lhs.nsec == rhs.nsec)
+end
+
+
+local function datetime_lt(lhs, rhs)
+    return (lhs.secs < rhs.secs) or
+           (lhs.secs == rhs.secs and lhs.nsec < rhs.nsec)
+end
+
+local function datetime_le(lhs, rhs)
+    return (lhs.secs <= rhs.secs) or
+           (lhs.secs == rhs.secs and lhs.nsec <= rhs.nsec)
+end
+
+local function datetime_serialize(self)
+    -- Allow YAML, MsgPack and JSON to dump objects with sockets
+    return { secs = self.secs, nsec = self.nsec, tz = self.offset }
+end
+
+local function duration_serialize(self)
+    -- Allow YAML and JSON to dump objects with sockets
+    return { secs = self.secs, nsec = self.nsec }
+end
+
+local datetime_mt = {
+    -- __tostring = datetime_tostring,
+    __serialize = datetime_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+
+    nanoseconds = function(self)
+        return tonumber(self.secs*NANOS_PER_SEC + self.nsec)
+    end,
+    microseconds = function(self)
+        return tonumber(self.secs*1e6 + self.nsec*1e3)
+    end,
+    seconds = function(self)
+        return tonumber(self.secs + self.nsec*1e3)
+    end,
+    minutes = function(self)
+        return tonumber((self._ticks/(1e6*60))%60)
+    end,
+    hours = function(self)
+        return tonumber(self._ticks/(1e6*60*60))
+    end,
+
+}
+
+local duration_mt = {
+    -- __tostring = duration_tostring,
+    __serialize = duration_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+}
+
+local function datetime_new_raw(secs, nsec, offset)
+    local dt_obj = ffi.new(datetime_t)
+    dt_obj.secs = secs
+    dt_obj.nsec = nsec
+    dt_obj.offset = offset
+    return dt_obj
+end
+
+local function local_rd(o)
+    return math.floor(o.secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET
+end
+
+local function local_dt(o)
+    return cdt.dt_from_rdn(local_rd(o))
+end
+
+local function mk_timestamp(dt, sp, fp, offset)
+    local epochV = dt ~= nil and (cdt.dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY or 0
+    local spV = sp ~= nil and sp or 0
+    local fpV = fp ~= nil and fp or 0
+    local ofsV = offset ~= nil and offset or 0
+    return datetime_new_raw (epochV + spV - ofsV * 60, fpV, ofsV)
+end
+
+-- create @datetime_t given object @o fields
+local function datetime_new(o)
+    if o == nil then
+        return datetime_new_raw(0, 0, 0)
+    end
+    local secs = 0
+    local nsec = 0
+    local offset = 0
+    local easy_way = false
+    local y, M, d, ymd
+    y, M, d, ymd = 0, 0, 0, false
+
+    local h, m, s, frac, hms
+    h, m, s, frac, hms = 0, 0, 0, 0, false
+
+    local dt = 0
+
+    for key, value in pairs(o) do
+        local handlers = {
+            secs = function(v)
+                secs = v
+                easy_way = true
+            end,
+
+            nsec = function(v)
+                nsec = v
+                easy_way = true
+            end,
+
+            offset = function (v)
+                offset = v
+                easy_way = true
+            end,
+
+            year = function(v)
+                assert(v > 0 and v < 10000)
+                y = v
+                ymd = true
+            end,
+
+            month = function(v)
+                assert(v > 0 and v < 12 )
+                M = v
+                ymd = true
+            end,
+
+            day = function(v)
+                assert(v > 0 and v < 32)
+                d = v
+                ymd = true
+            end,
+
+            hour = function(v)
+                assert(v >= 0 and v < 24)
+                h = v
+                hms = true
+            end,
+
+            minute = function(v)
+                assert(v >= 0 and v < 60)
+                m = v
+                hms = true
+            end,
+
+            second = function(v)
+                assert(v >= 0 and v < 61)
+                frac = v % 1
+                if frac then
+                    s = v - (v % 1)
+                else
+                    s = v
+                end
+                hms = true
+            end,
+
+            -- tz offset in minutes
+            tz = function(v)
+                assert(v >= 0 and v <= 720)
+                offset = v
+            end
+        }
+        handlers[key](value)
+    end
+
+    -- .sec, .nsec, .offset
+    if easy_way then
+        return datetime_new_raw(secs, nsec, offset)
+    end
+
+    -- .year, .month, .day
+    if ymd then
+        dt = dt + cdt.dt_from_ymd(y, M, d)
+    end
+
+    -- .hour, .minute, .second
+    if hms then
+        secs = h * 3600 + m * 60 + s
+    end
+
+    return mk_timestamp(dt, secs, frac, offset)
+end
+
+
+-- simple parse functions:
+-- parse_date/parse_time/parse_zone
+
+--[[
+    Basic      Extended
+    20121224   2012-12-24   Calendar date   (ISO 8601)
+    2012359    2012-359     Ordinal date    (ISO 8601)
+    2012W521   2012-W52-1   Week date       (ISO 8601)
+    2012Q485   2012-Q4-85   Quarter date
+]]
+
+local function parse_date(str)
+    local dt = ffi.new('dt_t[1]')
+    local rc = cdt.dt_parse_iso_date(str, #str, dt)
+    assert(rc > 0)
+    return mk_timestamp(dt[0])
+end
+
+--[[
+    Basic               Extended
+    T12                 N/A
+    T1230               T12:30
+    T123045             T12:30:45
+    T123045.123456789   T12:30:45.123456789
+    T123045,123456789   T12:30:45,123456789
+
+    The time designator [T] may be omitted.
+]]
+local function parse_time(str)
+    local sp = ffi.new('int[1]')
+    local fp = ffi.new('int[1]')
+    local rc = cdt.dt_parse_iso_time(str, #str, sp, fp)
+    assert(rc > 0)
+    return mk_timestamp(nil, sp[0], fp[0])
+end
+
+--[[
+    Basic    Extended
+    Z        N/A
+    ±hh      N/A
+    ±hhmm    ±hh:mm
+]]
+local function parse_zone(str)
+    local offset = ffi.new('int[1]')
+    local rc = cdt.dt_parse_iso_zone(str, #str, offset)
+    assert(rc > 0)
+    return mk_timestamp(nil, nil, nil, offset[0])
+end
+
+
+--[[
+    aggregated parse functions
+    assumes to deal with date T time time_zone
+    at once
+
+    date [T] time [ ] time_zone
+]]
+local function parse_str(str)
+    local dt = ffi.new('dt_t[1]')
+    local len = #str
+    local n = cdt.dt_parse_iso_date(str, len, dt)
+    local dt_ = dt[0]
+    if n == 0 or len == n then
+        return mk_timestamp(dt_)
+    end
+
+    str = str:sub(tonumber(n) + 1)
+
+    local ch = str:sub(1,1)
+    if ch ~= 't' and ch ~= 'T' and ch ~= ' ' then
+        return mk_timestamp(dt_)
+    end
+
+    str = str:sub(2)
+    len = #str
+
+    local sp = ffi.new('int[1]')
+    local fp = ffi.new('int[1]')
+    local n = cdt.dt_parse_iso_time(str, len, sp, fp)
+    if n == 0 then
+        return mk_timestamp(dt_)
+    end
+    local sp_ = sp[0]
+    local fp_ = fp[0]
+    if len == n then
+        return mk_timestamp(dt_, sp_, fp_)
+    end
+
+    str = str:sub(tonumber(n) + 1)
+
+    if str:sub(1,1) == ' ' then
+        str = str:sub(2)
+    end
+
+    len = #str
+
+    local offset = ffi.new('int[1]')
+    n = cdt.dt_parse_iso_zone(str, len, offset)
+    if n == 0 then
+        return mk_timestamp(dt_, sp_, fp_)
+    end
+    return mk_timestamp(dt_, sp_, fp_, offset[0])
+end
+
+local function datetime_from(o)
+    if o == nil or type(o) == 'table' then
+        return datetime_new(o)
+    elseif type(o) == 'string' then
+        return parse_str(o)
+    end
+end
+
+local function local_now()
+    local p_tv = ffi.new ' struct timeval [1] '
+    local rc = native.gettimeofday(p_tv, nil)
+    assert(rc == 0)
+
+    local secs = p_tv[0].tv_sec
+    local nsec = p_tv[0].tv_usec * 1000
+
+    local p_time = ffi.new 'time_t[1]'
+    local p_tm = ffi.new 'struct tm[1]'
+    native.time(p_time)
+    native.localtime_r(p_time, p_tm)
+    -- local dt = cdt.dt_from_struct_tm(p_tm)
+    local ofs = p_tm[0].tm_gmtoff / 60 -- convert seconds to minutes
+
+    return datetime_new_raw(secs, nsec, ofs) -- FIXME
+end
+
+local function asctime(o)
+    assert(ffi.typeof(o) == datetime_t)
+    local p_tm = ffi.new 'struct tm[1]'
+    cdt.dt_to_struct_tm(local_dt(o), p_tm)
+    return ffi.string(native.asctime(p_tm))
+end
+
+local function ctime(o)
+    assert(ffi.typeof(o) == datetime_t)
+    local p_time = ffi.new 'time_t[1]'
+    p_time[0] = o.secs
+    return ffi.string(native.ctime(p_time))
+end
+
+local function strftime(fmt, o)
+    assert(ffi.typeof(o) == datetime_t)
+    local sz = 50
+    local buff = ffi.new('char[?]', sz)
+    local p_tm = ffi.new 'struct tm[1]'
+    cdt.dt_to_struct_tm(local_dt(o), p_tm)
+    native.strftime(buff, sz, fmt, p_tm)
+    return ffi.string(buff)
+end
+
+-- strftime may be redirected to datetime:fmt("format")
+local function datetime_fmt()
+end
+
+
+ffi.metatype(duration_t, duration_mt)
+ffi.metatype(datetime_t, datetime_mt)
+
+return setmetatable(
+    {
+        datetime = datetime_new,
+        delta = duration_new,
+
+        parse = parse_str,
+        parse_date = parse_date,
+        parse_time = parse_time,
+        parse_zone = parse_zone,
+        fmt = datetime_fmt,
+
+        now = local_now,
+    -- strptime = strptime;
+        strftime = strftime,
+        asctime = asctime,
+        ctime = ctime,
+    }, {
+        __call = function(self, ...) return datetime_from(...) end
+    }
+)
diff --git a/src/lua/init.c b/src/lua/init.c
index f9738025d..89b070310 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -62,6 +62,7 @@
 #include "lua/utf8.h"
 #include "lua/swim.h"
 #include "lua/decimal.h"
+#include "lua/datetime.h"
 #include "digest.h"
 #include "errinj.h"
 #include <small/ibuf.h>
@@ -129,7 +130,8 @@ extern char strict_lua[],
 	parse_lua[],
 	process_lua[],
 	humanize_lua[],
-	memprof_lua[]
+	memprof_lua[],
+	datetime_lua[]
 ;
 
 static const char *lua_modules[] = {
@@ -184,6 +186,7 @@ static const char *lua_modules[] = {
 	"memprof.process", process_lua,
 	"memprof.humanize", humanize_lua,
 	"memprof", memprof_lua,
+	"datetime", datetime_lua,
 	NULL
 };
 
@@ -479,6 +482,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
 	tarantool_lua_serializer_init(L);
 	tarantool_lua_swim_init(L);
 	tarantool_lua_decimal_init(L);
+	tarantool_lua_datetime_init(L);
 	luaopen_http_client_driver(L);
 	lua_pop(L, 1);
 	luaopen_msgpack(L);
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..25c9c1f1a
--- /dev/null
+++ b/test/unit/datetime.c
@@ -0,0 +1,157 @@
+#include "dt.h"
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "unit.h"
+
+const char sample[] = "2012-12-24T15:30Z";
+
+#define S(s) {s, sizeof(s) - 1}
+struct {
+	const char * sz;
+	size_t len;
+} tests[] = {
+	S("2012-12-24 15:30Z"),
+	S("2012-12-24 15:30z"),
+	S("2012-12-24 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 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 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-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-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, int64_t *np,
+	       int64_t *op)
+{
+	size_t n;
+	dt_t dt;
+	char c;
+	int sod, nanosecond, offset;
+
+	n = dt_parse_iso_date(str, len, &dt);
+	if (!n || n == len)
+		return 1;
+
+	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 || n == len)
+		return 1;
+
+	if (str[n] == ' ')
+	n++;
+
+	str += n;
+	len -= n;
+
+	n = dt_parse_iso_zone_lenient(str, len, &offset);
+	if (!n || n != len)
+		return 1;
+
+	*sp = ((int64_t)dt_rdn(dt) - 719163) * 86400 + sod - offset * 60;
+	*np = nanosecond;
+	*op = offset;
+
+	return 0;
+}
+
+static void datetime_test(void)
+{
+	size_t index;
+	int64_t secs_expected;
+	int64_t nanosecs;
+	int64_t ofs;
+
+	plan(132);
+	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);
+	}
+}
+
+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..5cd68dea5
--- /dev/null
+++ b/test/unit/datetime.result
@@ -0,0 +1,135 @@
+1..1
+    1..132
+    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 - correct parse_datetime return value for '2012-12-24 15:30z'
+    ok 4 - correct parse_datetime output seconds for '2012-12-24 15:30z
+    ok 5 - correct parse_datetime return value for '2012-12-24 16:30+01:00'
+    ok 6 - correct parse_datetime output seconds for '2012-12-24 16:30+01:00
+    ok 7 - correct parse_datetime return value for '2012-12-24 16:30+0100'
+    ok 8 - correct parse_datetime output seconds for '2012-12-24 16:30+0100
+    ok 9 - correct parse_datetime return value for '2012-12-24 16:30+01'
+    ok 10 - correct parse_datetime output seconds for '2012-12-24 16:30+01
+    ok 11 - correct parse_datetime return value for '2012-12-24 14:30-01:00'
+    ok 12 - correct parse_datetime output seconds for '2012-12-24 14:30-01:00
+    ok 13 - correct parse_datetime return value for '2012-12-24 14:30-0100'
+    ok 14 - correct parse_datetime output seconds for '2012-12-24 14:30-0100
+    ok 15 - correct parse_datetime return value for '2012-12-24 14:30-01'
+    ok 16 - correct parse_datetime output seconds for '2012-12-24 14:30-01
+    ok 17 - correct parse_datetime return value for '2012-12-24 15:30:00Z'
+    ok 18 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z
+    ok 19 - correct parse_datetime return value for '2012-12-24 15:30:00z'
+    ok 20 - correct parse_datetime output seconds for '2012-12-24 15:30:00z
+    ok 21 - correct parse_datetime return value for '2012-12-24 16:30:00+01:00'
+    ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01:00
+    ok 23 - correct parse_datetime return value for '2012-12-24 16:30:00+0100'
+    ok 24 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100
+    ok 25 - correct parse_datetime return value for '2012-12-24 14:30:00-01:00'
+    ok 26 - correct parse_datetime output seconds for '2012-12-24 14:30:00-01:00
+    ok 27 - correct parse_datetime return value for '2012-12-24 14:30:00-0100'
+    ok 28 - correct parse_datetime output seconds for '2012-12-24 14:30:00-0100
+    ok 29 - correct parse_datetime return value for '2012-12-24 15:30:00.123456Z'
+    ok 30 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z
+    ok 31 - correct parse_datetime return value for '2012-12-24 15:30:00.123456z'
+    ok 32 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z
+    ok 33 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01:00'
+    ok 34 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01:00
+    ok 35 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01'
+    ok 36 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01
+    ok 37 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01:00'
+    ok 38 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01:00
+    ok 39 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01'
+    ok 40 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01
+    ok 41 - correct parse_datetime return value for '2012-12-24t15:30Z'
+    ok 42 - correct parse_datetime output seconds for '2012-12-24t15:30Z
+    ok 43 - correct parse_datetime return value for '2012-12-24t15:30z'
+    ok 44 - correct parse_datetime output seconds for '2012-12-24t15:30z
+    ok 45 - correct parse_datetime return value for '2012-12-24t16:30+01:00'
+    ok 46 - correct parse_datetime output seconds for '2012-12-24t16:30+01:00
+    ok 47 - correct parse_datetime return value for '2012-12-24t16:30+0100'
+    ok 48 - correct parse_datetime output seconds for '2012-12-24t16:30+0100
+    ok 49 - correct parse_datetime return value for '2012-12-24t14:30-01:00'
+    ok 50 - correct parse_datetime output seconds for '2012-12-24t14:30-01:00
+    ok 51 - correct parse_datetime return value for '2012-12-24t14:30-0100'
+    ok 52 - correct parse_datetime output seconds for '2012-12-24t14:30-0100
+    ok 53 - correct parse_datetime return value for '2012-12-24t15:30:00Z'
+    ok 54 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z
+    ok 55 - correct parse_datetime return value for '2012-12-24t15:30:00z'
+    ok 56 - correct parse_datetime output seconds for '2012-12-24t15:30:00z
+    ok 57 - correct parse_datetime return value for '2012-12-24t16:30:00+01:00'
+    ok 58 - correct parse_datetime output seconds for '2012-12-24t16:30:00+01:00
+    ok 59 - correct parse_datetime return value for '2012-12-24t16:30:00+0100'
+    ok 60 - correct parse_datetime output seconds for '2012-12-24t16:30:00+0100
+    ok 61 - correct parse_datetime return value for '2012-12-24t14:30:00-01:00'
+    ok 62 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01:00
+    ok 63 - correct parse_datetime return value for '2012-12-24t14:30:00-0100'
+    ok 64 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100
+    ok 65 - correct parse_datetime return value for '2012-12-24t15:30:00.123456Z'
+    ok 66 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z
+    ok 67 - correct parse_datetime return value for '2012-12-24t15:30:00.123456z'
+    ok 68 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z
+    ok 69 - correct parse_datetime return value for '2012-12-24t16:30:00.123456+01:00'
+    ok 70 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01:00
+    ok 71 - correct parse_datetime return value for '2012-12-24t14:30:00.123456-01:00'
+    ok 72 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-01:00
+    ok 73 - correct parse_datetime return value for '2012-12-24 16:30 +01:00'
+    ok 74 - correct parse_datetime output seconds for '2012-12-24 16:30 +01:00
+    ok 75 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
+    ok 76 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
+    ok 77 - correct parse_datetime return value for '2012-12-24 15:30 UTC'
+    ok 78 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC
+    ok 79 - correct parse_datetime return value for '2012-12-24 16:30 UTC+1'
+    ok 80 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1
+    ok 81 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01'
+    ok 82 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01
+    ok 83 - correct parse_datetime return value for '2012-12-24 16:30 UTC+0100'
+    ok 84 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100
+    ok 85 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01:00'
+    ok 86 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01:00
+    ok 87 - correct parse_datetime return value for '2012-12-24 14:30 UTC-1'
+    ok 88 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1
+    ok 89 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01'
+    ok 90 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01
+    ok 91 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01:00'
+    ok 92 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01:00
+    ok 93 - correct parse_datetime return value for '2012-12-24 14:30 UTC-0100'
+    ok 94 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-0100
+    ok 95 - correct parse_datetime return value for '2012-12-24 15:30 GMT'
+    ok 96 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT
+    ok 97 - correct parse_datetime return value for '2012-12-24 16:30 GMT+1'
+    ok 98 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+1
+    ok 99 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01'
+    ok 100 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01
+    ok 101 - correct parse_datetime return value for '2012-12-24 16:30 GMT+0100'
+    ok 102 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100
+    ok 103 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01:00'
+    ok 104 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01:00
+    ok 105 - correct parse_datetime return value for '2012-12-24 14:30 GMT-1'
+    ok 106 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1
+    ok 107 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01'
+    ok 108 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01
+    ok 109 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01:00'
+    ok 110 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01:00
+    ok 111 - correct parse_datetime return value for '2012-12-24 14:30 GMT-0100'
+    ok 112 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-0100
+    ok 113 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
+    ok 114 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
+    ok 115 - correct parse_datetime return value for '2012-12-24 16:30:00 +01:00'
+    ok 116 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01:00
+    ok 117 - correct parse_datetime return value for '2012-12-24 14:30:00 -01:00'
+    ok 118 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01:00
+    ok 119 - correct parse_datetime return value for '2012-12-24 16:30:00.123456 +01:00'
+    ok 120 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +01:00
+    ok 121 - correct parse_datetime return value for '2012-12-24 14:30:00.123456 -01:00'
+    ok 122 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00
+    ok 123 - correct parse_datetime return value for '2012-12-24 15:30:00.123456 -00:00'
+    ok 124 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00:00
+    ok 125 - correct parse_datetime return value for '20121224T1630+01:00'
+    ok 126 - correct parse_datetime output seconds for '20121224T1630+01:00
+    ok 127 - correct parse_datetime return value for '2012-12-24T1630+01:00'
+    ok 128 - correct parse_datetime output seconds for '2012-12-24T1630+01:00
+    ok 129 - correct parse_datetime return value for '20121224T16:30+01'
+    ok 130 - correct parse_datetime output seconds for '20121224T16:30+01
+    ok 131 - correct parse_datetime return value for '20121224T16:30 +01'
+    ok 132 - correct parse_datetime output seconds for '20121224T16:30 +01
+ok 1 - subtests
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 03/13] test: datetime test
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 02/13] lua: built-in module datetime Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 04/13] test: datetime string formatting Timur Safin via Tarantool-patches
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* created app-tap test fro newly created builtin module - `datetime.lua`
---
 test/app-tap/datetime.test.lua | 89 ++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100755 test/app-tap/datetime.test.lua

diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
new file mode 100755
index 000000000..bc66b3296
--- /dev/null
+++ b/test/app-tap/datetime.test.lua
@@ -0,0 +1,89 @@
+#!/usr/bin/env tarantool
+
+local tap = require('tap')
+local test = tap.test("errno")
+local date = require('datetime')
+
+test:plan(2)
+
+test:test("Simple tests for parser", function(test)
+    test:plan(2)
+    test:ok(date("1970-01-01T01:00:00Z") ==
+            date {year=1970, month=1, day=1, hour=1, minute=0, second=0})
+    test:ok(date("1970-01-01T02:00:00+02:00") ==
+            date {year=1970, month=1, day=1, hour=2, minute=0, second=0, tz=120})
+end)
+
+test:test("Multiple tests for parser (with nanoseconds)", function(test)
+    test:plan(165)
+    -- borrowed from p5-time-moments/t/180_from_string.t
+    local tests =
+    {
+        { '1970-01-01T00:00:00Z',                       0,           0,    0 },
+        { '1970-01-01T02:00:00+02:00',                  0,           0,  120 },
+        { '1970-01-01T01:30:00+01:30',                  0,           0,   90 },
+        { '1970-01-01T01:00:00+01:00',                  0,           0,   60 },
+        { '1970-01-01T00:01:00+00:01',                  0,           0,    1 },
+        { '1970-01-01T00:00:00+00:00',                  0,           0,    0 },
+        { '1969-12-31T23:59:00-00:01',                  0,           0,   -1 },
+        { '1969-12-31T23:00:00-01:00',                  0,           0,  -60 },
+        { '1969-12-31T22:30:00-01:30',                  0,           0,  -90 },
+        { '1969-12-31T22:00:00-02:00',                  0,           0, -120 },
+        { '1970-01-01T00:00:00.123456789Z',             0,   123456789,    0 },
+        { '1970-01-01T00:00:00.12345678Z',              0,   123456780,    0 },
+        { '1970-01-01T00:00:00.1234567Z',               0,   123456700,    0 },
+        { '1970-01-01T00:00:00.123456Z',                0,   123456000,    0 },
+        { '1970-01-01T00:00:00.12345Z',                 0,   123450000,    0 },
+        { '1970-01-01T00:00:00.1234Z',                  0,   123400000,    0 },
+        { '1970-01-01T00:00:00.123Z',                   0,   123000000,    0 },
+        { '1970-01-01T00:00:00.12Z',                    0,   120000000,    0 },
+        { '1970-01-01T00:00:00.1Z',                     0,   100000000,    0 },
+        { '1970-01-01T00:00:00.01Z',                    0,    10000000,    0 },
+        { '1970-01-01T00:00:00.001Z',                   0,     1000000,    0 },
+        { '1970-01-01T00:00:00.0001Z',                  0,      100000,    0 },
+        { '1970-01-01T00:00:00.00001Z',                 0,       10000,    0 },
+        { '1970-01-01T00:00:00.000001Z',                0,        1000,    0 },
+        { '1970-01-01T00:00:00.0000001Z',               0,         100,    0 },
+        { '1970-01-01T00:00:00.00000001Z',              0,          10,    0 },
+        { '1970-01-01T00:00:00.000000001Z',             0,           1,    0 },
+        { '1970-01-01T00:00:00.000000009Z',             0,           9,    0 },
+        { '1970-01-01T00:00:00.00000009Z',              0,          90,    0 },
+        { '1970-01-01T00:00:00.0000009Z',               0,         900,    0 },
+        { '1970-01-01T00:00:00.000009Z',                0,        9000,    0 },
+        { '1970-01-01T00:00:00.00009Z',                 0,       90000,    0 },
+        { '1970-01-01T00:00:00.0009Z',                  0,      900000,    0 },
+        { '1970-01-01T00:00:00.009Z',                   0,     9000000,    0 },
+        { '1970-01-01T00:00:00.09Z',                    0,    90000000,    0 },
+        { '1970-01-01T00:00:00.9Z',                     0,   900000000,    0 },
+        { '1970-01-01T00:00:00.99Z',                    0,   990000000,    0 },
+        { '1970-01-01T00:00:00.999Z',                   0,   999000000,    0 },
+        { '1970-01-01T00:00:00.9999Z',                  0,   999900000,    0 },
+        { '1970-01-01T00:00:00.99999Z',                 0,   999990000,    0 },
+        { '1970-01-01T00:00:00.999999Z',                0,   999999000,    0 },
+        { '1970-01-01T00:00:00.9999999Z',               0,   999999900,    0 },
+        { '1970-01-01T00:00:00.99999999Z',              0,   999999990,    0 },
+        { '1970-01-01T00:00:00.999999999Z',             0,   999999999,    0 },
+        { '1970-01-01T00:00:00.0Z',                     0,           0,    0 },
+        { '1970-01-01T00:00:00.00Z',                    0,           0,    0 },
+        { '1970-01-01T00:00:00.000Z',                   0,           0,    0 },
+        { '1970-01-01T00:00:00.0000Z',                  0,           0,    0 },
+        { '1970-01-01T00:00:00.00000Z',                 0,           0,    0 },
+        { '1970-01-01T00:00:00.000000Z',                0,           0,    0 },
+        { '1970-01-01T00:00:00.0000000Z',               0,           0,    0 },
+        { '1970-01-01T00:00:00.00000000Z',              0,           0,    0 },
+        { '1970-01-01T00:00:00.000000000Z',             0,           0,    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 },
+    }
+    for _, value in ipairs(tests) do
+        local str, epoch, nsec, offset
+        str, epoch, nsec, offset = unpack(value)
+        local dt = date(str)
+        test:ok(dt.secs == epoch, ('%s: dt.secs == %d'):format(str, epoch))
+        test:ok(dt.nsec == nsec, ('%s: dt.nsec == %d'):format(str, nsec))
+        test:ok(dt.offset == offset, ('%s: dt.offset == %d'):format(str, offset))
+    end
+end)
+
+os.exit(test:check() and 0 or 1)
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 04/13] test: datetime string formatting
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (2 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 03/13] test: datetime test Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 05/13] box: add messagepack support for datetime Timur Safin via Tarantool-patches
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* Added case to check datetime string formatting using
  - asctime (gmt time);
  - ctime (local TZ time);
  - strftime (using given format).
---
 test/app-tap/datetime.test.lua | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index bc66b3296..914371747 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -4,7 +4,7 @@ local tap = require('tap')
 local test = tap.test("errno")
 local date = require('datetime')
 
-test:plan(2)
+test:plan(3)
 
 test:test("Simple tests for parser", function(test)
     test:plan(2)
@@ -86,4 +86,30 @@ test:test("Multiple tests for parser (with nanoseconds)", function(test)
     end
 end)
 
+local ffi = require('ffi')
+
+ffi.cdef [[
+    void tzset(void);
+]]
+
+test:test("Datetime string formatting", function(test)
+    test:plan(7)
+    local str = "1970-01-01"
+    local t = date(str)
+    test:ok(t.secs == 0, ('%s: t.secs == %d'):format(str, t.secs))
+    test:ok(t.nsec == 0, ('%s: t.nsec == %d'):format(str, t.nsec))
+    test:ok(t.offset == 0, ('%s: t.offset == %d'):format(str, t.offset))
+    test:ok(date.asctime(t) == 'Thu Jan  1 00:00:00 1970\n', ('%s: asctime'):format(str))
+    -- ctime() is local timezone dependent. To make sure that
+    -- test is deterministic we enforce timezone via TZ environment
+    -- manipulations and calling tzset()
+
+    -- redefine timezone to be always GMT-2
+    os.setenv('TZ', 'GMT-2')
+    ffi.C.tzset()
+    test:ok(date.ctime(t) == 'Thu Jan  1 02:00:00 1970\n', ('%s: ctime with timezone'):format(str))
+    test:ok(date.strftime('%d/%m/%Y', t) == '01/01/1970', ('%s: strftime #1'):format(str))
+    test:ok(date.strftime('%A %d. %B %Y', t) == 'Thursday 01. January 1970', ('%s: strftime #2'):format(str))
+end)
+
 os.exit(test:check() and 0 or 1)
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 05/13] box: add messagepack support for datetime
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (3 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 04/13] test: datetime string formatting Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 06/13] lua: positive/negative cases in datetime test Timur Safin via Tarantool-patches
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

EDIT HERE

* implemented mp_snprint_datetime and mp_fprint_datetime
  for generic extended messagepack types stringization

* json and yaml serialization for DATETIME data type
---
 src/box/field_def.c               |  34 +++--
 src/box/field_def.h               |   1 +
 src/box/msgpack.c                 |   7 +-
 src/box/tuple_compare.cc          |  24 ++++
 src/exports.h                     |   2 +
 src/lib/core/CMakeLists.txt       |   4 +-
 src/lib/core/datetime.h           |  37 ++++-
 src/lib/core/mp_datetime.c        | 232 ++++++++++++++++++++++++++++++
 src/lib/core/mp_extension_types.h |   1 +
 src/lib/mpstream/mpstream.c       |  11 ++
 src/lib/mpstream/mpstream.h       |   4 +
 src/lua/datetime.c                |   6 +
 src/lua/datetime.h                |   2 +-
 src/lua/msgpack.c                 |  12 ++
 src/lua/msgpackffi.lua            |   8 ++
 src/lua/serializer.c              |   4 +
 src/lua/serializer.h              |   2 +
 third_party/lua-cjson/lua_cjson.c |   8 ++
 third_party/lua-yaml/lyaml.cc     |   6 +-
 19 files changed, 385 insertions(+), 20 deletions(-)
 create mode 100644 src/lib/core/mp_datetime.c

diff --git a/src/box/field_def.c b/src/box/field_def.c
index 51acb8025..33606a52d 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -67,11 +67,12 @@ const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_VARBINARY] =  */ 1U << MP_BIN,
 	/* [FIELD_TYPE_SCALAR]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
 		(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_STR) |
-		(1U << MP_BIN) | (1U << MP_BOOL),
+		(1U << MP_BIN) | (1U << MP_BOOL) | (1U << MP_DATETIME),
 	/* [FIELD_TYPE_DECIMAL]  =  */ 0, /* only MP_DECIMAL is supported */
 	/* [FIELD_TYPE_UUID]     =  */ 0, /* only MP_UUID is supported */
 	/* [FIELD_TYPE_ARRAY]    =  */ 1U << MP_ARRAY,
 	/* [FIELD_TYPE_MAP]      =  */ (1U << MP_MAP),
+	/* [FIELD_TYPE_DATETIME] =  */ 0, /* only MP_DATETIME is supported */
 };
 
 const uint32_t field_ext_type[] = {
@@ -88,6 +89,7 @@ const uint32_t field_ext_type[] = {
 	/* [FIELD_TYPE_UUID]      = */ 1U << MP_UUID,
 	/* [FIELD_TYPE_ARRAY]     = */ 0,
 	/* [FIELD_TYPE_MAP]       = */ 0,
+	/* [FIELD_TYPE_DATETIME]  =  */ 1U << MP_DATETIME,
 };
 
 const char *field_type_strs[] = {
@@ -104,6 +106,7 @@ const char *field_type_strs[] = {
 	/* [FIELD_TYPE_UUID]     = */ "uuid",
 	/* [FIELD_TYPE_ARRAY]    = */ "array",
 	/* [FIELD_TYPE_MAP]      = */ "map",
+	/* [FIELD_TYPE_DATETIME] = */ "datetime",
 };
 
 const char *on_conflict_action_strs[] = {
@@ -128,20 +131,21 @@ field_type_by_name_wrapper(const char *str, uint32_t len)
  * values can be stored in the j type.
  */
 static const bool field_type_compatibility[] = {
-	   /*   ANY   UNSIGNED  STRING   NUMBER  DOUBLE  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL   UUID    ARRAY    MAP  */
-/*   ANY    */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,   false,
-/* UNSIGNED */ true,   true,    false,   true,    false,   true,    false,   false,  true,   false,  false,   false,   false,
-/*  STRING  */ true,   false,   true,    false,   false,   false,   false,   false,  true,   false,  false,   false,   false,
-/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,   false,
-/*  DOUBLE  */ true,   false,   false,   true,    true,    false,   false,   false,  true,   false,  false,   false,   false,
-/*  INTEGER */ true,   false,   false,   true,    false,   true,    false,   false,  true,   false,  false,   false,   false,
-/*  BOOLEAN */ true,   false,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,   false,
-/* VARBINARY*/ true,   false,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,   false,
-/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,   false,
-/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,   false,  true,   true,   false,   false,   false,
-/*   UUID   */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,   false,
-/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,    false,
-/*    MAP   */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,   true,
+	   /*   ANY   UNSIGNED  STRING   NUMBER  DOUBLE  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL   UUID    ARRAY    MAP     DATETIME */
+/*   ANY    */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,   false,   false,
+/* UNSIGNED */ true,   true,    false,   true,    false,   true,    false,   false,  true,   false,  false,   false,   false,   false,
+/*  STRING  */ true,   false,   true,    false,   false,   false,   false,   false,  true,   false,  false,   false,   false,   false,
+/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,   false,   false,
+/*  DOUBLE  */ true,   false,   false,   true,    true,    false,   false,   false,  true,   false,  false,   false,   false,   false,
+/*  INTEGER */ true,   false,   false,   true,    false,   true,    false,   false,  true,   false,  false,   false,   false,   false,
+/*  BOOLEAN */ true,   false,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,   false,   false,
+/* VARBINARY*/ true,   false,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,   false,   false,
+/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,   false,   false,
+/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,   false,  true,   true,   false,   false,   false,   false,
+/*   UUID   */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,   false,   false,
+/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,    false,   false,
+/*    MAP   */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,   true,    false,
+/* DATETIME */ true,   false,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,   false,   true,
 };
 
 bool
diff --git a/src/box/field_def.h b/src/box/field_def.h
index c5cfe5e86..120b2a93d 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -63,6 +63,7 @@ enum field_type {
 	FIELD_TYPE_UUID,
 	FIELD_TYPE_ARRAY,
 	FIELD_TYPE_MAP,
+	FIELD_TYPE_DATETIME,
 	field_type_MAX
 };
 
diff --git a/src/box/msgpack.c b/src/box/msgpack.c
index 1723dea4c..e53af548c 100644
--- a/src/box/msgpack.c
+++ b/src/box/msgpack.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020, Tarantool AUTHORS, please see AUTHORS file.
+ * Copyright 2020-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
@@ -35,6 +35,7 @@
 #include "mp_decimal.h"
 #include "uuid/mp_uuid.h"
 #include "mp_error.h"
+#include "datetime.h"
 
 static int
 msgpack_fprint_ext(FILE *file, const char **data, int depth)
@@ -47,6 +48,8 @@ msgpack_fprint_ext(FILE *file, const char **data, int depth)
 		return mp_fprint_decimal(file, data, len);
 	case MP_UUID:
 		return mp_fprint_uuid(file, data, len);
+	case MP_DATETIME:
+		return mp_fprint_datetime(file, data, len);
 	case MP_ERROR:
 		return mp_fprint_error(file, data, depth);
 	default:
@@ -65,6 +68,8 @@ msgpack_snprint_ext(char *buf, int size, const char **data, int depth)
 		return mp_snprint_decimal(buf, size, data, len);
 	case MP_UUID:
 		return mp_snprint_uuid(buf, size, data, len);
+	case MP_DATETIME:
+		return mp_snprint_datetime(buf, size, data, len);
 	case MP_ERROR:
 		return mp_snprint_error(buf, size, data, depth);
 	default:
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 98938fb39..39c9dd6e9 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -36,6 +36,7 @@
 #include "lib/core/decimal.h"
 #include "lib/core/mp_decimal.h"
 #include "uuid/mp_uuid.h"
+#include "core/datetime.h"
 #include "lib/core/mp_extension_types.h"
 
 /* {{{ tuple_compare */
@@ -76,6 +77,7 @@ enum mp_class {
 	MP_CLASS_STR,
 	MP_CLASS_BIN,
 	MP_CLASS_UUID,
+	MP_CLASS_DATETIME,
 	MP_CLASS_ARRAY,
 	MP_CLASS_MAP,
 	mp_class_max,
@@ -99,6 +101,8 @@ static enum mp_class mp_ext_classes[] = {
 	/* .MP_UNKNOWN_EXTENSION = */ mp_class_max, /* unsupported */
 	/* .MP_DECIMAL		 = */ MP_CLASS_NUMBER,
 	/* .MP_UUID		 = */ MP_CLASS_UUID,
+	/* .MP_ERROR		 = */ mp_class_max,
+	/* .MP_DATETIME		 = */ MP_CLASS_DATETIME,
 };
 
 static enum mp_class
@@ -299,6 +303,8 @@ mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
 			return mp_compare_decimal_any_number(
 				decimal_unpack(&rhs, len, &dec), lhs, lhs_type, -1
 			);
+		case MP_DATETIME:
+			// FIXME
 		default:
 			unreachable();
 		}
@@ -311,6 +317,8 @@ mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
 			return mp_compare_decimal_any_number(
 				decimal_unpack(&lhs, len, &dec), rhs, rhs_type, 1
 			);
+		case MP_DATETIME:
+			// FIXME
 		default:
 			unreachable();
 		}
@@ -390,6 +398,19 @@ mp_compare_uuid(const char *field_a, const char *field_b)
 	return memcmp(field_a + 2, field_b + 2, UUID_PACKED_LEN);
 }
 
+static int
+mp_compare_datetime(const char *lhs, const char *rhs)
+{
+	t_datetime_tz lhs_dt, rhs_dt;
+	t_datetime_tz *ret;
+	ret = mp_decode_datetime(&lhs, &lhs_dt);
+	assert(ret != NULL);
+	ret = mp_decode_datetime(&rhs, &rhs_dt);
+	assert(ret != NULL);
+	(void)ret;
+	return datetime_compare(&lhs_dt, &rhs_dt);
+}
+
 typedef int (*mp_compare_f)(const char *, const char *);
 static mp_compare_f mp_class_comparators[] = {
 	/* .MP_CLASS_NIL    = */ NULL,
@@ -398,6 +419,7 @@ static mp_compare_f mp_class_comparators[] = {
 	/* .MP_CLASS_STR    = */ mp_compare_str,
 	/* .MP_CLASS_BIN    = */ mp_compare_bin,
 	/* .MP_CLASS_UUID   = */ mp_compare_uuid,
+	/* .MP_CLASS_DATETIME=*/ mp_compare_datetime,
 	/* .MP_CLASS_ARRAY  = */ NULL,
 	/* .MP_CLASS_MAP    = */ NULL,
 };
@@ -478,6 +500,8 @@ tuple_compare_field(const char *field_a, const char *field_b,
 		return mp_compare_decimal(field_a, field_b);
 	case FIELD_TYPE_UUID:
 		return mp_compare_uuid(field_a, field_b);
+	case FIELD_TYPE_DATETIME:
+		return mp_compare_datetime(field_a, field_b);
 	default:
 		unreachable();
 		return 0;
diff --git a/src/exports.h b/src/exports.h
index db40c03a4..7397010e0 100644
--- a/src/exports.h
+++ b/src/exports.h
@@ -531,6 +531,8 @@ EXPORT(uri_format)
 EXPORT(uri_parse)
 EXPORT(uuid_nil)
 EXPORT(uuid_unpack)
+EXPORT(datetime_unpack)
+EXPORT(datetime_pack)
 EXPORT(dt_from_rdn)
 EXPORT(dt_from_yd)
 EXPORT(dt_from_ymd)
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 2cd4d0b4f..f1801dcb0 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
+    mp_datetime.c
 )
 
 if (TARGET_OS_NETBSD)
@@ -43,7 +44,8 @@ add_library(core STATIC ${core_sources})
 
 target_link_libraries(core salad small uri decNumber bit ${LIBEV_LIBRARIES}
                       ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES}
-                      ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES})
+                      ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES}
+                      ${LIBCDT_LIBRARIES})
 
 if (ENABLE_BACKTRACE AND NOT TARGET_OS_DARWIN)
     target_link_libraries(core gcc_s ${UNWIND_LIBRARIES})
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
index 837ed346c..bd23f33a3 100644
--- a/src/lib/core/datetime.h
+++ b/src/lib/core/datetime.h
@@ -30,14 +30,20 @@
  * SUCH DAMAGE.
  */
 
-#include <c-dt/dt_core.h>
+#include <c-dt/dt.h>
 #include <stdint.h>
 #include <stdbool.h>
+#include <stdio.h>
 
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+#ifndef SECS_PER_DAY
+#define SECS_PER_DAY	86400
+#define NANOS_PER_SEC	1000000000
+#endif
+
 /**
  * datetime structure consisting of:
  */
@@ -55,6 +61,35 @@ struct t_datetime_duration {
 	int nsec;	///< nanoseconds delta
 };
 
+int
+datetime_compare(const struct t_datetime_tz * lhs,
+		 const struct t_datetime_tz * rhs);
+
+
+struct t_datetime_tz *
+datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date);
+
+char *
+datetime_pack(char *data, const struct t_datetime_tz *date);
+
+uint32_t
+mp_sizeof_datetime(const struct t_datetime_tz *date);
+
+struct t_datetime_tz *
+mp_decode_datetime(const char **data, struct t_datetime_tz *date);
+
+char *
+mp_encode_datetime(char *data, const struct t_datetime_tz *date);
+
+int
+datetime_to_string(const struct t_datetime_tz * 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/lib/core/mp_datetime.c b/src/lib/core/mp_datetime.c
new file mode 100644
index 000000000..2cf3fd79b
--- /dev/null
+++ b/src/lib/core/mp_datetime.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "trivia/util.h"
+#include "datetime.h"
+#include "msgpuck.h"
+#include "mp_extension_types.h"
+
+static inline uint32_t
+mp_sizeof_Xint(int64_t n)
+{
+	return n < 0 ? mp_sizeof_int(n) : mp_sizeof_uint(n);
+}
+
+static inline char *
+mp_encode_Xint(char *data, int64_t v)
+{
+	return v < 0 ? mp_encode_int(data, v) : mp_encode_uint(data, v);
+}
+	
+static inline int64_t
+mp_decode_Xint(const char **data)
+{
+	switch (mp_typeof(**data)) {
+	case MP_UINT:
+		return (int64_t)mp_decode_uint(data);
+	case MP_INT:
+		return mp_decode_int(data);
+	default:
+		mp_unreachable();
+	}
+	return 0;
+}
+
+uint32_t
+mp_sizeof_datetime(const struct t_datetime_tz *date)
+{
+	uint32_t sz = mp_sizeof_Xint(date->secs);
+
+	// even if nanosecs == 0 we need to output anything
+	// if we have non-null tz offset
+	if (date->nsec != 0 || date->offset != 0)
+		sz += mp_sizeof_Xint(date->nsec);
+	if (date->offset)
+		sz += mp_sizeof_Xint(date->offset);
+
+	return sz;
+}
+
+struct t_datetime_tz *
+datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date)
+{
+	const char * svp = *data;
+
+	memset(date, 0, sizeof(*date));
+
+	date->secs = mp_decode_Xint(data);
+
+	len -= *data - svp;
+	if (len <= 0)
+		return date;
+
+	svp = *data;
+	date->secs = mp_decode_Xint(data);
+	len -= *data - svp;
+
+	if (len <= 0)
+		return date;
+
+	date->offset = mp_decode_Xint(data);
+
+	return date;
+}
+
+struct t_datetime_tz *
+mp_decode_datetime(const char **data, struct t_datetime_tz *date)
+{
+	if (mp_typeof(**data) != MP_EXT)
+		return NULL;
+
+	int8_t type;
+	uint32_t len = mp_decode_extl(data, &type);
+
+	if (type != MP_DATETIME || len == 0) {
+		return NULL;
+	}
+	return datetime_unpack(data, len, date);
+}
+
+char *
+datetime_pack(char *data, const struct t_datetime_tz *date)
+{
+	data = mp_encode_Xint(data, date->secs);
+	if (date->nsec != 0 || date->offset != 0)
+		data = mp_encode_Xint(data, date->nsec);
+	if (date->offset)
+		data = mp_encode_Xint(data, date->offset);
+
+	return data;
+}
+
+char *
+mp_encode_datetime(char *data, const struct t_datetime_tz *date)
+{
+	uint32_t len = mp_sizeof_datetime(date);
+
+	data = mp_encode_extl(data, MP_DATETIME, len);
+
+	return datetime_pack(data, date);
+}
+
+int
+datetime_to_string(const struct t_datetime_tz * date, char *buf, uint32_t len)
+{
+	char * src = buf;
+	int offset = date->offset;
+	int secs = date->secs + offset * 60;
+	dt_t dt = dt_from_rdn((secs / SECS_PER_DAY) + 719163);
+
+	int year, month, day, sec, ns, sign;
+	dt_to_ymd(dt, &year, &month, &day);
+
+	int hour = (secs / 3600) % 24,
+	    minute = (secs / 60) % 60;
+	;
+	sec = secs % 60;
+	ns = date->nsec;
+	uint32_t sz;
+	sz = snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d",
+		      year, month, day, hour, minute);
+	buf += sz; len -= sz;
+	if (sec || ns) {
+		sz = snprintf(buf, len, ":%02d", sec);
+		buf += sz; len -= sz;
+		if (ns) {
+			if ((ns % 1000000) == 0)
+				sz = snprintf(buf, len, ".%03d", ns / 1000000);
+			else if ((ns % 1000) == 0)
+				sz = snprintf(buf, len, ".%06d", ns / 1000);
+			else
+				sz = snprintf(buf, len, ".%09d", ns);
+			buf += sz; len -= sz;
+		}
+	}
+	if (offset == 0) {
+		strncpy(buf, "Z", len);
+		buf++;
+		len--;
+	}
+	else {
+		if (offset < 0)
+			sign = '-', offset = -offset;
+		else
+			sign = '+';
+
+		sz = snprintf(buf, len, "%c%02d:%02d", sign, offset / 60, offset % 60);
+		buf += sz; len -= sz;
+	}
+	return (buf - src);
+}
+int
+mp_snprint_datetime(char *buf, int size, const char **data, uint32_t len)
+{
+	struct t_datetime_tz date = {0};
+
+	if (datetime_unpack(data, len, &date) == NULL)
+		return -1;
+
+	return datetime_to_string(&date, buf, size);
+}
+
+int
+mp_fprint_datetime(FILE *file, const char **data, uint32_t len)
+{
+	struct  t_datetime_tz date;
+
+	if (datetime_unpack(data, len, &date) == NULL)
+		return -1;
+
+	char buf[128];
+	datetime_to_string(&date, buf, sizeof buf);
+
+	return fprintf(file, "%s", buf);
+}
+
+static inline int
+adjusted_secs(int secs, int offset)
+{
+	return secs - offset * 60;
+}
+
+int
+datetime_compare(const struct t_datetime_tz * lhs,
+		 const struct t_datetime_tz * rhs)
+{
+	int result = COMPARE_RESULT(adjusted_secs(lhs->secs, lhs->offset),
+				    adjusted_secs(rhs->secs, rhs->offset));
+	if (result != 0)
+		return result;
+
+	return COMPARE_RESULT(lhs->nsec, rhs->nsec);
+}
diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
index e3ff9f5d0..3b7eaee7c 100644
--- a/src/lib/core/mp_extension_types.h
+++ b/src/lib/core/mp_extension_types.h
@@ -44,6 +44,7 @@ enum mp_extension_type {
     MP_DECIMAL = 1,
     MP_UUID = 2,
     MP_ERROR = 3,
+    MP_DATETIME = 4,
     mp_extension_type_MAX,
 };
 
diff --git a/src/lib/mpstream/mpstream.c b/src/lib/mpstream/mpstream.c
index 70ca29889..8c92b4049 100644
--- a/src/lib/mpstream/mpstream.c
+++ b/src/lib/mpstream/mpstream.c
@@ -35,6 +35,7 @@
 #include "msgpuck.h"
 #include "mp_decimal.h"
 #include "uuid/mp_uuid.h"
+#include "core/datetime.h"
 
 void
 mpstream_reserve_slow(struct mpstream *stream, size_t size)
@@ -208,6 +209,16 @@ mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid)
 	mpstream_advance(stream, pos - data);
 }
 
+void
+mpstream_encode_datetime(struct mpstream *stream, const struct t_datetime_tz *val)
+{
+	char *data = mpstream_reserve(stream, mp_sizeof_datetime(val));
+	if (data == NULL)
+		return;
+	char *pos = mp_encode_datetime(data, val);
+	mpstream_advance(stream, pos - data);
+}
+
 void
 mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n)
 {
diff --git a/src/lib/mpstream/mpstream.h b/src/lib/mpstream/mpstream.h
index a60add143..39e8fd956 100644
--- a/src/lib/mpstream/mpstream.h
+++ b/src/lib/mpstream/mpstream.h
@@ -39,6 +39,7 @@ extern "C" {
 #endif /* defined(__cplusplus) */
 
 struct tt_uuid;
+struct t_datetime_tz;
 
 /**
 * Ask the allocator to reserve at least size bytes. It can reserve
@@ -145,6 +146,9 @@ mpstream_encode_decimal(struct mpstream *stream, const decimal_t *val);
 void
 mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid);
 
+void
+mpstream_encode_datetime(struct mpstream *stream, const struct t_datetime_tz *dt);
+
 /** Copies n bytes from memory area src to stream. */
 void
 mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n);
diff --git a/src/lua/datetime.c b/src/lua/datetime.c
index 37a8bc020..215cb89fb 100644
--- a/src/lua/datetime.c
+++ b/src/lua/datetime.c
@@ -39,6 +39,12 @@
 uint32_t CTID_DATETIME_TZ = 0;
 uint32_t CTID_DURATION = 0;
 
+struct t_datetime_tz *
+luaL_pushdatetime(struct lua_State *L)
+{
+	return luaL_pushcdata(L, CTID_DATETIME_TZ);
+}
+
 void
 tarantool_lua_datetime_init(struct lua_State *L)
 {
diff --git a/src/lua/datetime.h b/src/lua/datetime.h
index d290a6d13..38ba53561 100644
--- a/src/lua/datetime.h
+++ b/src/lua/datetime.h
@@ -43,7 +43,7 @@ extern uint32_t CTID_DURATION;
 struct lua_State;
 
 struct t_datetime_tz*
-lua_pushdatetime(struct lua_State *L);
+luaL_pushdatetime(struct lua_State *L);
 
 void
 tarantool_lua_datetime_init(struct lua_State *L);
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index b6ecf2b1e..dd2c41056 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -46,6 +46,7 @@
 #include "lib/core/decimal.h" /* decimal_unpack() */
 #include "lib/uuid/mp_uuid.h" /* mp_decode_uuid() */
 #include "lib/core/mp_extension_types.h"
+#include "datetime.h"
 
 #include "cord_buf.h"
 #include <fiber.h>
@@ -200,6 +201,9 @@ restart: /* used by MP_EXT of unidentified subtype */
 			break;
 		case MP_ERROR:
 			return luamp_encode_extension(L, top, stream);
+		case MP_DATETIME:
+			mpstream_encode_datetime(stream, field->dateval);
+			break;
 		default:
 			/* Run trigger if type can't be encoded */
 			type = luamp_encode_extension(L, top, stream);
@@ -333,6 +337,14 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
 				goto ext_decode_err;
 			return;
 		}
+		case MP_DATETIME:
+		{
+			struct t_datetime_tz * date = luaL_pushdatetime(L);
+			date = datetime_unpack(data, len, date);
+			if (date == NULL)
+				goto ext_decode_err;
+			return;
+		}
 		default:
 			/* reset data to the extension header */
 			*data = svp;
diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index 1d54f11b8..2613cf216 100644
--- a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -36,6 +36,8 @@ decimal_t *
 decimal_unpack(const char **data, uint32_t len, decimal_t *dec);
 struct tt_uuid *
 uuid_unpack(const char **data, uint32_t len, struct tt_uuid *uuid);
+struct t_datetime_tz *
+datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date);
 ]])
 
 local strict_alignment = (jit.arch == 'arm')
@@ -513,6 +515,12 @@ local ext_decoder = {
         builtin.uuid_unpack(data, len, uuid)
         return uuid
     end,
+    -- MP_DATETIME
+    [4] = function(data, len)
+        local dt = ffi.new("struct t_datetime_tz")
+        builtin.datetime_unpack(data, len, dt)
+        return dt
+    end,
 }
 
 local function decode_ext(data)
diff --git a/src/lua/serializer.c b/src/lua/serializer.c
index 8db6746a3..55120a725 100644
--- a/src/lua/serializer.c
+++ b/src/lua/serializer.c
@@ -41,6 +41,7 @@
 #include "lib/core/mp_extension_types.h"
 #include "lua/error.h"
 
+#include "datetime.h"
 #include "trivia/util.h"
 #include "diag.h"
 #include "serializer_opts.h"
@@ -540,6 +541,9 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 			} else if (cd->ctypeid == CTID_UUID) {
 				field->ext_type = MP_UUID;
 				field->uuidval = (struct tt_uuid *) cdata;
+			} else if (cd->ctypeid == CTID_DATETIME_TZ) {
+				field->ext_type = MP_DATETIME;
+				field->dateval = (struct t_datetime_tz *) cdata;
 			} else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF &&
 				   opts != NULL &&
 				   opts->error_marshaling_enabled) {
diff --git a/src/lua/serializer.h b/src/lua/serializer.h
index 0a0501a74..ec62c723f 100644
--- a/src/lua/serializer.h
+++ b/src/lua/serializer.h
@@ -52,6 +52,7 @@ extern "C" {
 #include <lauxlib.h>
 
 #include "trigger.h"
+#include "lib/core/datetime.h"
 #include "lib/core/decimal.h" /* decimal_t */
 #include "lib/core/mp_extension_types.h"
 #include "lua/error.h"
@@ -223,6 +224,7 @@ struct luaL_field {
 		uint32_t size;
 		decimal_t *decval;
 		struct tt_uuid *uuidval;
+		struct t_datetime_tz *dateval;
 	};
 	enum mp_type type;
 	/* subtypes of MP_EXT */
diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c
index 5123b9a74..d5ddc4ea0 100644
--- a/third_party/lua-cjson/lua_cjson.c
+++ b/third_party/lua-cjson/lua_cjson.c
@@ -52,6 +52,7 @@
 #include "mp_extension_types.h" /* MP_DECIMAL, MP_UUID */
 #include "tt_static.h"
 #include "uuid/tt_uuid.h" /* tt_uuid_to_string(), UUID_STR_LEN */
+#include "core/datetime.h"
 #include "cord_buf.h"
 
 typedef enum {
@@ -426,6 +427,13 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg,
         case MP_UUID:
             return json_append_string(cfg, json, tt_uuid_str(field.uuidval),
                                       UUID_STR_LEN);
+
+        case MP_DATETIME:
+        {
+            char buf[128];
+            size_t sz = datetime_to_string(field.dateval, buf, sizeof buf);
+            return json_append_string(cfg, json, buf, sz);
+        }
         default:
             assert(false);
         }
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 5469e9f4f..b76a45dfb 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -617,7 +617,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
    yaml_event_t ev;
    yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
    int is_binary = 0;
-   char buf[FPCONV_G_FMT_BUFSIZE];
+   char buf[FPCONV_G_FMT_BUFSIZE + 8]; // FIXME - need extra space for datetime
    struct luaL_field field;
    bool unused;
    (void) unused;
@@ -707,6 +707,10 @@ static int dump_node(struct lua_yaml_dumper *dumper)
          str = tt_uuid_str(field.uuidval);
          len = UUID_STR_LEN;
          break;
+      case MP_DATETIME:
+         len = datetime_to_string(field.dateval, buf, sizeof buf);
+         str = buf;
+         break;
       default:
          assert(0); /* checked by luaL_checkfield() */
       }
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 06/13] lua: positive/negative cases in datetime test
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (4 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 05/13] box: add messagepack support for datetime Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 07/13] lua: asctime and strfime fixed Timur Safin via Tarantool-patches
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

- extended api of datetime.parse_date, .parse_time, .parse_time_zone
  with the length of parsed (sub)string.
- This allow us to check partially valid strings like "20121224 Foo bar"
  in the updated test app-tap/datetime.test.lua
---
 src/lua/datetime.lua           | 19 ++++-----
 test/app-tap/datetime.test.lua | 78 +++++++++++++++++++++++++++++++++-
 2 files changed, 85 insertions(+), 12 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index cffa38cd5..670123b1f 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -318,7 +318,7 @@ local function datetime_new(o)
             end,
 
             month = function(v)
-                assert(v > 0 and v < 12 )
+                assert(v > 0 and v < 13 )
                 M = v
                 ymd = true
             end,
@@ -393,9 +393,8 @@ end
 
 local function parse_date(str)
     local dt = ffi.new('dt_t[1]')
-    local rc = cdt.dt_parse_iso_date(str, #str, dt)
-    assert(rc > 0)
-    return mk_timestamp(dt[0])
+    local len = cdt.dt_parse_iso_date(str, #str, dt)
+    return len > 0 and mk_timestamp(dt[0]) or nil, tonumber(len)
 end
 
 --[[
@@ -411,9 +410,8 @@ end
 local function parse_time(str)
     local sp = ffi.new('int[1]')
     local fp = ffi.new('int[1]')
-    local rc = cdt.dt_parse_iso_time(str, #str, sp, fp)
-    assert(rc > 0)
-    return mk_timestamp(nil, sp[0], fp[0])
+    local len = cdt.dt_parse_iso_time(str, #str, sp, fp)
+    return len > 0 and mk_timestamp(nil, sp[0], fp[0]) or nil, tonumber(len)
 end
 
 --[[
@@ -424,9 +422,8 @@ end
 ]]
 local function parse_zone(str)
     local offset = ffi.new('int[1]')
-    local rc = cdt.dt_parse_iso_zone(str, #str, offset)
-    assert(rc > 0)
-    return mk_timestamp(nil, nil, nil, offset[0])
+    local len = cdt.dt_parse_iso_zone(str, #str, offset)
+    return len > 0 and mk_timestamp(nil, nil, nil, offset[0]) or nil, tonumber(len)
 end
 
 
@@ -449,7 +446,7 @@ local function parse_str(str)
     str = str:sub(tonumber(n) + 1)
 
     local ch = str:sub(1,1)
-    if ch ~= 't' and ch ~= 'T' and ch ~= ' ' then
+    if ch:match('[Tt ]') == nil then
         return mk_timestamp(dt_)
     end
 
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 914371747..b79450ecc 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -4,7 +4,7 @@ local tap = require('tap')
 local test = tap.test("errno")
 local date = require('datetime')
 
-test:plan(3)
+test:plan(5)
 
 test:test("Simple tests for parser", function(test)
     test:plan(2)
@@ -112,4 +112,80 @@ test:test("Datetime string formatting", function(test)
     test:ok(date.strftime('%A %d. %B %Y', t) == 'Thursday 01. January 1970', ('%s: strftime #2'):format(str))
 end)
 
+test:test("Parse iso date - valid strings", function(test)
+    test:plan(32)
+    local good = {
+        {2012, 12, 24, "20121224",                   8 },
+        {2012, 12, 24, "20121224  Foo bar",          8 },
+        {2012, 12, 24, "2012-12-24",                10 },
+        {2012, 12, 24, "2012-12-24 23:59:59",       10 },
+        {2012, 12, 24, "2012-12-24T00:00:00+00:00", 10 },
+        {2012, 12, 24, "2012359",                    7 },
+        {2012, 12, 24, "2012359T235959+0130",        7 },
+        {2012, 12, 24, "2012-359",                   8 },
+        {2012, 12, 24, "2012W521",                   8 },
+        {2012, 12, 24, "2012-W52-1",                10 },
+        {2012, 12, 24, "2012Q485",                   8 },
+        {2012, 12, 24, "2012-Q4-85",                10 },
+        {   1,  1,  1, "0001-Q1-01",                10 },
+        {   1,  1,  1, "0001-W01-1",                10 },
+        {   1,  1,  1, "0001-01-01",                10 },
+        {   1,  1,  1, "0001-001",                   8 },
+    }
+
+    for _, value in ipairs(good) do
+        local year, month, day, str, date_part_len;
+        year, month, day, str, date_part_len = unpack(value)
+        local expected_date = date{year = year, month = month, day = day}
+        local date_part, len
+        date_part, len = date.parse_date(str)
+        test:ok(len == date_part_len, ('%s: length check %d'):format(str, len))
+        test:ok(expected_date == date_part, ('%s: expected date'):format(str))
+    end
+end)
+
+test:test("Parse iso date - invalid strings", function(test)
+    test:plan(62)
+    local bad = {
+        "20121232"   , -- Invalid day of month
+        "2012-12-310", -- Invalid day of month
+        "2012-13-24" , -- Invalid month
+        "2012367"    , -- Invalid day of year
+        "2012-000"   , -- Invalid day of year
+        "2012W533"   , -- Invalid week of year
+        "2012-W52-8" , -- Invalid day of week
+        "2012Q495"   , -- Invalid day of quarter
+        "2012-Q5-85" , -- Invalid quarter
+        "20123670"   , -- Trailing digit
+        "201212320"  , -- Trailing digit
+        "2012-12"    , -- Reduced accuracy
+        "2012-Q4"    , -- Reduced accuracy
+        "2012-Q42"   , -- Invalid
+        "2012-Q1-1"  , -- Invalid day of quarter
+        "2012Q--420" , -- Invalid
+        "2012-Q-420" , -- Invalid
+        "2012Q11"    , -- Incomplete
+        "2012Q1234"  , -- Trailing digit
+        "2012W12"    , -- Incomplete
+        "2012W1234"  , -- Trailing digit
+        "2012W-123"  , -- Invalid
+        "2012-W12"   , -- Incomplete
+        "2012-W12-12", -- Trailing digit
+        "2012U1234"  , -- Invalid
+        "2012-1234"  , -- Invalid
+        "2012-X1234" , -- Invalid
+        "0000-Q1-01" , -- Year less than 0001
+        "0000-W01-1" , -- Year less than 0001
+        "0000-01-01" , -- Year less than 0001
+        "0000-001"   , -- Year less than 0001
+    }
+
+    for _, str in ipairs(bad) do
+        local date_part, len
+        date_part, len = date.parse_date(str)
+        test:ok(len == 0, ('%s: length check %d'):format(str, len))
+        test:ok(date_part == nil, ('%s: empty date check %s'):format(str, date_part))
+    end
+end)
+
 os.exit(test:check() and 0 or 1)
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 07/13] lua: asctime and strfime fixed
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (5 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 06/13] lua: positive/negative cases in datetime test Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 08/13] box, lua: renamed t_datetime_tz structure to datetime_t Timur Safin via Tarantool-patches
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* fixed lua implementation for asctime, and strftime;
* introduced c test for reversible strftime roundtrip;
* introduced lua test for the same (TBD)
---
 src/lua/datetime.lua      |  30 ++-
 test/unit/datetime.c      |  56 ++++-
 test/unit/datetime.result | 460 +++++++++++++++++++++++++++-----------
 3 files changed, 408 insertions(+), 138 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 670123b1f..efd953de9 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -422,7 +422,7 @@ end
 ]]
 local function parse_zone(str)
     local offset = ffi.new('int[1]')
-    local len = cdt.dt_parse_iso_zone(str, #str, offset)
+    local len = cdt.dt_parse_iso_zone_lenient(str, #str, offset)
     return len > 0 and mk_timestamp(nil, nil, nil, offset[0]) or nil, tonumber(len)
 end
 
@@ -474,7 +474,7 @@ local function parse_str(str)
     len = #str
 
     local offset = ffi.new('int[1]')
-    n = cdt.dt_parse_iso_zone(str, len, offset)
+    n = cdt.dt_parse_iso_zone_lenient(str, len, offset)
     if n == 0 then
         return mk_timestamp(dt_, sp_, fp_)
     end
@@ -507,10 +507,29 @@ local function local_now()
     return datetime_new_raw(secs, nsec, ofs) -- FIXME
 end
 
-local function asctime(o)
-    assert(ffi.typeof(o) == datetime_t)
+local function datetime_to_tm_ptr(o)
     local p_tm = ffi.new 'struct tm[1]'
+    assert(ffi.typeof(o) == datetime_t)
+    -- dt_to_struct_tm() fills only date data
     cdt.dt_to_struct_tm(local_dt(o), p_tm)
+
+    -- calculate the smaller data (hour, minute,
+    -- seconds) using datetime seconds value
+    local seconds_of_day = o.secs % 86400
+    local hour = (seconds_of_day / 3600) % 24
+    local minute = (seconds_of_day / 60) % 60
+    p_tm[0].tm_sec = seconds_of_day % 60
+    p_tm[0].tm_min = minute
+    p_tm[0].tm_hour = hour
+
+    p_tm[0].tm_gmtoff = o.offset * 60
+
+    return p_tm
+end
+
+local function asctime(o)
+    assert(ffi.typeof(o) == datetime_t)
+    local p_tm = datetime_to_tm_ptr(o)
     return ffi.string(native.asctime(p_tm))
 end
 
@@ -525,8 +544,7 @@ local function strftime(fmt, o)
     assert(ffi.typeof(o) == datetime_t)
     local sz = 50
     local buff = ffi.new('char[?]', sz)
-    local p_tm = ffi.new 'struct tm[1]'
-    cdt.dt_to_struct_tm(local_dt(o), p_tm)
+    local p_tm = datetime_to_tm_ptr(o)
     native.strftime(buff, sz, fmt, p_tm)
     return ffi.string(buff)
 end
diff --git a/test/unit/datetime.c b/test/unit/datetime.c
index 25c9c1f1a..f74ac139d 100644
--- a/test/unit/datetime.c
+++ b/test/unit/datetime.c
@@ -2,6 +2,7 @@
 #include <assert.h>
 #include <stdint.h>
 #include <string.h>
+#include <time.h>
 
 #include "unit.h"
 
@@ -125,6 +126,46 @@ parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 	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 t_datetime_tz {
+	int64_t sec;
+	int64_t nsec;
+	int64_t offset;
+};
+
+static int
+local_rd(const struct t_datetime_tz * dt) {
+	return (int)(dt->sec / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET;
+}
+
+static int
+local_dt(const struct t_datetime_tz * dt) {
+	return dt_from_rdn(local_rd(dt));
+}
+
+
+struct tm*
+datetime_to_tm(struct t_datetime_tz * dt)
+{
+	static struct tm tm;
+
+	memset(&tm, 0, sizeof(tm));
+	dt_to_struct_tm(local_dt(dt), &tm);
+
+	int seconds_of_day = dt->sec % 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;
@@ -132,7 +173,7 @@ static void datetime_test(void)
 	int64_t nanosecs;
 	int64_t ofs;
 
-	plan(132);
+	plan(330);
 	parse_datetime(sample, sizeof(sample) - 1,
 		       &secs_expected, &nanosecs, &ofs);
 
@@ -144,6 +185,19 @@ static void datetime_test(void)
 		   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 t_datetime_tz 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.sec, &dt.nsec, &dt.offset);
+		is(rc, 0, "correct parse_datetime return value for '%s'", buff);
+		is(secs, dt.sec,
+		   "reversible seconds via strftime for '%s", buff);
 	}
 }
 
diff --git a/test/unit/datetime.result b/test/unit/datetime.result
index 5cd68dea5..453897769 100644
--- a/test/unit/datetime.result
+++ b/test/unit/datetime.result
@@ -1,135 +1,333 @@
 1..1
-    1..132
+    1..330
     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 - correct parse_datetime return value for '2012-12-24 15:30z'
-    ok 4 - correct parse_datetime output seconds for '2012-12-24 15:30z
-    ok 5 - correct parse_datetime return value for '2012-12-24 16:30+01:00'
-    ok 6 - correct parse_datetime output seconds for '2012-12-24 16:30+01:00
-    ok 7 - correct parse_datetime return value for '2012-12-24 16:30+0100'
-    ok 8 - correct parse_datetime output seconds for '2012-12-24 16:30+0100
-    ok 9 - correct parse_datetime return value for '2012-12-24 16:30+01'
-    ok 10 - correct parse_datetime output seconds for '2012-12-24 16:30+01
-    ok 11 - correct parse_datetime return value for '2012-12-24 14:30-01:00'
-    ok 12 - correct parse_datetime output seconds for '2012-12-24 14:30-01:00
-    ok 13 - correct parse_datetime return value for '2012-12-24 14:30-0100'
-    ok 14 - correct parse_datetime output seconds for '2012-12-24 14:30-0100
-    ok 15 - correct parse_datetime return value for '2012-12-24 14:30-01'
-    ok 16 - correct parse_datetime output seconds for '2012-12-24 14:30-01
-    ok 17 - correct parse_datetime return value for '2012-12-24 15:30:00Z'
-    ok 18 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z
-    ok 19 - correct parse_datetime return value for '2012-12-24 15:30:00z'
-    ok 20 - correct parse_datetime output seconds for '2012-12-24 15:30:00z
-    ok 21 - correct parse_datetime return value for '2012-12-24 16:30:00+01:00'
-    ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01:00
-    ok 23 - correct parse_datetime return value for '2012-12-24 16:30:00+0100'
-    ok 24 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100
-    ok 25 - correct parse_datetime return value for '2012-12-24 14:30:00-01:00'
-    ok 26 - correct parse_datetime output seconds for '2012-12-24 14:30:00-01:00
-    ok 27 - correct parse_datetime return value for '2012-12-24 14:30:00-0100'
-    ok 28 - correct parse_datetime output seconds for '2012-12-24 14:30:00-0100
-    ok 29 - correct parse_datetime return value for '2012-12-24 15:30:00.123456Z'
-    ok 30 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z
-    ok 31 - correct parse_datetime return value for '2012-12-24 15:30:00.123456z'
-    ok 32 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z
-    ok 33 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01:00'
-    ok 34 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01:00
-    ok 35 - correct parse_datetime return value for '2012-12-24 16:30:00.123456+01'
-    ok 36 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01
-    ok 37 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01:00'
-    ok 38 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01:00
-    ok 39 - correct parse_datetime return value for '2012-12-24 14:30:00.123456-01'
-    ok 40 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01
-    ok 41 - correct parse_datetime return value for '2012-12-24t15:30Z'
-    ok 42 - correct parse_datetime output seconds for '2012-12-24t15:30Z
-    ok 43 - correct parse_datetime return value for '2012-12-24t15:30z'
-    ok 44 - correct parse_datetime output seconds for '2012-12-24t15:30z
-    ok 45 - correct parse_datetime return value for '2012-12-24t16:30+01:00'
-    ok 46 - correct parse_datetime output seconds for '2012-12-24t16:30+01:00
-    ok 47 - correct parse_datetime return value for '2012-12-24t16:30+0100'
-    ok 48 - correct parse_datetime output seconds for '2012-12-24t16:30+0100
-    ok 49 - correct parse_datetime return value for '2012-12-24t14:30-01:00'
-    ok 50 - correct parse_datetime output seconds for '2012-12-24t14:30-01:00
-    ok 51 - correct parse_datetime return value for '2012-12-24t14:30-0100'
-    ok 52 - correct parse_datetime output seconds for '2012-12-24t14:30-0100
-    ok 53 - correct parse_datetime return value for '2012-12-24t15:30:00Z'
-    ok 54 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z
-    ok 55 - correct parse_datetime return value for '2012-12-24t15:30:00z'
-    ok 56 - correct parse_datetime output seconds for '2012-12-24t15:30:00z
-    ok 57 - correct parse_datetime return value for '2012-12-24t16:30:00+01:00'
-    ok 58 - correct parse_datetime output seconds for '2012-12-24t16:30:00+01:00
-    ok 59 - correct parse_datetime return value for '2012-12-24t16:30:00+0100'
-    ok 60 - correct parse_datetime output seconds for '2012-12-24t16:30:00+0100
-    ok 61 - correct parse_datetime return value for '2012-12-24t14:30:00-01:00'
-    ok 62 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01:00
-    ok 63 - correct parse_datetime return value for '2012-12-24t14:30:00-0100'
-    ok 64 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100
-    ok 65 - correct parse_datetime return value for '2012-12-24t15:30:00.123456Z'
-    ok 66 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z
-    ok 67 - correct parse_datetime return value for '2012-12-24t15:30:00.123456z'
-    ok 68 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z
-    ok 69 - correct parse_datetime return value for '2012-12-24t16:30:00.123456+01:00'
-    ok 70 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01:00
-    ok 71 - correct parse_datetime return value for '2012-12-24t14:30:00.123456-01:00'
-    ok 72 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-01:00
-    ok 73 - correct parse_datetime return value for '2012-12-24 16:30 +01:00'
-    ok 74 - correct parse_datetime output seconds for '2012-12-24 16:30 +01:00
-    ok 75 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
-    ok 76 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
-    ok 77 - correct parse_datetime return value for '2012-12-24 15:30 UTC'
-    ok 78 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC
-    ok 79 - correct parse_datetime return value for '2012-12-24 16:30 UTC+1'
-    ok 80 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1
-    ok 81 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01'
-    ok 82 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01
-    ok 83 - correct parse_datetime return value for '2012-12-24 16:30 UTC+0100'
-    ok 84 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100
-    ok 85 - correct parse_datetime return value for '2012-12-24 16:30 UTC+01:00'
-    ok 86 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01:00
-    ok 87 - correct parse_datetime return value for '2012-12-24 14:30 UTC-1'
-    ok 88 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1
-    ok 89 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01'
-    ok 90 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01
-    ok 91 - correct parse_datetime return value for '2012-12-24 14:30 UTC-01:00'
-    ok 92 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01:00
-    ok 93 - correct parse_datetime return value for '2012-12-24 14:30 UTC-0100'
-    ok 94 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-0100
-    ok 95 - correct parse_datetime return value for '2012-12-24 15:30 GMT'
-    ok 96 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT
-    ok 97 - correct parse_datetime return value for '2012-12-24 16:30 GMT+1'
-    ok 98 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+1
-    ok 99 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01'
-    ok 100 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01
-    ok 101 - correct parse_datetime return value for '2012-12-24 16:30 GMT+0100'
-    ok 102 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100
-    ok 103 - correct parse_datetime return value for '2012-12-24 16:30 GMT+01:00'
-    ok 104 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01:00
-    ok 105 - correct parse_datetime return value for '2012-12-24 14:30 GMT-1'
-    ok 106 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1
-    ok 107 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01'
-    ok 108 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01
-    ok 109 - correct parse_datetime return value for '2012-12-24 14:30 GMT-01:00'
-    ok 110 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01:00
-    ok 111 - correct parse_datetime return value for '2012-12-24 14:30 GMT-0100'
-    ok 112 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-0100
-    ok 113 - correct parse_datetime return value for '2012-12-24 14:30 -01:00'
-    ok 114 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
-    ok 115 - correct parse_datetime return value for '2012-12-24 16:30:00 +01:00'
-    ok 116 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01:00
-    ok 117 - correct parse_datetime return value for '2012-12-24 14:30:00 -01:00'
-    ok 118 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01:00
-    ok 119 - correct parse_datetime return value for '2012-12-24 16:30:00.123456 +01:00'
-    ok 120 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +01:00
-    ok 121 - correct parse_datetime return value for '2012-12-24 14:30:00.123456 -01:00'
-    ok 122 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00
-    ok 123 - correct parse_datetime return value for '2012-12-24 15:30:00.123456 -00:00'
-    ok 124 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00:00
-    ok 125 - correct parse_datetime return value for '20121224T1630+01:00'
-    ok 126 - correct parse_datetime output seconds for '20121224T1630+01:00
-    ok 127 - correct parse_datetime return value for '2012-12-24T1630+01:00'
-    ok 128 - correct parse_datetime output seconds for '2012-12-24T1630+01:00
-    ok 129 - correct parse_datetime return value for '20121224T16:30+01'
-    ok 130 - correct parse_datetime output seconds for '20121224T16:30+01
-    ok 131 - correct parse_datetime return value for '20121224T16:30 +01'
-    ok 132 - correct parse_datetime output seconds for '20121224T16:30 +01
+    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 16:30+01:00'
+    ok 12 - correct parse_datetime output seconds for '2012-12-24 16:30+01:00
+    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+0100'
+    ok 17 - correct parse_datetime output seconds for '2012-12-24 16:30+0100
+    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+01'
+    ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30+01
+    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 14:30-01:00'
+    ok 27 - correct parse_datetime output seconds for '2012-12-24 14:30-01:00
+    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-0100'
+    ok 32 - correct parse_datetime output seconds for '2012-12-24 14:30-0100
+    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-01'
+    ok 37 - correct parse_datetime output seconds for '2012-12-24 14:30-01
+    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 15:30:00Z'
+    ok 42 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z
+    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 16:30:00+01:00'
+    ok 52 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01:00
+    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 16:30:00+0100'
+    ok 57 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100
+    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 14:30:00-01:00'
+    ok 62 - correct parse_datetime output seconds for '2012-12-24 14: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 14:30:00-0100'
+    ok 67 - correct parse_datetime output seconds for '2012-12-24 14: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 15:30:00.123456Z'
+    ok 72 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z
+    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 15:30:00.123456z'
+    ok 77 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z
+    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 16:30:00.123456+01:00'
+    ok 82 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01:00
+    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 16:30:00.123456+01'
+    ok 87 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01
+    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 14:30:00.123456-01:00'
+    ok 92 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01:00
+    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 14:30:00.123456-01'
+    ok 97 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01
+    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-24t15:30Z'
+    ok 102 - correct parse_datetime output seconds for '2012-12-24t15:30Z
+    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-24t15:30z'
+    ok 107 - correct parse_datetime output seconds for '2012-12-24t15:30z
+    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-24t16:30+01:00'
+    ok 112 - correct parse_datetime output seconds for '2012-12-24t16:30+01:00
+    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-24t16:30+0100'
+    ok 117 - correct parse_datetime output seconds for '2012-12-24t16:30+0100
+    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-24t14:30-01:00'
+    ok 122 - correct parse_datetime output seconds for '2012-12-24t14:30-01:00
+    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-24t14:30-0100'
+    ok 127 - correct parse_datetime output seconds for '2012-12-24t14:30-0100
+    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-24t15:30:00Z'
+    ok 132 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z
+    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-24t15:30:00z'
+    ok 137 - correct parse_datetime output seconds for '2012-12-24t15:30:00z
+    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-24t16:30:00+01:00'
+    ok 142 - correct parse_datetime output seconds for '2012-12-24t16:30:00+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-24t16:30:00+0100'
+    ok 147 - correct parse_datetime output seconds for '2012-12-24t16:30:00+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-24t14:30:00-01:00'
+    ok 152 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01:00
+    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-24t14:30:00-0100'
+    ok 157 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100
+    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.123456Z'
+    ok 162 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z
+    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-24t15:30:00.123456z'
+    ok 167 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z
+    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.123456+01:00'
+    ok 172 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01:00
+    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.123456-01:00'
+    ok 177 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-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-24 16:30 +01:00'
+    ok 182 - correct parse_datetime output seconds for '2012-12-24 16:30 +01:00
+    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-24 14:30 -01:00'
+    ok 187 - correct parse_datetime output seconds for '2012-12-24 14:30 -01:00
+    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-24 15:30 UTC'
+    ok 192 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC
+    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-24 16:30 UTC+1'
+    ok 197 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1
+    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-24 16:30 UTC+01'
+    ok 202 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01
+    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 UTC+0100'
+    ok 207 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100
+    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 16:30 UTC+01:00'
+    ok 212 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+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 14:30 UTC-1'
+    ok 217 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1
+    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 14:30 UTC-01'
+    ok 222 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01
+    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 14:30 UTC-01:00'
+    ok 227 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01:00
+    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 14:30 UTC-0100'
+    ok 232 - correct parse_datetime output seconds for '2012-12-24 14: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 15:30 GMT'
+    ok 237 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT
+    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 16:30 GMT+1'
+    ok 242 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+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 16:30 GMT+01'
+    ok 247 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+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 16:30 GMT+0100'
+    ok 252 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100
+    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 16:30 GMT+01:00'
+    ok 257 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01:00
+    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 14:30 GMT-1'
+    ok 262 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1
+    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 14:30 GMT-01'
+    ok 267 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01
+    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 14:30 GMT-01:00'
+    ok 272 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01:00
+    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 14:30 GMT-0100'
+    ok 277 - correct parse_datetime output seconds for '2012-12-24 14: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 14:30 -01:00'
+    ok 282 - correct parse_datetime output seconds for '2012-12-24 14:30 -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 16:30:00 +01:00'
+    ok 287 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01:00
+    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:00 -01:00'
+    ok 292 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01:00
+    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 16:30:00.123456 +01:00'
+    ok 297 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +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:00.123456 -01:00'
+    ok 302 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00
+    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 15:30:00.123456 -00:00'
+    ok 307 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00: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 '20121224T1630+01:00'
+    ok 312 - correct parse_datetime output seconds for '20121224T1630+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-24T1630+01:00'
+    ok 317 - correct parse_datetime output seconds for '2012-12-24T1630+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 '20121224T16:30+01'
+    ok 322 - correct parse_datetime output seconds for '20121224T16:30+01
+    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 '20121224T16:30 +01'
+    ok 327 - correct parse_datetime output seconds for '20121224T16:30 +01
+    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 1 - subtests
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 08/13] box, lua: renamed t_datetime_tz structure to datetime_t
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (6 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 07/13] lua: asctime and strfime fixed Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 09/13] lua: calculated attributes for date Timur Safin via Tarantool-patches
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

---
 src/box/tuple_compare.cc    |  4 ++--
 src/lib/core/datetime.h     | 22 +++++++++++-----------
 src/lib/core/mp_datetime.c  | 24 ++++++++++++------------
 src/lib/mpstream/mpstream.c |  2 +-
 src/lib/mpstream/mpstream.h |  4 ++--
 src/lua/datetime.c          |  6 +++---
 src/lua/datetime.h          |  2 +-
 src/lua/datetime.lua        |  2 +-
 src/lua/msgpack.c           |  2 +-
 src/lua/msgpackffi.lua      |  6 +++---
 src/lua/serializer.c        |  2 +-
 src/lua/serializer.h        |  2 +-
 src/lua/utils.c             |  1 -
 test/unit/datetime.c        | 21 ++++++++++-----------
 14 files changed, 49 insertions(+), 51 deletions(-)

diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 39c9dd6e9..28814428c 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -401,8 +401,8 @@ mp_compare_uuid(const char *field_a, const char *field_b)
 static int
 mp_compare_datetime(const char *lhs, const char *rhs)
 {
-	t_datetime_tz lhs_dt, rhs_dt;
-	t_datetime_tz *ret;
+	datetime_t lhs_dt, rhs_dt;
+	datetime_t *ret;
 	ret = mp_decode_datetime(&lhs, &lhs_dt);
 	assert(ret != NULL);
 	ret = mp_decode_datetime(&rhs, &rhs_dt);
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
index bd23f33a3..b289b0486 100644
--- a/src/lib/core/datetime.h
+++ b/src/lib/core/datetime.h
@@ -47,7 +47,7 @@ extern "C" {
 /**
  * datetime structure consisting of:
  */
-struct t_datetime_tz {
+struct datetime_t {
 	int secs;	///< seconds since epoch
 	int nsec;	///< nanoseconds if any
 	int offset;	///< offset in minutes from GMT
@@ -62,27 +62,27 @@ struct t_datetime_duration {
 };
 
 int
-datetime_compare(const struct t_datetime_tz * lhs,
-		 const struct t_datetime_tz * rhs);
+datetime_compare(const struct datetime_t * lhs,
+		 const struct datetime_t * rhs);
 
 
-struct t_datetime_tz *
-datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date);
+struct datetime_t *
+datetime_unpack(const char **data, uint32_t len, struct datetime_t *date);
 
 char *
-datetime_pack(char *data, const struct t_datetime_tz *date);
+datetime_pack(char *data, const struct datetime_t *date);
 
 uint32_t
-mp_sizeof_datetime(const struct t_datetime_tz *date);
+mp_sizeof_datetime(const struct datetime_t *date);
 
-struct t_datetime_tz *
-mp_decode_datetime(const char **data, struct t_datetime_tz *date);
+struct datetime_t *
+mp_decode_datetime(const char **data, struct datetime_t *date);
 
 char *
-mp_encode_datetime(char *data, const struct t_datetime_tz *date);
+mp_encode_datetime(char *data, const struct datetime_t *date);
 
 int
-datetime_to_string(const struct t_datetime_tz * date, char *buf, uint32_t len);
+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);
diff --git a/src/lib/core/mp_datetime.c b/src/lib/core/mp_datetime.c
index 2cf3fd79b..ad18f00c8 100644
--- a/src/lib/core/mp_datetime.c
+++ b/src/lib/core/mp_datetime.c
@@ -63,7 +63,7 @@ mp_decode_Xint(const char **data)
 }
 
 uint32_t
-mp_sizeof_datetime(const struct t_datetime_tz *date)
+mp_sizeof_datetime(const struct datetime_t *date)
 {
 	uint32_t sz = mp_sizeof_Xint(date->secs);
 
@@ -77,8 +77,8 @@ mp_sizeof_datetime(const struct t_datetime_tz *date)
 	return sz;
 }
 
-struct t_datetime_tz *
-datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date)
+struct datetime_t *
+datetime_unpack(const char **data, uint32_t len, struct datetime_t *date)
 {
 	const char * svp = *data;
 
@@ -102,8 +102,8 @@ datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date)
 	return date;
 }
 
-struct t_datetime_tz *
-mp_decode_datetime(const char **data, struct t_datetime_tz *date)
+struct datetime_t *
+mp_decode_datetime(const char **data, struct datetime_t *date)
 {
 	if (mp_typeof(**data) != MP_EXT)
 		return NULL;
@@ -118,7 +118,7 @@ mp_decode_datetime(const char **data, struct t_datetime_tz *date)
 }
 
 char *
-datetime_pack(char *data, const struct t_datetime_tz *date)
+datetime_pack(char *data, const struct datetime_t *date)
 {
 	data = mp_encode_Xint(data, date->secs);
 	if (date->nsec != 0 || date->offset != 0)
@@ -130,7 +130,7 @@ datetime_pack(char *data, const struct t_datetime_tz *date)
 }
 
 char *
-mp_encode_datetime(char *data, const struct t_datetime_tz *date)
+mp_encode_datetime(char *data, const struct datetime_t *date)
 {
 	uint32_t len = mp_sizeof_datetime(date);
 
@@ -140,7 +140,7 @@ mp_encode_datetime(char *data, const struct t_datetime_tz *date)
 }
 
 int
-datetime_to_string(const struct t_datetime_tz * date, char *buf, uint32_t len)
+datetime_to_string(const struct datetime_t * date, char *buf, uint32_t len)
 {
 	char * src = buf;
 	int offset = date->offset;
@@ -191,7 +191,7 @@ datetime_to_string(const struct t_datetime_tz * date, char *buf, uint32_t len)
 int
 mp_snprint_datetime(char *buf, int size, const char **data, uint32_t len)
 {
-	struct t_datetime_tz date = {0};
+	struct datetime_t date = {0};
 
 	if (datetime_unpack(data, len, &date) == NULL)
 		return -1;
@@ -202,7 +202,7 @@ 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)
 {
-	struct  t_datetime_tz date;
+	struct  datetime_t date;
 
 	if (datetime_unpack(data, len, &date) == NULL)
 		return -1;
@@ -220,8 +220,8 @@ adjusted_secs(int secs, int offset)
 }
 
 int
-datetime_compare(const struct t_datetime_tz * lhs,
-		 const struct t_datetime_tz * rhs)
+datetime_compare(const struct datetime_t * lhs,
+		 const struct datetime_t * rhs)
 {
 	int result = COMPARE_RESULT(adjusted_secs(lhs->secs, lhs->offset),
 				    adjusted_secs(rhs->secs, rhs->offset));
diff --git a/src/lib/mpstream/mpstream.c b/src/lib/mpstream/mpstream.c
index 8c92b4049..1077e3b19 100644
--- a/src/lib/mpstream/mpstream.c
+++ b/src/lib/mpstream/mpstream.c
@@ -210,7 +210,7 @@ mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid)
 }
 
 void
-mpstream_encode_datetime(struct mpstream *stream, const struct t_datetime_tz *val)
+mpstream_encode_datetime(struct mpstream *stream, const struct datetime_t *val)
 {
 	char *data = mpstream_reserve(stream, mp_sizeof_datetime(val));
 	if (data == NULL)
diff --git a/src/lib/mpstream/mpstream.h b/src/lib/mpstream/mpstream.h
index 39e8fd956..540e9a666 100644
--- a/src/lib/mpstream/mpstream.h
+++ b/src/lib/mpstream/mpstream.h
@@ -39,7 +39,7 @@ extern "C" {
 #endif /* defined(__cplusplus) */
 
 struct tt_uuid;
-struct t_datetime_tz;
+struct datetime_t;
 
 /**
 * Ask the allocator to reserve at least size bytes. It can reserve
@@ -147,7 +147,7 @@ void
 mpstream_encode_uuid(struct mpstream *stream, const struct tt_uuid *uuid);
 
 void
-mpstream_encode_datetime(struct mpstream *stream, const struct t_datetime_tz *dt);
+mpstream_encode_datetime(struct mpstream *stream, const struct datetime_t *dt);
 
 /** Copies n bytes from memory area src to stream. */
 void
diff --git a/src/lua/datetime.c b/src/lua/datetime.c
index 215cb89fb..a4f9915e0 100644
--- a/src/lua/datetime.c
+++ b/src/lua/datetime.c
@@ -39,7 +39,7 @@
 uint32_t CTID_DATETIME_TZ = 0;
 uint32_t CTID_DURATION = 0;
 
-struct t_datetime_tz *
+struct datetime_t *
 luaL_pushdatetime(struct lua_State *L)
 {
 	return luaL_pushcdata(L, CTID_DATETIME_TZ);
@@ -48,14 +48,14 @@ luaL_pushdatetime(struct lua_State *L)
 void
 tarantool_lua_datetime_init(struct lua_State *L)
 {
-	int rc = luaL_cdef(L, "struct t_datetime_tz {"
+	int rc = luaL_cdef(L, "struct datetime_t {"
 				"int secs;"
 				"int nsec;"
 				"int offset;"
 			  "};");
 	assert(rc == 0);
 	(void) rc;
-	CTID_DATETIME_TZ = luaL_ctypeid(L, "struct t_datetime_tz");
+	CTID_DATETIME_TZ = luaL_ctypeid(L, "struct datetime_t");
 	assert(CTID_DATETIME_TZ != 0);
 
 
diff --git a/src/lua/datetime.h b/src/lua/datetime.h
index 38ba53561..cee96ca06 100644
--- a/src/lua/datetime.h
+++ b/src/lua/datetime.h
@@ -42,7 +42,7 @@ extern uint32_t CTID_DURATION;
 
 struct lua_State;
 
-struct t_datetime_tz*
+struct datetime_t*
 luaL_pushdatetime(struct lua_State *L);
 
 void
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index efd953de9..086c97697 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -150,7 +150,7 @@ local NANOS_PER_SEC    = 1000000000LL
 local DT_EPOCH_1970_OFFSET = 719163LL
 
 
-local datetime_t = ffi.typeof('struct t_datetime_tz')
+local datetime_t = ffi.typeof('struct datetime_t')
 local duration_t = ffi.typeof('struct t_datetime_duration')
 
 local function duration_new()
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index dd2c41056..0a4ba8129 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -339,7 +339,7 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
 		}
 		case MP_DATETIME:
 		{
-			struct t_datetime_tz * date = luaL_pushdatetime(L);
+			struct datetime_t * date = luaL_pushdatetime(L);
 			date = datetime_unpack(data, len, date);
 			if (date == NULL)
 				goto ext_decode_err;
diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index 2613cf216..271be857a 100644
--- a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -36,8 +36,8 @@ decimal_t *
 decimal_unpack(const char **data, uint32_t len, decimal_t *dec);
 struct tt_uuid *
 uuid_unpack(const char **data, uint32_t len, struct tt_uuid *uuid);
-struct t_datetime_tz *
-datetime_unpack(const char **data, uint32_t len, struct t_datetime_tz *date);
+struct datetime_t *
+datetime_unpack(const char **data, uint32_t len, struct datetime_t *date);
 ]])
 
 local strict_alignment = (jit.arch == 'arm')
@@ -517,7 +517,7 @@ local ext_decoder = {
     end,
     -- MP_DATETIME
     [4] = function(data, len)
-        local dt = ffi.new("struct t_datetime_tz")
+        local dt = ffi.new("struct datetime_t")
         builtin.datetime_unpack(data, len, dt)
         return dt
     end,
diff --git a/src/lua/serializer.c b/src/lua/serializer.c
index 55120a725..160d6fe7e 100644
--- a/src/lua/serializer.c
+++ b/src/lua/serializer.c
@@ -543,7 +543,7 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 				field->uuidval = (struct tt_uuid *) cdata;
 			} else if (cd->ctypeid == CTID_DATETIME_TZ) {
 				field->ext_type = MP_DATETIME;
-				field->dateval = (struct t_datetime_tz *) cdata;
+				field->dateval = (struct datetime_t *) cdata;
 			} else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF &&
 				   opts != NULL &&
 				   opts->error_marshaling_enabled) {
diff --git a/src/lua/serializer.h b/src/lua/serializer.h
index ec62c723f..52e51d279 100644
--- a/src/lua/serializer.h
+++ b/src/lua/serializer.h
@@ -224,7 +224,7 @@ struct luaL_field {
 		uint32_t size;
 		decimal_t *decval;
 		struct tt_uuid *uuidval;
-		struct t_datetime_tz *dateval;
+		struct datetime_t *dateval;
 	};
 	enum mp_type type;
 	/* subtypes of MP_EXT */
diff --git a/src/lua/utils.c b/src/lua/utils.c
index 34cec0eed..5ec0ce135 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -232,7 +232,6 @@ luaL_setcdatagc(struct lua_State *L, int idx)
 	lua_pop(L, 1);
 }
 
-
 /**
  * A helper to register a single type metatable.
  */
diff --git a/test/unit/datetime.c b/test/unit/datetime.c
index f74ac139d..7e0229758 100644
--- a/test/unit/datetime.c
+++ b/test/unit/datetime.c
@@ -129,36 +129,35 @@ parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 // 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 t_datetime_tz {
-	int64_t sec;
+struct datetime_t {
+	int64_t secs;
 	int64_t nsec;
 	int64_t offset;
 };
 
 static int
-local_rd(const struct t_datetime_tz * dt) {
-	return (int)(dt->sec / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET;
+local_rd(const struct datetime_t * dt) {
+	return (int)(dt->secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET;
 }
 
 static int
-local_dt(const struct t_datetime_tz * dt) {
+local_dt(const struct datetime_t * dt) {
 	return dt_from_rdn(local_rd(dt));
 }
 
 
 struct tm*
-datetime_to_tm(struct t_datetime_tz * dt)
+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->sec % 86400;
+	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;
@@ -189,14 +188,14 @@ static void datetime_test(void)
 		// check that stringized literal produces the same date
 		// time fields
 		static char buff[40];
-		struct t_datetime_tz dt = {secs, nanosecs, ofs};
+		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.sec, &dt.nsec, &dt.offset);
+		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.sec,
+		is(secs, dt.secs,
 		   "reversible seconds via strftime for '%s", buff);
 	}
 }
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 09/13] lua: calculated attributes for date
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (7 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 08/13] box, lua: renamed t_datetime_tz structure to datetime_t Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 10/13] lua: tostring formatization in datetime.lua Timur Safin via Tarantool-patches
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* introduced numerous calculated attributes to data object, like:
  - timestamp, seconds, microseconds, minute, or hours
* added new test case for that
---
 src/lua/datetime.lua           | 51 ++++++++++++++++++++++------------
 test/app-tap/datetime.test.lua | 17 +++++++++++-
 2 files changed, 50 insertions(+), 18 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 086c97697..5cb53c6f2 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -218,6 +218,36 @@ local function duration_serialize(self)
     return { secs = self.secs, nsec = self.nsec }
 end
 
+local datetime_index = function(self, key)
+    local attributes = {
+        timestamp = function(self)
+            return tonumber(self.secs + self.nsec / 1e9)
+        end,
+        nanoseconds = function(self)
+            return tonumber(self.secs * 1e9 + self.nsec)
+        end,
+        microseconds = function(self)
+            return tonumber(self.secs * 1e6 + self.nsec / 1e3)
+        end,
+        milliseconds = function(self)
+            return tonumber(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 % 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,
+    }
+    return attributes[key] ~= nil and attributes[key](self) or nil
+end
+
 local datetime_mt = {
     -- __tostring = datetime_tostring,
     __serialize = datetime_serialize,
@@ -226,23 +256,7 @@ local datetime_mt = {
     __le = datetime_le,
     __sub = datetime_sub,
     __add = datetime_add,
-
-    nanoseconds = function(self)
-        return tonumber(self.secs*NANOS_PER_SEC + self.nsec)
-    end,
-    microseconds = function(self)
-        return tonumber(self.secs*1e6 + self.nsec*1e3)
-    end,
-    seconds = function(self)
-        return tonumber(self.secs + self.nsec*1e3)
-    end,
-    minutes = function(self)
-        return tonumber((self._ticks/(1e6*60))%60)
-    end,
-    hours = function(self)
-        return tonumber(self._ticks/(1e6*60*60))
-    end,
-
+    __index = datetime_index,
 }
 
 local duration_mt = {
@@ -251,6 +265,9 @@ local duration_mt = {
     __eq = datetime_eq,
     __lt = datetime_lt,
     __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
 }
 
 local function datetime_new_raw(secs, nsec, offset)
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index b79450ecc..06720b241 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -4,7 +4,7 @@ local tap = require('tap')
 local test = tap.test("errno")
 local date = require('datetime')
 
-test:plan(5)
+test:plan(6)
 
 test:test("Simple tests for parser", function(test)
     test:plan(2)
@@ -188,4 +188,19 @@ test:test("Parse iso date - invalid strings", function(test)
     end
 end)
 
+test:test("Parse tiny date into seconds and other parts", function(test)
+    test:plan(9)
+    local str = '19700101 00:00:30.528'
+    local tiny = date(str)
+    test:ok(tiny.secs, 30, ("secs of '%s'"):format(str))
+    test:ok(tiny.nsec, 528000000, ("nsec of '%s'"):format(str))
+    test:ok(tiny.nanoseconds, 30528000000, "nanoseconds")
+    test:ok(tiny.microseconds, 30528000, "microseconds")
+    test:ok(tiny.milliseconds, 30528, "milliseconds")
+    test:ok(tiny.seconds, 30.528, "seconds")
+    test:ok(tiny.timestamp, 30.528, "timestamp")
+    test:ok(tiny.minutes, 0.5088, "minuts")
+    test:ok(tiny.hours,  0.00848, "hours")
+end)
+
 os.exit(test:check() and 0 or 1)
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 10/13] lua: tostring formatization in datetime.lua
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (8 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 09/13] lua: calculated attributes for date Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 11/13] test: allow relaxed date format without tz Timur Safin via Tarantool-patches
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

---
 src/lua/datetime.lua | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 5cb53c6f2..081e759fd 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -53,6 +53,12 @@ ffi.cdef [[
     dt_t    dt_from_struct_tm  (const struct tm *tm);
     void    dt_to_struct_tm    (dt_t dt, struct tm *tm);
 
+    // mp_datetime.c
+
+    int
+    datetime_to_string(const struct datetime_t * date, char *buf, uint32_t len);
+
+
     // <asm-generic/posix_types.h>
     typedef long            __kernel_long_t;
     typedef unsigned long   __kernel_ulong_t;
@@ -566,8 +572,13 @@ local function strftime(fmt, o)
     return ffi.string(buff)
 end
 
--- strftime may be redirected to datetime:fmt("format")
-local function datetime_fmt()
+local function datetime_tostring(o)
+    assert(ffi.typeof(o) == datetime_t)
+    local sz = 48
+    local buff = ffi.new('char[?]', sz)
+    local len = native.datetime_to_string(o, buff, sz)
+    assert(len < sz)
+    return ffi.string(buff)
 end
 
 
@@ -583,7 +594,8 @@ return setmetatable(
         parse_date = parse_date,
         parse_time = parse_time,
         parse_zone = parse_zone,
-        fmt = datetime_fmt,
+
+        tostring = datetime_tostring,
 
         now = local_now,
     -- strptime = strptime;
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 11/13] test: allow relaxed date format without tz
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (9 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 10/13] lua: tostring formatization in datetime.lua Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support Timur Safin via Tarantool-patches
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* added checks for parsing of date formats withour trailing
  timezones.

NB! That's already handled this way in lua parse_str
it's only parse_datetime() from unit test required some adaptation.
---
 test/unit/datetime.c      |  20 ++-
 test/unit/datetime.result | 283 +++++++++++++++++++++-----------------
 2 files changed, 169 insertions(+), 134 deletions(-)

diff --git a/test/unit/datetime.c b/test/unit/datetime.c
index 7e0229758..e979e8f0f 100644
--- a/test/unit/datetime.c
+++ b/test/unit/datetime.c
@@ -15,6 +15,7 @@ struct {
 } 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"),
@@ -23,24 +24,28 @@ struct {
 	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"),
@@ -84,7 +89,7 @@ struct {
 
 #define DIM(a) (sizeof(a) / sizeof(a[0]))
 
-// p5-time/moment/src/moment_parse.c: parse_string_lenient()
+// p5-time-moment/src/moment_parse.c: parse_string_lenient()
 static int
 parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 	       int64_t *op)
@@ -92,11 +97,13 @@ parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 	size_t n;
 	dt_t dt;
 	char c;
-	int sod, nanosecond, offset;
+	int sod = 0, nanosecond = 0, offset = 0;
 
 	n = dt_parse_iso_date(str, len, &dt);
-	if (!n || n == len)
+	if (!n)
 		return 1;
+	if (n == len)
+		goto exit;
 
 	c = str[n++];
 	if (!(c == 'T' || c == 't' || c == ' '))
@@ -106,8 +113,10 @@ parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 	len -= n;
 
 	n = dt_parse_iso_time(str, len, &sod, &nanosecond);
-	if (!n || n == len)
+	if (!n)
 		return 1;
+	if (n == len)
+		goto exit;
 
 	if (str[n] == ' ')
 	n++;
@@ -119,6 +128,7 @@ parse_datetime(const char *str, size_t len, int64_t *sp, int64_t *np,
 	if (!n || n != len)
 		return 1;
 
+exit:
 	*sp = ((int64_t)dt_rdn(dt) - 719163) * 86400 + sod - offset * 60;
 	*np = nanosecond;
 	*op = offset;
@@ -172,7 +182,7 @@ static void datetime_test(void)
 	int64_t nanosecs;
 	int64_t ofs;
 
-	plan(330);
+	plan(355);
 	parse_datetime(sample, sizeof(sample) - 1,
 		       &secs_expected, &nanosecs, &ofs);
 
diff --git a/test/unit/datetime.result b/test/unit/datetime.result
index 453897769..f106fa769 100644
--- a/test/unit/datetime.result
+++ b/test/unit/datetime.result
@@ -1,5 +1,5 @@
 1..1
-    1..330
+    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
@@ -10,324 +10,349 @@
     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 16:30+01:00'
-    ok 12 - correct parse_datetime output seconds for '2012-12-24 16:30+01: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+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+0100'
-    ok 17 - correct parse_datetime output seconds for '2012-12-24 16:30+0100
+    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+01'
-    ok 22 - correct parse_datetime output seconds for '2012-12-24 16:30+01
+    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 14:30-01:00'
-    ok 27 - correct parse_datetime output seconds for '2012-12-24 14:30-01: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+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-0100'
-    ok 32 - correct parse_datetime output seconds for '2012-12-24 14:30-0100
+    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-01'
-    ok 37 - correct parse_datetime output seconds for '2012-12-24 14:30-01
+    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 15:30:00Z'
-    ok 42 - correct parse_datetime output seconds for '2012-12-24 15:30:00Z
+    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 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 16:30:00+01:00'
-    ok 52 - correct parse_datetime output seconds for '2012-12-24 16:30:00+01: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+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 16:30:00+0100'
-    ok 57 - correct parse_datetime output seconds for '2012-12-24 16:30:00+0100
+    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 14:30:00-01:00'
-    ok 62 - correct parse_datetime output seconds for '2012-12-24 14:30:00-01: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+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 14:30:00-0100'
-    ok 67 - correct parse_datetime output seconds for '2012-12-24 14:30:00-0100
+    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 15:30:00.123456Z'
-    ok 72 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456Z
+    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 15:30:00.123456z'
-    ok 77 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456z
+    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 16:30:00.123456+01:00'
-    ok 82 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01: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+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 16:30:00.123456+01'
-    ok 87 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456+01
+    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 14:30:00.123456-01:00'
-    ok 92 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01: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+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 14:30:00.123456-01'
-    ok 97 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456-01
+    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-24t15:30Z'
-    ok 102 - correct parse_datetime output seconds for '2012-12-24t15:30Z
+    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-24t15:30z'
-    ok 107 - correct parse_datetime output seconds for '2012-12-24t15:30z
+    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-24t16:30+01:00'
-    ok 112 - correct parse_datetime output seconds for '2012-12-24t16:30+01: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+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-24t16:30+0100'
-    ok 117 - correct parse_datetime output seconds for '2012-12-24t16:30+0100
+    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-24t14:30-01:00'
-    ok 122 - correct parse_datetime output seconds for '2012-12-24t14:30-01: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+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-24t14:30-0100'
-    ok 127 - correct parse_datetime output seconds for '2012-12-24t14:30-0100
+    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-24t15:30:00Z'
-    ok 132 - correct parse_datetime output seconds for '2012-12-24t15:30:00Z
+    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-24t15:30:00z'
-    ok 137 - correct parse_datetime output seconds for '2012-12-24t15:30:00z
+    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-24t16:30:00+01:00'
-    ok 142 - correct parse_datetime output seconds for '2012-12-24t16:30:00+01: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+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-24t16:30:00+0100'
-    ok 147 - correct parse_datetime output seconds for '2012-12-24t16:30:00+0100
+    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-24t14:30:00-01:00'
-    ok 152 - correct parse_datetime output seconds for '2012-12-24t14:30:00-01: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+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-24t14:30:00-0100'
-    ok 157 - correct parse_datetime output seconds for '2012-12-24t14:30:00-0100
+    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.123456Z'
-    ok 162 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456Z
+    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-24t15:30:00.123456z'
-    ok 167 - correct parse_datetime output seconds for '2012-12-24t15:30:00.123456z
+    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.123456+01:00'
-    ok 172 - correct parse_datetime output seconds for '2012-12-24t16:30:00.123456+01: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+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.123456-01:00'
-    ok 177 - correct parse_datetime output seconds for '2012-12-24t14:30:00.123456-01: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+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-24 16:30 +01:00'
-    ok 182 - correct parse_datetime output seconds for '2012-12-24 16:30 +01: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+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-24 14:30 -01:00'
-    ok 187 - correct parse_datetime output seconds for '2012-12-24 14:30 -01: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+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-24 15:30 UTC'
-    ok 192 - correct parse_datetime output seconds for '2012-12-24 15:30 UTC
+    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-24 16:30 UTC+1'
-    ok 197 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+1
+    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-24 16:30 UTC+01'
-    ok 202 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01
+    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 UTC+0100'
-    ok 207 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+0100
+    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 16:30 UTC+01:00'
-    ok 212 - correct parse_datetime output seconds for '2012-12-24 16:30 UTC+01: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+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 14:30 UTC-1'
-    ok 217 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-1
+    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 14:30 UTC-01'
-    ok 222 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01
+    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 14:30 UTC-01:00'
-    ok 227 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-01: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+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 14:30 UTC-0100'
-    ok 232 - correct parse_datetime output seconds for '2012-12-24 14:30 UTC-0100
+    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 15:30 GMT'
-    ok 237 - correct parse_datetime output seconds for '2012-12-24 15:30 GMT
+    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 16:30 GMT+1'
-    ok 242 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+1
+    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 16:30 GMT+01'
-    ok 247 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01
+    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 16:30 GMT+0100'
-    ok 252 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+0100
+    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 16:30 GMT+01:00'
-    ok 257 - correct parse_datetime output seconds for '2012-12-24 16:30 GMT+01: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+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 14:30 GMT-1'
-    ok 262 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-1
+    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 14:30 GMT-01'
-    ok 267 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01
+    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 14:30 GMT-01:00'
-    ok 272 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-01: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+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 14:30 GMT-0100'
-    ok 277 - correct parse_datetime output seconds for '2012-12-24 14:30 GMT-0100
+    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 14:30 -01:00'
-    ok 282 - correct parse_datetime output seconds for '2012-12-24 14:30 -01: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+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 16:30:00 +01:00'
-    ok 287 - correct parse_datetime output seconds for '2012-12-24 16:30:00 +01: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+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:00 -01:00'
-    ok 292 - correct parse_datetime output seconds for '2012-12-24 14:30:00 -01: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+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 16:30:00.123456 +01:00'
-    ok 297 - correct parse_datetime output seconds for '2012-12-24 16:30:00.123456 +01: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+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:00.123456 -01:00'
-    ok 302 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01: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+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 15:30:00.123456 -00:00'
-    ok 307 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00: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+0000'
     ok 310 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
-    ok 311 - correct parse_datetime return value for '20121224T1630+01:00'
-    ok 312 - correct parse_datetime output seconds for '20121224T1630+01: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+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-24T1630+01:00'
-    ok 317 - correct parse_datetime output seconds for '2012-12-24T1630+01: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+0000'
     ok 320 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
-    ok 321 - correct parse_datetime return value for '20121224T16:30+01'
-    ok 322 - correct parse_datetime output seconds for '20121224T16:30+01
+    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 '20121224T16:30 +01'
-    ok 327 - correct parse_datetime output seconds for '20121224T16:30 +01
+    ok 326 - correct parse_datetime return value for '2012-12-24 14:30:00.123456 -01:00'
+    ok 327 - correct parse_datetime output seconds for '2012-12-24 14:30:00.123456 -01:00
     ok 328 - strftime
     ok 329 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
     ok 330 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
+    ok 331 - correct parse_datetime return value for '2012-12-24 15:30:00.123456 -00:00'
+    ok 332 - correct parse_datetime output seconds for '2012-12-24 15:30:00.123456 -00:00
+    ok 333 - strftime
+    ok 334 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
+    ok 335 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
+    ok 336 - correct parse_datetime return value for '20121224T1630+01:00'
+    ok 337 - correct parse_datetime output seconds for '20121224T1630+01:00
+    ok 338 - strftime
+    ok 339 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
+    ok 340 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
+    ok 341 - correct parse_datetime return value for '2012-12-24T1630+01:00'
+    ok 342 - correct parse_datetime output seconds for '2012-12-24T1630+01:00
+    ok 343 - strftime
+    ok 344 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
+    ok 345 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
+    ok 346 - correct parse_datetime return value for '20121224T16:30+01'
+    ok 347 - correct parse_datetime output seconds for '20121224T16:30+01
+    ok 348 - strftime
+    ok 349 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
+    ok 350 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
+    ok 351 - correct parse_datetime return value for '20121224T16:30 +01'
+    ok 352 - correct parse_datetime output seconds for '20121224T16:30 +01
+    ok 353 - strftime
+    ok 354 - correct parse_datetime return value for '2012-12-24 15:30:00+0000'
+    ok 355 - reversible seconds via strftime for '2012-12-24 15:30:00+0000
 ok 1 - subtests
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (10 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 11/13] test: allow relaxed date format without tz Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 13/13] lua: complete " Timur Safin via Tarantool-patches
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* created few entries (months(N), years(N), days(N), etc.)
  for easier date/time arithmetic;
* at the moment we require that on either side of + or -
  we should have at least one datetime. There is no (yet) support
  for duration + duration operations.
---
 src/lua/datetime.lua | 230 +++++++++++++++++++++++++++++++++----------
 1 file changed, 176 insertions(+), 54 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 081e759fd..698c55ebd 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -37,6 +37,17 @@ ffi.cdef [[
     int      dt_rdn          (dt_t dt);
     dt_dow_t dt_dow          (dt_t dt);
 
+    // dt_arithmetic.h
+    typedef enum {
+        DT_EXCESS,
+        DT_LIMIT,
+        DT_SNAP
+    } dt_adjust_t;
+
+    dt_t    dt_add_years        (dt_t dt, int delta, dt_adjust_t adjust);
+    dt_t    dt_add_quarters     (dt_t dt, int delta, dt_adjust_t adjust);
+    dt_t    dt_add_months       (dt_t dt, int delta, dt_adjust_t adjust);
+
     // dt_parse_iso.h
     size_t dt_parse_iso_date          (const char *str, size_t len, dt_t *dt);
 
@@ -158,40 +169,51 @@ local DT_EPOCH_1970_OFFSET = 719163LL
 
 local datetime_t = ffi.typeof('struct datetime_t')
 local duration_t = ffi.typeof('struct t_datetime_duration')
+ffi.cdef [[
+    struct t_duration_months {
+        int m;
+    };
+
+    struct t_duration_years {
+        int y;
+    };
+]]
+local duration_months_t = ffi.typeof('struct t_duration_months')
+local duration_years_t = ffi.typeof('struct t_duration_years')
 
 local function duration_new()
     local delta = ffi.new(duration_t)
     return delta
 end
 
-local function adjusted_secs(dt)
-    return dt.secs - dt.offset * 60
+local function duration_years_new(y)
+    local o = ffi.new(duration_years_t)
+    o.y = y
+    return o
 end
 
-local function datetime_sub(lhs, rhs)
-    local s1 = adjusted_secs(lhs)
-    local s2 = adjusted_secs(rhs)
-    local d = duration_new()
-    d.secs = s2 - s1
-    d.nsec = rhs.nsec - lhs.nsec
-    if d.nsec < 0 then
-        d.secs = d.secs - 1
-        d.nsec = d.nsec + NANOS_PER_SEC
-    end
-    return d
+local function duration_months_new(m)
+    local o = ffi.new(duration_months_t)
+    o.m = m
+    return o
 end
 
-local function datetime_add(lhs, rhs)
-    local s1 = adjusted_secs(lhs)
-    local s2 = adjusted_secs(rhs)
-    local d = duration_new()
-    d.secs = s2 + s1
-    d.nsec = rhs.nsec + lhs.nsec
-    if d.nsec >= NANOS_PER_SEC then
-        d.secs = d.secs + 1
-        d.nsec = d.nsec - NANOS_PER_SEC
-    end
-    return d
+local function duration_days_new(d)
+    local o = ffi.new(duration_t)
+    o.secs = d * SECS_PER_DAY
+    return o
+end
+
+local function duration_hours_new(h)
+    local o = ffi.new(duration_t)
+    o.secs = h * 60 * 60
+    return o
+end
+
+local function duration_minutes_new(m)
+    local o = ffi.new(duration_t)
+    o.secs = m * 60
+    return o
 end
 
 local function datetime_eq(lhs, rhs)
@@ -254,28 +276,6 @@ local datetime_index = function(self, key)
     return attributes[key] ~= nil and attributes[key](self) or nil
 end
 
-local datetime_mt = {
-    -- __tostring = datetime_tostring,
-    __serialize = datetime_serialize,
-    __eq = datetime_eq,
-    __lt = datetime_lt,
-    __le = datetime_le,
-    __sub = datetime_sub,
-    __add = datetime_add,
-    __index = datetime_index,
-}
-
-local duration_mt = {
-    -- __tostring = duration_tostring,
-    __serialize = duration_serialize,
-    __eq = datetime_eq,
-    __lt = datetime_lt,
-    __le = datetime_le,
-    __sub = datetime_sub,
-    __add = datetime_add,
-    __index = datetime_index,
-}
-
 local function datetime_new_raw(secs, nsec, offset)
     local dt_obj = ffi.new(datetime_t)
     dt_obj.secs = secs
@@ -402,6 +402,110 @@ local function datetime_new(o)
     return mk_timestamp(dt, secs, frac, offset)
 end
 
+local function datetime_tostring(o)
+    print(ffi.typeof(o))
+    assert(ffi.typeof(o) == datetime_t)
+    local sz = 48
+    local buff = ffi.new('char[?]', sz)
+    local len = native.datetime_to_string(o, buff, sz)
+    assert(len < sz)
+    return ffi.string(buff)
+end
+
+local function dt_to_ymd(dt)
+    local y, m, d
+    y = ffi.new('int[1]')
+    m = ffi.new('int[1]')
+    d = ffi.new('int[1]')
+    cdt.dt_to_ymd(dt, y, m, d)
+    return y[0], m[0], d[0]
+end
+
+local function check_date(o)
+    assert(ffi.typeof(o) == datetime_t, "date/time expected")
+end
+
+local function date_first(lhs, rhs)
+    if (ffi.typeof(lhs) == datetime_t) then
+        return lhs, rhs
+    else
+        return rhs, lhs
+    end
+end
+
+local function shift_months(y, M, deltaM)
+    M = M + deltaM
+    local newM = (M - 1) % 12 + 1
+    local newY = y + math.floor((M - 1)/12)
+    assert(newM >= 1 and newM <= 12, "month value is outside of range")
+    return newY, newM
+end
+
+local function datetime_sub(lhs, rhs)
+    check_date(lhs) -- make sure left is date
+    local d, s = lhs, rhs
+
+    -- 1. left is date, right is date or delta
+    if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
+        d.secs = d.secs - s.secs
+        d.nsec = s.nsec - s.nsec
+        if d.nsec < 0 then
+            d.secs = d.secs - 1
+            d.nsec = d.nsec + NANOS_PER_SEC
+        end
+
+    -- 2. left is date, right is duration in months
+    elseif ffi.typeof(s) == duration_months_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y, M = shift_months(y, M, -s.m)
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+
+    -- 2. left is date, right is duration in years
+    elseif ffi.typeof(s) == duration_years_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y = y - s.y
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+    else
+        assert(false, "unexpected type")
+    end
+end
+
+local function datetime_add(lhs, rhs)
+    local d, s = date_first(lhs, rhs)
+
+    -- 1. left is date, right is date or delta
+    if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
+        d.secs = d.secs + s.secs
+        d.nsec = d.nsec + s.nsec
+        if d.nsec >= NANOS_PER_SEC then
+            d.secs = d.secs + 1
+            d.nsec = d.nsec - NANOS_PER_SEC
+        end
+        return d
+
+    -- 2. left is date, right is duration in months
+    elseif ffi.typeof(s) == duration_months_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y, M = shift_months(y, M, s.m)
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+
+    -- 2. left is date, right is duration in years
+    elseif ffi.typeof(s) == duration_years_t then
+        local y, M, D = dt_to_ymd(local_dt(d))
+        y = y + s.y
+        local dt = cdt.dt_from_ymd(y, M, D)
+        local secs = d.secs % SECS_PER_DAY
+        return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
+    else
+        assert(false, "unexpected type")
+    end
+end
 
 -- simple parse functions:
 -- parse_date/parse_time/parse_zone
@@ -572,15 +676,28 @@ local function strftime(fmt, o)
     return ffi.string(buff)
 end
 
-local function datetime_tostring(o)
-    assert(ffi.typeof(o) == datetime_t)
-    local sz = 48
-    local buff = ffi.new('char[?]', sz)
-    local len = native.datetime_to_string(o, buff, sz)
-    assert(len < sz)
-    return ffi.string(buff)
-end
 
+local datetime_mt = {
+    -- __tostring = datetime_tostring,
+    __serialize = datetime_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
+
+local duration_mt = {
+    -- __tostring = duration_tostring,
+    __serialize = duration_serialize,
+    __eq = datetime_eq,
+    __lt = datetime_lt,
+    __le = datetime_le,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
 
 ffi.metatype(duration_t, duration_mt)
 ffi.metatype(datetime_t, datetime_mt)
@@ -588,6 +705,11 @@ ffi.metatype(datetime_t, datetime_mt)
 return setmetatable(
     {
         datetime = datetime_new,
+        years = duration_years_new,
+        months = duration_months_new,
+        days = duration_days_new,
+        hours = duration_hours_new,
+        minutes = duration_minutes_new,
         delta = duration_new,
 
         parse = parse_str,
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [Tarantool-patches] [RFC PATCH 13/13] lua: complete time duration support
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (11 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support Timur Safin via Tarantool-patches
@ 2021-07-15  8:18 ` Timur Safin via Tarantool-patches
  2021-07-15 16:56 ` [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Oleg Babin via Tarantool-patches
  2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
  14 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15  8:18 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

* reworked code which adds/subs years and months to
  use `dt_add_years()` and `dt_add_months()` instead
  of manual plumbing as before
* introduced `:add{}` and `:sub{}` methods to datetime
  object to add or substract complex periods of times
* implemented stringization of period objects
---
 src/exports.h                  |   3 +
 src/lua/datetime.lua           | 276 ++++++++++++++++++++++++---------
 test/app-tap/datetime.test.lua |  80 ++++++++--
 3 files changed, 280 insertions(+), 79 deletions(-)

diff --git a/src/exports.h b/src/exports.h
index 7397010e0..3388fb685 100644
--- a/src/exports.h
+++ b/src/exports.h
@@ -533,6 +533,9 @@ EXPORT(uuid_nil)
 EXPORT(uuid_unpack)
 EXPORT(datetime_unpack)
 EXPORT(datetime_pack)
+EXPORT(dt_add_months)
+EXPORT(dt_add_years)
+EXPORT(dt_add_quarters)
 EXPORT(dt_from_rdn)
 EXPORT(dt_from_yd)
 EXPORT(dt_from_ymd)
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 698c55ebd..d28f931f6 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -216,6 +216,13 @@ local function duration_minutes_new(m)
     return o
 end
 
+local function duration_seconds_new(s)
+    local o = ffi.new(duration_t)
+    o.nsec = s % 1 * 1e9
+    o.secs = s - (s % 1)
+    return o
+end
+
 local function datetime_eq(lhs, rhs)
     -- we usually don't need to check nullness
     -- but older tarantool console will call us checking for equality to nil
@@ -246,31 +253,141 @@ local function duration_serialize(self)
     return { secs = self.secs, nsec = self.nsec }
 end
 
+local function local_rd(o)
+    return math.floor(o.secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET
+end
+
+local function local_dt(o)
+    return cdt.dt_from_rdn(local_rd(o))
+end
+
+-- addition or subtraction from date/time of a given period described via table
+-- direction should be +1 or -1
+local function duration_increment(self, o, direction)
+    assert(direction == -1 or direction == 1)
+    assert(ffi.typeof(self) == datetime_t)
+    assert(type(o) == 'table')
+
+    local ym_updated = false
+    local dhms_updated = false
+
+    local dt = local_dt(self)
+    local secs, nsec
+    secs, nsec = self.secs, self.nsec
+
+    for key, value in pairs(o) do
+        local handlers = {
+            years = function(v)
+                assert(v > 0 and v < 10000)
+                dt = cdt.dt_add_years(dt, direction * v, cdt.DT_LIMIT)
+                ym_updated = true
+            end,
+
+            months = function(v)
+                assert(v > 0 and v < 13 )
+                dt = cdt.dt_add_months(dt, direction * v, cdt.DT_LIMIT)
+                ym_updated = true
+            end,
+
+            weeks = function(v)
+                assert(v > 0 and v < 32)
+                secs = secs + direction * 7 * v * SECS_PER_DAY
+                dhms_updated = true
+            end,
+
+            days = function(v)
+                assert(v > 0 and v < 32)
+                secs = secs + direction * v * SECS_PER_DAY
+                dhms_updated = true
+            end,
+
+            hours = function(v)
+                assert(v >= 0 and v < 24)
+                secs = secs + direction * 60 * 60 * v
+                dhms_updated = true
+            end,
+
+            minutes = function(v)
+                assert(v >= 0 and v < 60)
+                secs = secs + direction * 60 * v
+            end,
+
+            seconds = function(v)
+                assert(v >= 0 and v < 61)
+                local s, frac
+                frac = v % 1
+                if frac > 0 then
+                    s = v - (v % 1)
+                else
+                    s = v
+                end
+                secs = secs + direction * s
+                nsec = nsec + direction * frac * 1e9 -- convert fraction to nanoseconds
+                dhms_updated = true
+            end,
+        }
+        handlers[key](value)
+    end
+
+    -- normalize values
+    if nsec < 0 then
+        nsec = nsec + NANOS_PER_SEC
+        secs = secs - 1
+    elseif nsec >= NANOS_PER_SEC then
+        nsec = nsec - NANOS_PER_SEC
+        secs = secs + 1
+    end
+
+    -- .days, .hours, .minutes, .seconds
+    if dhms_updated then
+        self.secs = secs
+        self.nsec = nsec
+    end
+
+    -- .years, .months updated
+    if ym_updated then
+        self.secs = (cdt.dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY +
+                    secs % SECS_PER_DAY
+    end
+
+    return self
+end
+
 local datetime_index = function(self, key)
     local attributes = {
         timestamp = function(self)
-            return tonumber(self.secs + self.nsec / 1e9)
+            return self.secs + self.nsec / 1e9
         end,
         nanoseconds = function(self)
-            return tonumber(self.secs * 1e9 + self.nsec)
+            return self.secs * 1e9 + self.nsec
         end,
         microseconds = function(self)
-            return tonumber(self.secs * 1e6 + self.nsec / 1e3)
+            return self.secs * 1e6 + self.nsec / 1e3
         end,
         milliseconds = function(self)
-            return tonumber(self.secs * 1e3 + self.nsec / 1e6)
+            return self.secs * 1e3 + self.nsec / 1e6
         end,
         seconds = function(self)
-            return tonumber(self.secs + self.nsec / 1e9)
+            return self.secs + self.nsec / 1e9
         end,
         minutes = function(self)
-            return tonumber((self.secs + self.nsec / 1e9) / 60 % 60)
+            return (self.secs + self.nsec / 1e9) / 60
         end,
         hours = function(self)
-            return tonumber((self.secs + self.nsec / 1e9) / (60 * 60))
+            return (self.secs + self.nsec / 1e9) / (60 * 60)
         end,
         days = function(self)
-            return tonumber((self.secs + self.nsec / 1e9) / (60 * 60)) / 24
+            return (self.secs + self.nsec / 1e9) / (60 * 60) / 24
+        end,
+        add = function(self)
+            return function(self, o)
+                return duration_increment(self, o, 1)
+            end
+        end,
+        sub = function(self)
+            return function(self, o)
+                return duration_increment(self, o, -1)
+            end
         end,
     }
     return attributes[key] ~= nil and attributes[key](self) or nil
@@ -284,14 +401,6 @@ local function datetime_new_raw(secs, nsec, offset)
     return dt_obj
 end
 
-local function local_rd(o)
-    return math.floor(o.secs / SECS_PER_DAY) + DT_EPOCH_1970_OFFSET
-end
-
-local function local_dt(o)
-    return cdt.dt_from_rdn(local_rd(o))
-end
-
 local function mk_timestamp(dt, sp, fp, offset)
     local epochV = dt ~= nil and (cdt.dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY or 0
     local spV = sp ~= nil and sp or 0
@@ -367,11 +476,12 @@ local function datetime_new(o)
             second = function(v)
                 assert(v >= 0 and v < 61)
                 frac = v % 1
-                if frac then
+                if frac > 0 then
                     s = v - (v % 1)
                 else
                     s = v
                 end
+                frac = frac * 1e9 -- convert fraction to nanoseconds
                 hms = true
             end,
 
@@ -403,22 +513,37 @@ local function datetime_new(o)
 end
 
 local function datetime_tostring(o)
-    print(ffi.typeof(o))
-    assert(ffi.typeof(o) == datetime_t)
-    local sz = 48
-    local buff = ffi.new('char[?]', sz)
-    local len = native.datetime_to_string(o, buff, sz)
-    assert(len < sz)
-    return ffi.string(buff)
-end
+    if ffi.typeof(o) == datetime_t then
+        local sz = 48
+        local buff = ffi.new('char[?]', sz)
+        local len = native.datetime_to_string(o, buff, sz)
+        assert(len < sz)
+        return ffi.string(buff)
+    elseif ffi.typeof(o) == duration_years_t then
+        return ('%+d years'):format(o.y)
+    elseif ffi.typeof(o) == duration_months_t then
+        return ('%+d months'):format(o.m)
+    elseif ffi.typeof(o) == duration_t then
+        local ts = o.timestamp
+        local sign = '+'
+
+        if ts < 0 then
+            ts = -ts
+            sign = '-'
+        end
 
-local function dt_to_ymd(dt)
-    local y, m, d
-    y = ffi.new('int[1]')
-    m = ffi.new('int[1]')
-    d = ffi.new('int[1]')
-    cdt.dt_to_ymd(dt, y, m, d)
-    return y[0], m[0], d[0]
+        if ts < 60 then
+            return ('%s%s secs'):format(sign, ts)
+        elseif ts < 60 * 60 then
+            return ('%+d minutes, %s seconds'):format(o.minutes, ts % 60)
+        elseif ts < 24 * 60 * 60 then
+            return ('%+d hours, %d minutes, %s seconds'):format(
+                    o.hours, o.minutes % 60, ts % 60)
+        else
+            return ('%+d days, %d hours, %d minutes, %s seconds'):format(
+                    o.days, o.hours % 24, o.minutes % 60, ts % 60)
+        end
+    end
 end
 
 local function check_date(o)
@@ -433,40 +558,36 @@ local function date_first(lhs, rhs)
     end
 end
 
-local function shift_months(y, M, deltaM)
-    M = M + deltaM
-    local newM = (M - 1) % 12 + 1
-    local newY = y + math.floor((M - 1)/12)
-    assert(newM >= 1 and newM <= 12, "month value is outside of range")
-    return newY, newM
-end
-
 local function datetime_sub(lhs, rhs)
     check_date(lhs) -- make sure left is date
     local d, s = lhs, rhs
 
     -- 1. left is date, right is date or delta
     if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
-        d.secs = d.secs - s.secs
-        d.nsec = s.nsec - s.nsec
-        if d.nsec < 0 then
-            d.secs = d.secs - 1
-            d.nsec = d.nsec + NANOS_PER_SEC
+        local o
+        -- if they are both dates then result is delta
+        if ffi.typeof(s) == datetime_t then
+            o = duration_new()
+        else
+            o = datetime_new()
+        end
+        o.secs = d.secs - s.secs
+        o.nsec = d.nsec - s.nsec
+        if o.nsec < 0 then
+            o.secs = d.secs - 1
+            o.nsec = d.nsec + NANOS_PER_SEC
         end
+        return o
 
     -- 2. left is date, right is duration in months
     elseif ffi.typeof(s) == duration_months_t then
-        local y, M, D = dt_to_ymd(local_dt(d))
-        y, M = shift_months(y, M, -s.m)
-        local dt = cdt.dt_from_ymd(y, M, D)
+        local dt = cdt.dt_add_months(local_dt(d), -s.m, cdt.DT_LIMIT)
         local secs = d.secs % SECS_PER_DAY
         return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
 
     -- 2. left is date, right is duration in years
     elseif ffi.typeof(s) == duration_years_t then
-        local y, M, D = dt_to_ymd(local_dt(d))
-        y = y - s.y
-        local dt = cdt.dt_from_ymd(y, M, D)
+        local dt = cdt.dt_add_years(local_dt(d), -s.y, cdt.DT_LIMIT)
         local secs = d.secs % SECS_PER_DAY
         return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
     else
@@ -479,27 +600,25 @@ local function datetime_add(lhs, rhs)
 
     -- 1. left is date, right is date or delta
     if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
-        d.secs = d.secs + s.secs
-        d.nsec = d.nsec + s.nsec
-        if d.nsec >= NANOS_PER_SEC then
-            d.secs = d.secs + 1
-            d.nsec = d.nsec - NANOS_PER_SEC
+        local o = datetime_new()
+
+        o.secs = d.secs + s.secs
+        o.nsec = d.nsec + s.nsec
+        if o.nsec >= NANOS_PER_SEC then
+            o.secs = d.secs + 1
+            o.nsec = d.nsec - NANOS_PER_SEC
         end
-        return d
+        return o
 
     -- 2. left is date, right is duration in months
     elseif ffi.typeof(s) == duration_months_t then
-        local y, M, D = dt_to_ymd(local_dt(d))
-        y, M = shift_months(y, M, s.m)
-        local dt = cdt.dt_from_ymd(y, M, D)
+        local dt = cdt.dt_add_months(local_dt(d), s.m, cdt.DT_LIMIT)
         local secs = d.secs % SECS_PER_DAY
         return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
 
     -- 2. left is date, right is duration in years
     elseif ffi.typeof(s) == duration_years_t then
-        local y, M, D = dt_to_ymd(local_dt(d))
-        y = y + s.y
-        local dt = cdt.dt_from_ymd(y, M, D)
+        local dt = cdt.dt_add_years(local_dt(d), s.y, cdt.DT_LIMIT)
         local secs = d.secs % SECS_PER_DAY
         return mk_timestamp(dt, secs, d.nsec, d.offset or 0)
     else
@@ -636,7 +755,7 @@ end
 
 local function datetime_to_tm_ptr(o)
     local p_tm = ffi.new 'struct tm[1]'
-    assert(ffi.typeof(o) == datetime_t)
+    check_date(o)
     -- dt_to_struct_tm() fills only date data
     cdt.dt_to_struct_tm(local_dt(o), p_tm)
 
@@ -655,20 +774,20 @@ local function datetime_to_tm_ptr(o)
 end
 
 local function asctime(o)
-    assert(ffi.typeof(o) == datetime_t)
+    check_date(o)
     local p_tm = datetime_to_tm_ptr(o)
     return ffi.string(native.asctime(p_tm))
 end
 
 local function ctime(o)
-    assert(ffi.typeof(o) == datetime_t)
+    check_date(o)
     local p_time = ffi.new 'time_t[1]'
     p_time[0] = o.secs
     return ffi.string(native.ctime(p_time))
 end
 
 local function strftime(fmt, o)
-    assert(ffi.typeof(o) == datetime_t)
+    check_date(o)
     local sz = 50
     local buff = ffi.new('char[?]', sz)
     local p_tm = datetime_to_tm_ptr(o)
@@ -678,7 +797,7 @@ end
 
 
 local datetime_mt = {
-    -- __tostring = datetime_tostring,
+    __tostring = datetime_tostring,
     __serialize = datetime_serialize,
     __eq = datetime_eq,
     __lt = datetime_lt,
@@ -686,10 +805,18 @@ local datetime_mt = {
     __sub = datetime_sub,
     __add = datetime_add,
     __index = datetime_index,
+    add = function(self, o)
+        self = duration_increment(self, o, 1)
+        return self
+    end,
+    sub = function(self, o)
+        self = duration_increment(self, o, -1)
+        return self
+    end
 }
 
 local duration_mt = {
-    -- __tostring = duration_tostring,
+    __tostring = datetime_tostring,
     __serialize = duration_serialize,
     __eq = datetime_eq,
     __lt = datetime_lt,
@@ -699,8 +826,18 @@ local duration_mt = {
     __index = datetime_index,
 }
 
+local duration_tiny_mt = {
+    __tostring = datetime_tostring,
+    __serialize = duration_serialize,
+    __sub = datetime_sub,
+    __add = datetime_add,
+    __index = datetime_index,
+}
+
 ffi.metatype(duration_t, duration_mt)
 ffi.metatype(datetime_t, datetime_mt)
+ffi.metatype(duration_years_t, duration_tiny_mt)
+ffi.metatype(duration_months_t, duration_tiny_mt)
 
 return setmetatable(
     {
@@ -710,6 +847,7 @@ return setmetatable(
         days = duration_days_new,
         hours = duration_hours_new,
         minutes = duration_minutes_new,
+        seconds = duration_seconds_new,
         delta = duration_new,
 
         parse = parse_str,
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 06720b241..f911412bf 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -4,7 +4,7 @@ local tap = require('tap')
 local test = tap.test("errno")
 local date = require('datetime')
 
-test:plan(6)
+test:plan(8)
 
 test:test("Simple tests for parser", function(test)
     test:plan(2)
@@ -192,15 +192,75 @@ test:test("Parse tiny date into seconds and other parts", function(test)
     test:plan(9)
     local str = '19700101 00:00:30.528'
     local tiny = date(str)
-    test:ok(tiny.secs, 30, ("secs of '%s'"):format(str))
-    test:ok(tiny.nsec, 528000000, ("nsec of '%s'"):format(str))
-    test:ok(tiny.nanoseconds, 30528000000, "nanoseconds")
-    test:ok(tiny.microseconds, 30528000, "microseconds")
-    test:ok(tiny.milliseconds, 30528, "milliseconds")
-    test:ok(tiny.seconds, 30.528, "seconds")
-    test:ok(tiny.timestamp, 30.528, "timestamp")
-    test:ok(tiny.minutes, 0.5088, "minuts")
-    test:ok(tiny.hours,  0.00848, "hours")
+    test:ok(tiny.secs == 30, ("secs of '%s'"):format(str))
+    test:ok(tiny.nsec == 528000000, ("nsec of '%s'"):format(str))
+    test:ok(tiny.nanoseconds == 30528000000, "nanoseconds")
+    test:ok(tiny.microseconds == 30528000, "microseconds")
+    test:ok(tiny.milliseconds == 30528, "milliseconds")
+    test:ok(tiny.seconds == 30.528, "seconds")
+    test:ok(tiny.timestamp == 30.528, "timestamp")
+    test:ok(tiny.minutes == 0.5088, "minuts")
+    test:ok(tiny.hours == 0.00848, "hours")
+end)
+
+test:test("Stringization of dates and periods", function(test)
+    test:plan(13)
+    local str = '19700101Z'
+    local dt = date(str)
+    test:ok(tostring(dt) == '1970-01-01T00:00Z', ('tostring(%s)'):format(str))
+    test:ok(tostring(date.seconds(12)) == '+12 secs', '+12 seconds')
+    test:ok(tostring(date.seconds(-12)) == '-12 secs', '-12 seconds')
+    test:ok(tostring(date.minutes(12)) == '+12 minutes, 0 seconds', '+12 minutes')
+    test:ok(tostring(date.minutes(-12)) == '-12 minutes, 0 seconds', '-12 minutes')
+    test:ok(tostring(date.hours(12)) == '+12 hours, 0 minutes, 0 seconds',
+            '+12 hours')
+    test:ok(tostring(date.hours(-12)) == '-12 hours, 0 minutes, 0 seconds',
+            '-12 hours')
+    test:ok(tostring(date.days(12)) == '+12 days, 0 hours, 0 minutes, 0 seconds',
+            '+12 days')
+    test:ok(tostring(date.days(-12)) == '-12 days, 0 hours, 0 minutes, 0 seconds',
+            '-12 days')
+    test:ok(tostring(date.months(5)) == '+5 months', '+5 months')
+    test:ok(tostring(date.months(-5)) == '-5 months', '-5 months')
+    test:ok(tostring(date.years(4)) == '+4 years', '+4 years')
+    test:ok(tostring(date.years(-4)) == '-4 years', '-4 years')
+end)
+
+test:test("Time duration operations", function(test)
+    test:plan(12)
+
+    -- check arithmetic with leap dates
+    local T = date('1972-02-29')
+    local M = date.months(2)
+    local Y = date.years(1)
+    test:ok(tostring(T + M) == '1972-04-29T00:00Z', ('T(%s) + M(%s'):format(T, M))
+    test:ok(tostring(T + Y) == '1973-03-01T00:00Z', ('T(%s) + Y(%s'):format(T, Y))
+    test:ok(tostring(T + M + Y) == '1973-04-30T00:00Z',
+            ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+    test:ok(tostring(T + Y + M) == '1973-05-01T00:00Z',
+            ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+    test:ok(tostring(T:add{years = 1, months = 2}) == '1973-04-30T00:00Z',
+            ('T:add{years=1,months=2}(%s)'):format(T))
+
+    -- check average, not leap dates
+    T = date('1970-01-08')
+    test:ok(tostring(T + M) == '1970-03-08T00:00Z', ('T(%s) + M(%s'):format(T, M))
+    test:ok(tostring(T + Y) == '1971-01-08T00:00Z', ('T(%s) + Y(%s'):format(T, Y))
+    test:ok(tostring(T + M + Y) == '1971-03-08T00:00Z',
+            ('T(%s) + M(%s) + Y(%s'):format(T, M, Y))
+    test:ok(tostring(T + Y + M) == '1971-03-08T00:00Z',
+            ('T(%s) + Y(%s) + M(%s'):format(T, Y, M))
+    test:ok(tostring(T:add{years = 1, months = 2}) == '1971-03-08T00:00Z',
+            ('T:add{years=1,months=2}(%s)'):format(T))
+
+
+    -- subtraction of 2 dates
+    local T2 = date('19700103')
+    local T1 = date('1970-01-01')
+    test:ok(tostring(T2 - T1) == '+2 days, 0 hours, 0 minutes, 0 seconds',
+            ('T2(%s) - T1(%s'):format(T2, T1))
+    test:ok(tostring(T1 - T2) == '-2 days, 0 hours, 0 minutes, 0 seconds',
+            ('T2(%s) - T1(%s'):format(T2, T1))
 end)
 
 os.exit(test:check() and 0 or 1)
-- 
2.29.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (12 preceding siblings ...)
  2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 13/13] lua: complete " Timur Safin via Tarantool-patches
@ 2021-07-15 16:56 ` Oleg Babin via Tarantool-patches
  2021-07-15 23:03   ` Timur Safin via Tarantool-patches
  2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
  14 siblings, 1 reply; 19+ messages in thread
From: Oleg Babin via Tarantool-patches @ 2021-07-15 16:56 UTC (permalink / raw)
  To: tarantool-patches

Thanks for your patch.


I'd want to use native datetime data type in one project

that should be released soon. So I'll give you some feedback because I'm 
really

interested in this patch. I probably won't perform deep review and I'll 
just give some

feedback about an interface.


1. I see you implemented "datetime" module as ffi wrapper over c-dt.

Why did you choose FFI and could you show that FFI is better than Lua C API?

I know there are such benchmark for decimal and they show that FFI was 
slower

than Lua C API 
(https://github.com/tarantool/tarantool/issues/692#issuecomment-505043929).


2. Could you please implement a hepler `is_datetime`? Uuid and decimal 
have is_* methods.


3. Modue throws unhandled error:

```

tarantool> datetime.now() == newproxy()
---
- error: 'builtin/datetime.lua:232: attempt to index local ''rhs'' (a 
userdata value)'
...

```

I think you can see how works uuid module 
(https://github.com/tarantool/tarantool/blob/master/src/lua/uuid.lua)

to fix such errors.


4. Parse function has quite strange output format

```

tarantool> datetime.parse_date('2016-12-18')
---
- 2016-12-18T00:00Z
- 10
...

tarantool> datetime.parse_date('2016-13-18')
---
- null
- 0
...

```


Do we really need the second value in case of success?

Do we really need 0 as the second return value in case of failue? 
Probably we should extend error message somehow.


5. More strange errors, please improve the validation.

```

tarantool> datetime.days()
---
- error: 'builtin/datetime.lua:203: attempt to perform arithmetic on 
local ''d'' (a
     nil value)'
...

tarantool> datetime.ctime()
---
- error: 'builtin/datetime.lua:550: bad argument #1 to ''typeof'' (C 
type expected,
     got nil)'
...

```


Also I see some assertions in code (e.g. `assert(v >= 0 and v < 61)`). 
This yields to

"assertion failed" message that is not completely clear.


6. Does this module allows to manipulate with timezones and locales?

Icu-date that we currently use allows it 
(https://github.com/tarantool/icu-date).

Also try to use tests from icu-date module. I think it could be useful. 
The best option is

to allow drop-in but I'm not sure that it's possible.


7. Is it expected?

```

tarantool> datetime.now() - datetime.now()
---
- +18823 days, 16 hours, 29 minutes, 44.178836107254 seconds
...

```

I believe it should be 0.


8. Could you provide a helper that allows to convert unix time to datetime?

I tried this way but it seems an interval instead of datetime.

```

tarantool> datetime.seconds(datetime.now().seconds)
---
- +18823 days, 16 hours, 34 minutes, 10.283248901367 seconds
...

```


9. Lua output doesn't support datetime

```

tarantool> \set output yaml
---
- true
...

tarantool> datetime.now()
---
- 2021-07-15T19:46:15.797333+03:00
...

tarantool> \set output lua
true;
tarantool> datetime.now()
nil;

```


10. You can export also the number of months/days in week as constansts.

Probably it could be useful to make something like:

```

datetime_object:set('month', datetime.months.JANUARY)

```


It was brief feedback as from (I hope) future customer of this module.

If I've found another interface issues I'll give you feedback in next 
e-mails.

I didn't see code itself if you need it I can review this patchset in 
more detailed way.


  On 15.07.2021 11:18, Timur Safin via Tarantool-patches wrote:
> Here is the preliminary RFC version of a patchset, which is implementing
> datetime lua support in box, their messagepack, yaml, json serialization
> support, and a set of c and lua tests.
>
> * It's heavily influenced by Sci-Lua lua-time module implementation
>    https://github.com/stepelu/lua-time
>    e.g. you could find very similar approach for handling of operations
>    with year and months periods (which should be handled differently than
>    usual seconds, days periods).
>
> * But internally it actually uses Christina Hanson' c-dt module
>    https://github.com/chansen/c-dt
>    (though it has been modified slightly for cleaner integration
>    into cmake build process)
>
> This is preliminary version of patchset, with unoptimal patches divisions
> (like you could see patches with rename refactorings), but, due to an amount
> of code in patchset, and limited time have, I send patchset as RFC today,
> expecting to receive initial feedback for various parts of a series.
>
> Prior plan was to split series into one, which introduces datetime module api,
> and another one with datetime messagepack serialization implementation.
> But here they left as part of a same series. Sorry, for invonvenience - I'll
> reshuffle order according to feedback provided.
>
> The problem, which was stopping such simple division - that `datetime.lua`
> module uses `datetime_to_string()` ffi function for stringization of a
> datetime object. And this function has been introduced as part of
> messagepack/yaml/json serialization support. Yes, intra-dependendencies
> between parts looks suboptimal, and I'm very open to suggestions how to
> divide it properly.
>
> Module API
> ==========
>
> To simplify review of a code in patchset below you could find preliminary
> documentation of a datetime api module. Eventually we may put it
> as .rst file in documentation repository (though, personally, I'd prefer
> to have all modules api committed to the same repo as code, but I digress)
>
> Datetime module
> ===============
>
> This module allows to parse date/time stamps, manipulate with them, and
> generate textual representation. Parsing of date constants will handle
> all ISO-8601 formats, and deal with some extensions.
>
> For internal date representation it uses cdata structures of a form:
>
> ```c++
> struct t_datetime {
> 	int secs;
> 	int nsecs;
> 	int offset;
> };
> ```
>
> Where:
>
> -   secs is the (signed) number of seconds since epoch 1970-01-01T00:00Z;
> -   nsecs is number of nanoseconds since beginning of day;
> -   offset is the timezone offset (in minutes);
>
> `datetime()` � create datetime object
> -------------------------------------
>
> Create date/time object using either string literals, or initialization
> object. When given string literals it behaves like wrapper around
> parse() method.
>
> When there is initialization object it could create date/time object
> using these attributes below:
>
> | **Easy way** |                                                                                                 |
> |--------------|-------------------------------------------------------------------------------------------------|
> | secs         | Seconds since epoch                                                                             |
> | nsec         | Nanoseconds since midnight                                                                      |
> | offset       | Time-zone offset in minutes                                                                     |
> | **YMD part** |                                                                                                 |
> | year         | Year in range \[1..9999\]                                                                       |
> | month        | Month in range \[1..12\]                                                                        |
> | day          | Day in month in range \[1..31\]                                                                 |
> | **HMS part** |                                                                                                 |
> | hour         | Hour in range \[0..23\]                                                                         |
> | minute       | Minute in range \[0..59\]                                                                       |
> | second       | Seconds in range \[0..60\]. It allows to have fraction part in which case it goes to nsec field |
> | tz           | Timezone offset (in minutes) for HMS part                                                       |
>
> Example:
>
> ```lua
>
> datetime = require `datetime`
>
> d = datetime{secs = 0, nsec = 0, offset = 0}
> d = datetime(�1970-01-01�)
> d = datetime{year = 1970, month = 1, day = 1}
> ```
>
> `delta()` � create time duration object
> ---------------------------------------
>
> TBD
>
> `parse()` � parse full ISO-8601 string
> --------------------------------------
>
> Parse full length date/time literal, which may be in ISO-8601 format of
> any of extended formats supported by `parse_date()`, `parse_time()` or
> `parse_timezone()`
>
> It deals with date/time string in format
>
> `date ([Tt ] time ([ ] time_zone)? )?`
>
> Where time or `time_zone` parts may be omitted.
>
> Example:
>
> ```lua
>
> datetime = require `datetime`
>
> d = datetime.parse(`1970-01-01`)
> d = datetime.parse(`1970-01-01T00:00:00Z`)
> d = datetime.parse(`1970-01-01T02:00:00+02:00`)
> ```
>
> `parse_date()` � parse ISO-8601 date literal
> --------------------------------------------
>
> Parse date string literal, return partial date object which has
> precision of up-to date period of time.
>
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
>
> | 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             |
>
> `parse_time()` � parse ISO-8601 time literal
> --------------------------------------------
>
> Parse time string literal, return partial date/time object, which
> defines time period inside of single date.
>
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
>
> | 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.
>
> `parse_zone()` � parse ISO-8601 time zone
> -----------------------------------------
>
> Parse time-zone string literal, return partial date/time object, which
> defines timezone offset in minutes sing GMT.
>
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
>
> | Basic | Extended |
> |-------|----------|
> | Z     | N/A      |
> | �hh   | N/A      |
> | �hhmm | �hh:mm   |
>
> `tostring()` � convert datetime object to string
> ------------------------------------------------
>
> Return string representation (probably compact if there are some parts
> missing) of a date-time objects passed
>
> `now()` � return current date/time
> ----------------------------------
>
> `now()` returns local date and time object. It will use local time-zone
> and nanosecond precision.
>
> `strftime()` � convert date object to string using format
> ---------------------------------------------------------
>
> `strftime()` is the FFI wrapper around strftime() function in LIBC. It
> supports all the same flags which supports strftime() from host OS.
>
> See
> <https://pubs.opengroup.org/onlinepubs/000095399/functions/strftime.html>
> for more details.
>
> `asctime()` � convert date object to string using asctime predefined format
> ---------------------------------------------------------------------------
>
> `asctime()` is the FFI wrapper over `asctime_r()` from a host libc. asctime
> returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`
>
> <https://pubs.opengroup.org/onlinepubs/009695399/functions/asctime.html>
>
> `ctime()` � convert local time to string using ctime() predefined format
> ------------------------------------------------------------------------
>
> `ctime()` is the FFI wrapper over `ctime_r()` in the host libc. ctime
> returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`
>
> <https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html>
>
> The difference of `ctime()` and `asctime()` is that former is returning
> local time zone formatted, while the latter will deal with GMT.
>
> Examples:
>
> ```
> tarantool> date = require 'datetime'
> ---
> ...
> tarantool> T = date.now()
> ---
> ...
> tarantool> T
> ---
> - 2021-07-14T01:36:48.554105+03:00
> ...
> tarantool> date.asctime(T)
> ---
> - 'Tue Jul 13 22:36:48 2021
> '
> ...
> tarantool> date.ctime(T)
> ---
> - 'Wed Jul 14 01:36:48 2021
> '
> ...
> ```
>
> Date attribute accessors
> ------------------------
>
> |                |                                                                 |
> |----------------|-----------------------------------------------------------------|
> | `timestamp`    | Calculate timestamp with seconds and nanoseconds parts combined |
> | `nanoseconds`  | Number of nanoseconds in time part                              |
> | `microseconds` | Number of microseconds in time part                             |
> | `milliseconds` | Number of milliseconds in time part                             |
> | `seconds`      | Alias to timestamp                                              |
> | `minutes`      | Number of minutes in time part                                  |
> | `hours`        | Number of hours in time part                                    |
> | `days`         | Number of days in time part                                     |
>
> ```
> tarantool> d = date{year = 1970, month = 1, day = 1, hour = 0, minute = 10, second=10}
> tarantool> d.secs
> ---
> - 610
> ...
> tarantool> d.nsec
> ---
> - 0
> ...
> tarantool> d.offset
> ---
> - 0
> ...
> tarantool> d.nanoseconds
> ---
> - 610000000000
> ...
> tarantool> d.milliseconds
> ---
> - 610000
> ...
> tarantool> d.hours
> ---
> - 0.16944444444444
> ...
> tarantool> d.minutes
> ---
> - 10.166666666667
> ...
> ```
>
> Date arithmetic
> ---------------
>
> TBD
>
>
> https://github.com/tarantool/tarantool/issues/5941
> https://github.com/tarantool/tarantool/issues/5946
> https://github.com/tarantool/tarantool/tree/tsafin/gh-5941-datetime-V2
>
>
>
> Timur Safin (13):
>    build: add Christian Hansen c-dt to the build
>    lua: built-in module datetime
>    test: datetime test
>    test: datetime string formatting
>    box: add messagepack support for datetime
>    lua: positive/negative cases in datetime test
>    lua: asctime and strfime fixed
>    box, lua: renamed t_datetime_tz structure to datetime_t
>    lua: calculated attributes for date
>    lua: tostring formatization in datetime.lua
>    test: allow relaxed date format without tz
>    lua: initial time duration support
>    lua: complete time duration support
>
>   .gitmodules                       |   3 +
>   CMakeLists.txt                    |   8 +
>   cmake/BuildCDT.cmake              |   6 +
>   src/CMakeLists.txt                |   4 +
>   src/box/field_def.c               |  34 +-
>   src/box/field_def.h               |   1 +
>   src/box/msgpack.c                 |   7 +-
>   src/box/tuple_compare.cc          |  24 +
>   src/exports.h                     |  26 +
>   src/lib/core/CMakeLists.txt       |   4 +-
>   src/lib/core/datetime.h           |  96 ++++
>   src/lib/core/mp_datetime.c        | 232 ++++++++
>   src/lib/core/mp_extension_types.h |   1 +
>   src/lib/mpstream/mpstream.c       |  11 +
>   src/lib/mpstream/mpstream.h       |   4 +
>   src/lua/datetime.c                |  70 +++
>   src/lua/datetime.h                |  53 ++
>   src/lua/datetime.lua              | 868 ++++++++++++++++++++++++++++++
>   src/lua/init.c                    |   6 +-
>   src/lua/msgpack.c                 |  12 +
>   src/lua/msgpackffi.lua            |   8 +
>   src/lua/serializer.c              |   4 +
>   src/lua/serializer.h              |   2 +
>   src/lua/utils.c                   |   1 -
>   test/app-tap/datetime.test.lua    | 266 +++++++++
>   test/unit/CMakeLists.txt          |   2 +
>   test/unit/datetime.c              | 220 ++++++++
>   test/unit/datetime.result         | 358 ++++++++++++
>   third_party/c-dt                  |   1 +
>   third_party/lua-cjson/lua_cjson.c |   8 +
>   third_party/lua-yaml/lyaml.cc     |   6 +-
>   31 files changed, 2326 insertions(+), 20 deletions(-)
>   create mode 100644 cmake/BuildCDT.cmake
>   create mode 100644 src/lib/core/datetime.h
>   create mode 100644 src/lib/core/mp_datetime.c
>   create mode 100644 src/lua/datetime.c
>   create mode 100644 src/lua/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
>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
  2021-07-15 16:56 ` [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Oleg Babin via Tarantool-patches
@ 2021-07-15 23:03   ` Timur Safin via Tarantool-patches
  2021-07-16  6:58     ` Oleg Babin via Tarantool-patches
  0 siblings, 1 reply; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-15 23:03 UTC (permalink / raw)
  To: 'Oleg Babin'; +Cc: TML

Thanks, Oleg, for your fast and valuable feedback! 
[ You are the fastest hand on the wild west today! ]


: From: Tarantool-patches <tarantool-patches-bounces@dev.tarantool.org> On
: Subject: Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
: 
: Thanks for your patch.
: 
: 
: I'd want to use native datetime data type in one project 
: that should be released soon. So I'll give you some feedback because I'm
: really interested in this patch. I probably won't perform deep review and I'll
: just give some feedback about an interface.

Very good! At this stage of feature development feedback to api created is 
the most valuable.

: 1. I see you implemented "datetime" module as ffi wrapper over c-dt.
: 
: Why did you choose FFI and could you show that FFI is better than Lua C API?
: 
: I know there are such benchmark for decimal and they show that FFI was
: slower than Lua C API
: (https://github.com/tarantool/tarantool/issues/692#issuecomment-505043929).

I didn't see these results, that was prior my Tarantool times. Very interesting!
I've asked to LuaJIT woodoo masters for their feedback (it will be very 
unfortunate to see the necessity to rework whole C interface from such 
convenient FFI mechanism). But, well, let us check performance here.

: 2. Could you please implement a hepler `is_datetime`? Uuid and decimal
: have is_* methods.

Yes, will do so.

: 
: 
: 3. Modue throws unhandled error:
: 
: ```
: 
: tarantool> datetime.now() == newproxy()
: ---
: - error: 'builtin/datetime.lua:232: attempt to index local ''rhs'' (a
: userdata value)'
: ...
: 
: ```
: 
: I think you can see how works uuid module
: (https://github.com/tarantool/tarantool/blob/master/src/lua/uuid.lua)
: 
: to fix such errors.

Yes, careful processing of invalid arguments now is the missing point
at the moment. [Will is is_datetime() wherever it's reasonable.]

: 
: 4. Parse function has quite strange output format
: 
: ```
: 
: tarantool> datetime.parse_date('2016-12-18')
: ---
: - 2016-12-18T00:00Z
: - 10
: ...
: 
: tarantool> datetime.parse_date('2016-13-18')
: ---
: - null
: - 0
: ...
: 
: ```
: 
: 
: Do we really need the second value in case of success?
: 
: Do we really need 0 as the second return value in case of failue?
: Probably we should extend error message somehow.

Good question! I could explain why it behave so strangely - underlying
c-dt dt_parse_iso_date(), dt_parse_iso_time() and dt_parse_iso_timezone()
could deal with incorrect suffices, but return the length of properly
parsed part of string, thus I return this length as 2nd value in returned
tuple. You could see how it's used in the test/app-tap/datetime.test.lua 
cases named "Parse iso date - valid strings" and "Parse iso date - 
invalid strings". I agreed that it looks strangely, and we may rework 
a way we signal parse errors here.

: 
: 
: 5. More strange errors, please improve the validation.
: 
: ```
: 
: tarantool> datetime.days()
: ---
: - error: 'builtin/datetime.lua:203: attempt to perform arithmetic on
: local ''d'' (a
:      nil value)'
: ...
: 
: tarantool> datetime.ctime()
: ---
: - error: 'builtin/datetime.lua:550: bad argument #1 to ''typeof'' (C
: type expected,
:      got nil)'
: ...
: 
: ```

Yes, will be handled similarly to the cases above (where is_datetime() 
Would be used for invalid arguments handling).

: 
: 
: Also I see some assertions in code (e.g. `assert(v >= 0 and v < 61)`).
: This yields to
: 
: "assertion failed" message that is not completely clear.

Agreed that many assertions there are not user-friendly and ask
for some massaging.

: 
: 6. Does this module allows to manipulate with timezones and locales?
: 
: Icu-date that we currently use allows it
: (https://github.com/tarantool/icu-date).

Not, at the moment. And I do like how it's handled in icu-date. And 
surprisingly that was exactly original plan - to use icu database 
for translation of timezone names into their offsets, with memorization
of results (exactly as it's done in zone_id_cache in icu-date.lua).

Locales is the strongest feature in icu-date, and should be reused 
properly while handling named-timezones. Will return here

: 
: Also try to use tests from icu-date module. I think it could be useful.
: The best option is to allow drop-in but I'm not sure that it's possible.

Drop-in is hardly possible (because icu-date api looks too much mouthful
with all their forms of a kind date:get(icu_date.fields.MILLISECOND) or
date:get_attribute(attributes.MINIMAL_DAYS_IN_FIRST_WEEK). But tests might
be of help. (And I assume you meant tap tests, not busted tests). And while
we are here - why there are 2 kinds of tests? (And not only busted used because
it's much more convenient? :) )

: 7. Is it expected?
: 
: ```
: 
: tarantool> datetime.now() - datetime.now()
: ---
: - +18823 days, 16 hours, 29 minutes, 44.178836107254 seconds
: ...
: 
: ```
: 
: I believe it should be 0.

Not exactly 0, but very small fraction of second. Thanks for reporting - that
Was artifact of last minute rename/refactoring. Here is the patch for this 
Problem and similar in +

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index d28f931f6..57ef80403 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -558,6 +558,17 @@ local function date_first(lhs, rhs)
     end
 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_sub(lhs, rhs)
     check_date(lhs) -- make sure left is date
     local d, s = lhs, rhs
@@ -565,18 +576,15 @@ local function datetime_sub(lhs, rhs)
     -- 1. left is date, right is date or delta
     if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
         local o
+
         -- if they are both dates then result is delta
         if ffi.typeof(s) == datetime_t then
             o = duration_new()
         else
             o = datetime_new()
         end
-        o.secs = d.secs - s.secs
-        o.nsec = d.nsec - s.nsec
-        if o.nsec < 0 then
-            o.secs = d.secs - 1
-            o.nsec = d.nsec + NANOS_PER_SEC
-        end
+        o.secs, o.nsec = _normalize_nsec(d.secs - s.secs, d.nsec - s.nsec)
+
         return o
 
     -- 2. left is date, right is duration in months
@@ -602,12 +610,8 @@ local function datetime_add(lhs, rhs)
     if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
         local o = datetime_new()
 
-        o.secs = d.secs + s.secs
-        o.nsec = d.nsec + s.nsec
-        if o.nsec >= NANOS_PER_SEC then
-            o.secs = d.secs + 1
-            o.nsec = d.nsec - NANOS_PER_SEC
-        end
+        o.secs, o.nsec = _normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
+
         return o
 
     -- 2. left is date, right is duration in months

: 
: 
: 8. Could you provide a helper that allows to convert unix time to datetime?

Will introduce something, yes, we need one. 

: 
: I tried this way but it seems an interval instead of datetime.
: 
: ```
: 
: tarantool> datetime.seconds(datetime.now().seconds)
: ---
: - +18823 days, 16 hours, 34 minutes, 10.283248901367 seconds
: ...
: 
: ```
: 
: 
: 9. Lua output doesn't support datetime
: 
: ```
: 
: tarantool> \set output yaml
: ---
: - true
: ...
: 
: tarantool> datetime.now()
: ---
: - 2021-07-15T19:46:15.797333+03:00
: ...
: 
: tarantool> \set output lua
: true;
: tarantool> datetime.now()
: nil;
: 
: ```

Interesting, didn't know of that. Do you have any hints where it's 
Implemented for, say, decimal, or uuid?

: 
: 
: 10. You can export also the number of months/days in week as constansts.
: 
: Probably it could be useful to make something like:
: 
: ```
: datetime_object:set('month', datetime.months.JANUARY)
: 
: ```

Hmm, hmm. Need to discuss with others. Didn't see so much details exported
in python or perl datetime modules. How frequently you needed such 
functionality from icu-date?

: 
: 
: It was brief feedback as from (I hope) future customer of this module.
: 
: If I've found another interface issues I'll give you feedback in next
: e-mails.
: 
: I didn't see code itself if you need it I can review this patchset in
: more detailed way.

That would be interesting to hear, thanks in advance!

Timur


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
  2021-07-15 23:03   ` Timur Safin via Tarantool-patches
@ 2021-07-16  6:58     ` Oleg Babin via Tarantool-patches
  0 siblings, 0 replies; 19+ messages in thread
From: Oleg Babin via Tarantool-patches @ 2021-07-16  6:58 UTC (permalink / raw)
  To: Timur Safin; +Cc: TML


On 16.07.2021 02:03, Timur Safin wrote:
> Thanks, Oleg, for your fast and valuable feedback!
> [ You are the fastest hand on the wild west today! ]
>
>
> : From: Tarantool-patches <tarantool-patches-bounces@dev.tarantool.org> On
> : Subject: Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
> :
<...>
> :
> : 6. Does this module allows to manipulate with timezones and locales?
> :
> : Icu-date that we currently use allows it
> : (https://github.com/tarantool/icu-date).
>
> Not, at the moment. And I do like how it's handled in icu-date. And
> surprisingly that was exactly original plan - to use icu database
> for translation of timezone names into their offsets, with memorization
> of results (exactly as it's done in zone_id_cache in icu-date.lua).
>
> Locales is the strongest feature in icu-date, and should be reused
> properly while handling named-timezones. Will return here
>
> :
> : Also try to use tests from icu-date module. I think it could be useful.
> : The best option is to allow drop-in but I'm not sure that it's possible.
>
> Drop-in is hardly possible (because icu-date api looks too much mouthful
> with all their forms of a kind date:get(icu_date.fields.MILLISECOND) or
> date:get_attribute(attributes.MINIMAL_DAYS_IN_FIRST_WEEK). But tests might
> be of help. (And I assume you meant tap tests, not busted tests). And while
> we are here - why there are 2 kinds of tests? (And not only busted used because
> it's much more convenient? :) )

Because it's impossible to install "busted" via tarantoolctl, because 
nobody tests is it compatible with Tarantool.

Tarantool has built-in tap module that is used and also there is a 
luatest that seems to be ok for many cases.

(But luatest was introduced a bit later than module was improted so we 
use tap for test).

>
> : 7. Is it expected?
> :
> : ```
> :
> : tarantool> datetime.now() - datetime.now()
> : ---
> : - +18823 days, 16 hours, 29 minutes, 44.178836107254 seconds
> : ...
> :
> : ```
> :
> : I believe it should be 0.
>
> Not exactly 0, but very small fraction of second. Thanks for reporting - that
> Was artifact of last minute rename/refactoring. Here is the patch for this
> Problem and similar in +
>
> diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
> index d28f931f6..57ef80403 100644
> --- a/src/lua/datetime.lua
> +++ b/src/lua/datetime.lua
> @@ -558,6 +558,17 @@ local function date_first(lhs, rhs)
>       end
>   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_sub(lhs, rhs)
>       check_date(lhs) -- make sure left is date
>       local d, s = lhs, rhs
> @@ -565,18 +576,15 @@ local function datetime_sub(lhs, rhs)
>       -- 1. left is date, right is date or delta
>       if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
>           local o
> +
>           -- if they are both dates then result is delta
>           if ffi.typeof(s) == datetime_t then
>               o = duration_new()
>           else
>               o = datetime_new()
>           end
> -        o.secs = d.secs - s.secs
> -        o.nsec = d.nsec - s.nsec
> -        if o.nsec < 0 then
> -            o.secs = d.secs - 1
> -            o.nsec = d.nsec + NANOS_PER_SEC
> -        end
> +        o.secs, o.nsec = _normalize_nsec(d.secs - s.secs, d.nsec - s.nsec)
> +
>           return o
>   
>       -- 2. left is date, right is duration in months
> @@ -602,12 +610,8 @@ local function datetime_add(lhs, rhs)
>       if (ffi.typeof(s) == datetime_t) or (ffi.typeof(s) == duration_t) then
>           local o = datetime_new()
>   
> -        o.secs = d.secs + s.secs
> -        o.nsec = d.nsec + s.nsec
> -        if o.nsec >= NANOS_PER_SEC then
> -            o.secs = d.secs + 1
> -            o.nsec = d.nsec - NANOS_PER_SEC
> -        end
> +        o.secs, o.nsec = _normalize_nsec(d.secs + s.secs, d.nsec + s.nsec)
> +
>           return o
>   
>       -- 2. left is date, right is duration in months
>
> :
> :
> : 8. Could you provide a helper that allows to convert unix time to datetime?
>
> Will introduce something, yes, we need one.

Thanks!

>
> :
> : I tried this way but it seems an interval instead of datetime.
> :
> : ```
> :
> : tarantool> datetime.seconds(datetime.now().seconds)
> : ---
> : - +18823 days, 16 hours, 34 minutes, 10.283248901367 seconds
> : ...
> :
> : ```
> :
> :
> : 9. Lua output doesn't support datetime
> :
> : ```
> :
> : tarantool> \set output yaml
> : ---
> : - true
> : ...
> :
> : tarantool> datetime.now()
> : ---
> : - 2021-07-15T19:46:15.797333+03:00
> : ...
> :
> : tarantool> \set output lua
> : true;
> : tarantool> datetime.now()
> : nil;
> :
> : ```
>
> Interesting, didn't know of that. Do you have any hints where it's
> Implemented for, say, decimal, or uuid?

https://github.com/tarantool/tarantool/blob/master/src/box/lua/serialize_lua.c

>
> :
> :
> : 10. You can export also the number of months/days in week as constansts.
> :
> : Probably it could be useful to make something like:
> :
> : ```
> : datetime_object:set('month', datetime.months.JANUARY)
> :
> : ```
>
> Hmm, hmm. Need to discuss with others. Didn't see so much details exported
> in python or perl datetime modules. How frequently you needed such
> functionality from icu-date?

I can't anser this question. Because we just provide some interfaces for 
our customers that is actually

wrappers over icu-date.

As I can see there are several function that uses it. There is one of them:

```

-- Returns the start of the current UTC date (zero hours, minutes, 
seconds, etc.)
-- The result is in nanoseconds
local function curr_date_nsec()
     local date = get_icu_date_instance()

     date:set_millis(date:now())
     local year = date:get(icu_date.fields.YEAR)
     local month = date:get(icu_date.fields.MONTH)
     local day_of_month = date:get(icu_date.fields.DAY_OF_MONTH)

     date:set_millis(0)
     date:set(icu_date.fields.YEAR, year)
     date:set(icu_date.fields.MONTH, month)
     date:set(icu_date.fields.DAY_OF_MONTH, day_of_month)

     return date:get_millis() * NSEC_IN_MILLISEC
end

```

However here we need to just strip datetime to just date.

Also it seems that getters are used in more intensive way:

```

local function nsec_to_day_of_week(nsec)
     checks("number|cdata")
     local millis = to_milliseconds(nsec)
     local date = get_icu_date_instance()
     date:set_millis(millis)
     return date:get(icu_date.fields.DAY_OF_WEEK)
end

local function parse_unix_time(nsec)
     checks("number|cdata")
     local millis = to_milliseconds(nsec)
     local date = get_icu_date_instance()
     date:set_millis(millis)
     return {
         year = date:get(icu_date.fields.YEAR),
         month = date:get(icu_date.fields.MONTH) + 1, -- starts with 0
         day = date:get(icu_date.fields.DAY_OF_MONTH),
     }
end

```

>
> :
> :
> : It was brief feedback as from (I hope) future customer of this module.
> :
> : If I've found another interface issues I'll give you feedback in next
> : e-mails.
> :
> : I didn't see code itself if you need it I can review this patchset in
> : more detailed way.
>
> That would be interesting to hear, thanks in advance!
>
> Timur
>
I think I'll do it on the next iteration of this patch. Thanks for your 
work on it.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
  2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
                   ` (13 preceding siblings ...)
  2021-07-15 16:56 ` [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Oleg Babin via Tarantool-patches
@ 2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
  2021-07-22 12:54   ` Timur Safin via Tarantool-patches
  14 siblings, 1 reply; 19+ messages in thread
From: Igor Munkin via Tarantool-patches @ 2021-07-22 10:01 UTC (permalink / raw)
  To: Timur Safin; +Cc: tarantool-patches, v.shpilevoy

Timur,

Thanks for the series! I wonder, why you decided to reinvent several
approaches being already used for quite similar Tarantool types: decimal
and uuid. Frankly speaking, I expect you introduce datetime the very
*very* similar way. I didn't make the precise review for this series
(this is waste of time), so please consider my major comments below.

Let's start with top 3 comments to the eye catching problems I've found
while looking at the patches.

1. Tests have to be committed in the same scope the corresponding
   changes in Tarantool are introduced.
2. Docbot request is required for the changes you're introducing: this
   is the place where you can leave the description you've added below.
3. Please consider approaches used for decimal and uuid: almost every
   Oleg's comment came from the bugs fixed for these types.

As I write above, this is not a precise review, so I don't even start
nitpicking the patches and simply address you to this document[1] and
our dev guide[2]. Please, check whether your next series respects these
guidelines.

On 15.07.21, Timur Safin wrote:
> Here is the preliminary RFC version of a patchset, which is implementing
> datetime lua support in box, their messagepack, yaml, json serialization
> support, and a set of c and lua tests.
> 
> * It's heavily influenced by Sci-Lua lua-time module implementation
>   https://github.com/stepelu/lua-time 
>   e.g. you could find very similar approach for handling of operations
>   with year and months periods (which should be handled differently than
>   usual seconds, days periods).
> 
> * But internally it actually uses Christina Hanson' c-dt module
>   https://github.com/chansen/c-dt 
>   (though it has been modified slightly for cleaner integration
>   into cmake build process)

BTW, submodule points to the very strange repo: please use either
vanilla one, or create a fork into Tarantool repositories.

> 
> This is preliminary version of patchset, with unoptimal patches divisions
> (like you could see patches with rename refactorings), but, due to an amount
> of code in patchset, and limited time have, I send patchset as RFC today,
> expecting to receive initial feedback for various parts of a series.

Well, I guess I can stop here for the initial feedback.

I have no idea, why you decided to introduce a separate tiny module
src/lua/datetime.c just for initializing several constants and a
oneliner for pushing datetime GCcdata on Lua stack. This module is
excess. Please consider the way this is done for uuid and move
everything to src/lua/utils.c that encloses all illegitimate tricks with
Lua C API.

> 
> Prior plan was to split series into one, which introduces datetime module api,
> and another one with datetime messagepack serialization implementation.
> But here they left as part of a same series. Sorry, for invonvenience - I'll
> reshuffle order according to feedback provided.

[I personally don't know what kind of the feedback you need to reshuffle
the series...]

> 
> The problem, which was stopping such simple division - that `datetime.lua`
> module uses `datetime_to_string()` ffi function for stringization of a
> datetime object. And this function has been introduced as part of
> messagepack/yaml/json serialization support. Yes, intra-dependendencies
> between parts looks suboptimal, and I'm very open to suggestions how to
> divide it properly.

Again, vse uje ukradeno^W pridumano do nas: consider the way this is
solved for uuid (within src/lib/uuid/tt_uuid.[ch]), since you've chosen
FFI approach for this type (we'll return to this question later).

> 
> Module API
> ==========
> 
> To simplify review of a code in patchset below you could find preliminary
> documentation of a datetime api module. Eventually we may put it
> as .rst file in documentation repository (though, personally, I'd prefer
> to have all modules api committed to the same repo as code, but I digress)

I guess, I'll leave general comments right here.

1. <asctime>, <strftime>, <ctime> can be implemented via FFI by user,
   and you simply provide a wrapped. Anyway, it's already implemented,
   so OK.
2. IMHO, you test too many exotic formats and corner cases of creating
   datetime object. I doubt somebody will use at least 10% of the
   provided functionality (especially in beta).
3. Datetime and interval arithmetics -- this is what bother me a lot.
   I believe every average Lua user can parse date into the table and
   build the string from this table back. But nobody wants to mess with
   all that hell with leap years arithmetics, leap seconds, etc.
4. Furthermore, I don't understand the following behaviour:
   | tarantool> d = require('datetime')
   | ---
   | ...
   | 
   | tarantool> ft = require('ffi').typeof
   | ---
   | - +2 days, 0 hours, 0 minutes, 0 seconds
   | ...
   | 
   | tarantool> d.days(2) + d.days(1)
   | ---
   | - 1970-01-04T00:00Z
   | ...
   | 
   | tarantool> ft(d.days(2) + d.days(1))
   | ---
   | - ctype<struct datetime_t>
   | ...
   | 
   | tarantool> d.days(2) - d.days(1)
   | ---
   | - error: 'builtin/datetime.lua:554: date/time expected'
   | ...
   | 
   I believe it's quite natural to do arithmetics with intervals to
   create a new one, isn't it?

> 
> Datetime module
> ===============
> 
> This module allows to parse date/time stamps, manipulate with them, and
> generate textual representation. Parsing of date constants will handle
> all ISO-8601 formats, and deal with some extensions.
> 
> For internal date representation it uses cdata structures of a form:

BTW, I agree w/ Oleg and see no motivation why FFI is chosen for
implementation: decimal is implemented via Lua C API, uuid -- via FFI.
Are there any proofs and benchmarks for datetime? Does it violate any
limit mentioned here[1]?

> 
> ```c++
> struct t_datetime {
> 	int secs;
> 	int nsecs;
> 	int offset;
> };
> ```
> 
> Where:
> 
> -   secs is the (signed) number of seconds since epoch 1970-01-01T00:00Z;
> -   nsecs is number of nanoseconds since beginning of day;
> -   offset is the timezone offset (in minutes);
> 
> `datetime()` ? create datetime object
> -------------------------------------
> 

Both decimal and uuid modules provides <new> function, but you decided
to name the datetime constructor <datetime>. I see no reason for such
inconsistency.

> Create date/time object using either string literals, or initialization
> object. When given string literals it behaves like wrapper around
> parse() method.
> 
> When there is initialization object it could create date/time object
> using these attributes below:
> 
> | **Easy way** |                                                                                                 |
> |--------------|-------------------------------------------------------------------------------------------------|
> | secs         | Seconds since epoch                                                                             |
> | nsec         | Nanoseconds since midnight                                                                      |
> | offset       | Time-zone offset in minutes                                                                     |
> | **YMD part** |                                                                                                 |
> | year         | Year in range \[1..9999\]                                                                       |
> | month        | Month in range \[1..12\]                                                                        |
> | day          | Day in month in range \[1..31\]                                                                 |
> | **HMS part** |                                                                                                 |
> | hour         | Hour in range \[0..23\]                                                                         |
> | minute       | Minute in range \[0..59\]                                                                       |
> | second       | Seconds in range \[0..60\]. It allows to have fraction part in which case it goes to nsec field |
> | tz           | Timezone offset (in minutes) for HMS part                                                       |
> 
> Example:
> 
> ```lua
> 
> datetime = require `datetime`
> 
> d = datetime{secs = 0, nsec = 0, offset = 0}
> d = datetime(?1970-01-01?)
> d = datetime{year = 1970, month = 1, day = 1}
> ```
> 
> `delta()` ? create time duration object
> ---------------------------------------
> 
> TBD

Why delta? Sci-Lua uses period, that is quite accurate term. SQL
provides INTERVAL type. I guess you can choose something from this.

> 
> `parse()` ? parse full ISO-8601 string
> --------------------------------------
> 
> Parse full length date/time literal, which may be in ISO-8601 format of
> any of extended formats supported by `parse_date()`, `parse_time()` or
> `parse_timezone()`
> 
> It deals with date/time string in format
> 
> `date ([Tt ] time ([ ] time_zone)? )?`
> 
> Where time or `time_zone` parts may be omitted.
> 
> Example:
> 
> ```lua
> 
> datetime = require `datetime`
> 
> d = datetime.parse(`1970-01-01`)
> d = datetime.parse(`1970-01-01T00:00:00Z`)
> d = datetime.parse(`1970-01-01T02:00:00+02:00`)
> ```
> 
> `parse_date()` ? parse ISO-8601 date literal
> --------------------------------------------
> 
> Parse date string literal, return partial date object which has
> precision of up-to date period of time.
> 
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
> 
> | 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             |
> 
> `parse_time()` ? parse ISO-8601 time literal
> --------------------------------------------
> 
> Parse time string literal, return partial date/time object, which
> defines time period inside of single date.
> 
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
> 
> | 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.
> 
> `parse_zone()` ? parse ISO-8601 time zone
> -----------------------------------------
> 
> Parse time-zone string literal, return partial date/time object, which
> defines timezone offset in minutes sing GMT.
> 
> A number of standard ISO-8601 formats supported, plus there are some
> relaxed formats which are of frequently use:
> 
> | Basic | Extended |
> |-------|----------|
> | Z     | N/A      |
> | ?hh   | N/A      |
> | ?hhmm | ?hh:mm   |

AFAIU, <parse_{date,time,zone}> are the parts of <parse> routine, right?
Then why do you provide a separate interfaces for these purposes?

> 
> `tostring()` ? convert datetime object to string
> ------------------------------------------------
> 
> Return string representation (probably compact if there are some parts
> missing) of a date-time objects passed
> 
> `now()` ? return current date/time
> ----------------------------------
> 
> `now()` returns local date and time object. It will use local time-zone
> and nanosecond precision.
> 
> `strftime()` ? convert date object to string using format
> ---------------------------------------------------------
> 
> `strftime()` is the FFI wrapper around strftime() function in LIBC. It
> supports all the same flags which supports strftime() from host OS.
> 
> See
> <https://pubs.opengroup.org/onlinepubs/000095399/functions/strftime.html>
> for more details.
> 
> `asctime()` ? convert date object to string using asctime predefined format
> ---------------------------------------------------------------------------
> 
> `asctime()` is the FFI wrapper over `asctime_r()` from a host libc. asctime
> returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`
> 
> <https://pubs.opengroup.org/onlinepubs/009695399/functions/asctime.html>
> 
> `ctime()` ? convert local time to string using ctime() predefined format
> ------------------------------------------------------------------------
> 
> `ctime()` is the FFI wrapper over `ctime_r()` in the host libc. ctime
> returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`

This is not quite right, considering your code, so here is the question:
do you mean ctime(3) or ctime_r(3).

> 
> <https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html>
> 
> The difference of `ctime()` and `asctime()` is that former is returning
> local time zone formatted, while the latter will deal with GMT.
> 
> Examples:
> 
> ```
> tarantool> date = require 'datetime'
> ---
> ...
> tarantool> T = date.now()
> ---
> ...
> tarantool> T
> ---
> - 2021-07-14T01:36:48.554105+03:00
> ...
> tarantool> date.asctime(T)
> ---
> - 'Tue Jul 13 22:36:48 2021
> '
> ...
> tarantool> date.ctime(T)
> ---
> - 'Wed Jul 14 01:36:48 2021
> '
> ...
> ```
> 
> Date attribute accessors
> ------------------------
> 
> |                |                                                                 |
> |----------------|-----------------------------------------------------------------|
> | `timestamp`    | Calculate timestamp with seconds and nanoseconds parts combined |
> | `nanoseconds`  | Number of nanoseconds in time part                              |
> | `microseconds` | Number of microseconds in time part                             |
> | `milliseconds` | Number of milliseconds in time part                             |
> | `seconds`      | Alias to timestamp                                              |
> | `minutes`      | Number of minutes in time part                                  |
> | `hours`        | Number of hours in time part                                    |
> | `days`         | Number of days in time part                                     |
> 
> ```
> tarantool> d = date{year = 1970, month = 1, day = 1, hour = 0, minute = 10, second=10}
> tarantool> d.secs
> ---
> - 610
> ...
> tarantool> d.nsec
> ---
> - 0
> ...
> tarantool> d.offset
> ---
> - 0
> ...
> tarantool> d.nanoseconds
> ---
> - 610000000000
> ...
> tarantool> d.milliseconds
> ---
> - 610000
> ...
> tarantool> d.hours
> ---
> - 0.16944444444444
> ...
> tarantool> d.minutes
> ---
> - 10.166666666667
> ...
> ```
> 
> Date arithmetic
> ---------------
> 

The most interesting part of the doc by the way.

> TBD
> 
> 
> https://github.com/tarantool/tarantool/issues/5941
> https://github.com/tarantool/tarantool/issues/5946
> https://github.com/tarantool/tarantool/tree/tsafin/gh-5941-datetime-V2
> 
> 
> 
> Timur Safin (13):
>   build: add Christian Hansen c-dt to the build
>   lua: built-in module datetime
>   test: datetime test
>   test: datetime string formatting
>   box: add messagepack support for datetime
>   lua: positive/negative cases in datetime test
>   lua: asctime and strfime fixed
>   box, lua: renamed t_datetime_tz structure to datetime_t
>   lua: calculated attributes for date
>   lua: tostring formatization in datetime.lua
>   test: allow relaxed date format without tz
>   lua: initial time duration support
>   lua: complete time duration support
> 
>  .gitmodules                       |   3 +
>  CMakeLists.txt                    |   8 +
>  cmake/BuildCDT.cmake              |   6 +
>  src/CMakeLists.txt                |   4 +
>  src/box/field_def.c               |  34 +-
>  src/box/field_def.h               |   1 +
>  src/box/msgpack.c                 |   7 +-
>  src/box/tuple_compare.cc          |  24 +
>  src/exports.h                     |  26 +
>  src/lib/core/CMakeLists.txt       |   4 +-
>  src/lib/core/datetime.h           |  96 ++++
>  src/lib/core/mp_datetime.c        | 232 ++++++++
>  src/lib/core/mp_extension_types.h |   1 +
>  src/lib/mpstream/mpstream.c       |  11 +
>  src/lib/mpstream/mpstream.h       |   4 +
>  src/lua/datetime.c                |  70 +++
>  src/lua/datetime.h                |  53 ++
>  src/lua/datetime.lua              | 868 ++++++++++++++++++++++++++++++
>  src/lua/init.c                    |   6 +-
>  src/lua/msgpack.c                 |  12 +
>  src/lua/msgpackffi.lua            |   8 +
>  src/lua/serializer.c              |   4 +
>  src/lua/serializer.h              |   2 +
>  src/lua/utils.c                   |   1 -
>  test/app-tap/datetime.test.lua    | 266 +++++++++
>  test/unit/CMakeLists.txt          |   2 +
>  test/unit/datetime.c              | 220 ++++++++
>  test/unit/datetime.result         | 358 ++++++++++++
>  third_party/c-dt                  |   1 +
>  third_party/lua-cjson/lua_cjson.c |   8 +
>  third_party/lua-yaml/lyaml.cc     |   6 +-
>  31 files changed, 2326 insertions(+), 20 deletions(-)
>  create mode 100644 cmake/BuildCDT.cmake
>  create mode 100644 src/lib/core/datetime.h
>  create mode 100644 src/lib/core/mp_datetime.c
>  create mode 100644 src/lua/datetime.c
>  create mode 100644 src/lua/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
> 
> -- 
> 2.29.2
> 

[1]: https://github.com/tarantool/tarantool/wiki/Code-review-procedure
[2]: https://www.tarantool.io/en/doc/latest/dev_guide/developer_guidelines/
[1]: http://luajit.org/ext_ffi_semantics.html#status

-- 
Best regards,
IM

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support
  2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
@ 2021-07-22 12:54   ` Timur Safin via Tarantool-patches
  0 siblings, 0 replies; 19+ messages in thread
From: Timur Safin via Tarantool-patches @ 2021-07-22 12:54 UTC (permalink / raw)
  To: 'Igor Munkin'; +Cc: tarantool-patches, v.shpilevoy, Alexander Turenko

Hello Igor,

Please see some responses (but better see the next iteration of 
patchset to be sent later)

: From: Igor Munkin <imun@tarantool.org>
: Subject: Re: [RFC PATCH 00/13] Initial datetime support
: 
: Timur,
: 
: Thanks for the series! I wonder, why you decided to reinvent several
: approaches being already used for quite similar Tarantool types: decimal
: and uuid. Frankly speaking, I expect you introduce datetime the very
: *very* similar way. I didn't make the precise review for this series
: (this is waste of time), so please consider my major comments below.
: 
: Let's start with top 3 comments to the eye catching problems I've found
: while looking at the patches.
: 
: 1. Tests have to be committed in the same scope the corresponding
:    changes in Tarantool are introduced.
: 2. Docbot request is required for the changes you're introducing: this
:    is the place where you can leave the description you've added below.
: 3. Please consider approaches used for decimal and uuid: almost every
:    Oleg's comment came from the bugs fixed for these types.
: 
: As I write above, this is not a precise review, so I don't even start
: nitpicking the patches and simply address you to this document[1] and
: our dev guide[2]. Please, check whether your next series respects these
: guidelines.
: 
: On 15.07.21, Timur Safin wrote:
: > Here is the preliminary RFC version of a patchset, which is implementing
: > datetime lua support in box, their messagepack, yaml, json serialization
: > support, and a set of c and lua tests.
: >
: > * It's heavily influenced by Sci-Lua lua-time module implementation
: >   https://github.com/stepelu/lua-time
: >   e.g. you could find very similar approach for handling of operations
: >   with year and months periods (which should be handled differently than
: >   usual seconds, days periods).
: >
: > * But internally it actually uses Christina Hanson' c-dt module
: >   https://github.com/chansen/c-dt
: >   (though it has been modified slightly for cleaner integration
: >   into cmake build process)
: 
: BTW, submodule points to the very strange repo: please use either
: vanilla one, or create a fork into Tarantool repositories.

At the moment it points to my own fork, because I do not have 
Permissions, and unable to create new repo in our organization. 
I'll ask Sasha Turenko for a help in moving this repo.

: 
: >
: > This is preliminary version of patchset, with unoptimal patches divisions
: > (like you could see patches with rename refactorings), but, due to an
: amount
: > of code in patchset, and limited time have, I send patchset as RFC today,
: > expecting to receive initial feedback for various parts of a series.
: 
: Well, I guess I can stop here for the initial feedback.
: 
: I have no idea, why you decided to introduce a separate tiny module
: src/lua/datetime.c just for initializing several constants and a
: oneliner for pushing datetime GCcdata on Lua stack. This module is
: excess. Please consider the way this is done for uuid and move
: everything to src/lua/utils.c that encloses all illegitimate tricks with
: Lua C API.

I'll look there. 

: 
: >
: > Prior plan was to split series into one, which introduces datetime module
: api,
: > and another one with datetime messagepack serialization implementation.
: > But here they left as part of a same series. Sorry, for invonvenience -
: I'll
: > reshuffle order according to feedback provided.
: 
: [I personally don't know what kind of the feedback you need to reshuffle
: the series...]
: 
: >
: > The problem, which was stopping such simple division - that `datetime.lua`
: > module uses `datetime_to_string()` ffi function for stringization of a
: > datetime object. And this function has been introduced as part of
: > messagepack/yaml/json serialization support. Yes, intra-dependendencies
: > between parts looks suboptimal, and I'm very open to suggestions how to
: > divide it properly.
: 
: Again, vse uje ukradeno^W pridumano do nas: consider the way this is
: solved for uuid (within src/lib/uuid/tt_uuid.[ch]), since you've chosen
: FFI approach for this type (we'll return to this question later).

I've seen tt_uuid library, will consider extraction of datetime stringization
Helper somewhere similarly to tt_uuid. 

: 
: >
: > Module API
: > ==========
: >
: > To simplify review of a code in patchset below you could find preliminary
: > documentation of a datetime api module. Eventually we may put it
: > as .rst file in documentation repository (though, personally, I'd prefer
: > to have all modules api committed to the same repo as code, but I digress)
: 
: I guess, I'll leave general comments right here.
: 
: 1. <asctime>, <strftime>, <ctime> can be implemented via FFI by user,
:    and you simply provide a wrapped. Anyway, it's already implemented,
:    so OK.
: 2. IMHO, you test too many exotic formats and corner cases of creating
:    datetime object. I doubt somebody will use at least 10% of the
:    provided functionality (especially in beta).

Those are types of standard ISO-8601 date/time formats with few extensions.

: 3. Datetime and interval arithmetics -- this is what bother me a lot.
:    I believe every average Lua user can parse date into the table and
:    build the string from this table back. But nobody wants to mess with
:    all that hell with leap years arithmetics, leap seconds, etc.

Agreed that convenient interval arithmetic is the most important part
of datetime module. 

: 4. Furthermore, I don't understand the following behaviour:
:    | tarantool> d = require('datetime')
:    | ---
:    | ...
:    |
:    | tarantool> ft = require('ffi').typeof
:    | ---
:    | - +2 days, 0 hours, 0 minutes, 0 seconds
:    | ...
:    |
:    | tarantool> d.days(2) + d.days(1)
:    | ---
:    | - 1970-01-04T00:00Z
:    | ...
:    |
:    | tarantool> ft(d.days(2) + d.days(1))
:    | ---
:    | - ctype<struct datetime_t>
:    | ...
:    |
:    | tarantool> d.days(2) - d.days(1)
:    | ---
:    | - error: 'builtin/datetime.lua:554: date/time expected'
:    | ...
:    |
:    I believe it's quite natural to do arithmetics with intervals to
:    create a new one, isn't it?

At the moment there was limitation that at least 1 of 2 arguments
passed (either left or right) have to be full datetime object, and
only another one could be interval. (Though in __add we could
summarize 2 intervals, as you've shown above, but that was oversight
in original patchset, which later has been "fixed")

Now, while you are mentioning it, I tend to agree that it's ok to
summarize or subtract 2 intervals, and I'll relax this restriction. 

: 
: >
: > Datetime module
: > ===============
: >
: > This module allows to parse date/time stamps, manipulate with them, and
: > generate textual representation. Parsing of date constants will handle
: > all ISO-8601 formats, and deal with some extensions.
: >
: > For internal date representation it uses cdata structures of a form:
: 
: BTW, I agree w/ Oleg and see no motivation why FFI is chosen for
: implementation: decimal is implemented via Lua C API, uuid -- via FFI.
: Are there any proofs and benchmarks for datetime? Does it violate any
: limit mentioned here[1]?

Using ffi to call c-dt was an obvious, easiest way to call c-dt. And taking
into account that uuid is still using FFI I conclude there is no strict
policy here and ffi is acceptable. 

Did not understand "Does it violate any limit mentioned here[1]?" what are
you talking about here? Could you please elaborate?

: 
: >
: > ```c++
: > struct t_datetime {
: > 	int secs;
: > 	int nsecs;
: > 	int offset;
: > };
: > ```
: >
: > Where:
: >
: > -   secs is the (signed) number of seconds since epoch 1970-01-01T00:00Z;
: > -   nsecs is number of nanoseconds since beginning of day;
: > -   offset is the timezone offset (in minutes);
: >
: > `datetime()` ? create datetime object
: > -------------------------------------
: >
: 
: Both decimal and uuid modules provides <new> function, but you decided
: to name the datetime constructor <datetime>. I see no reason for such
: inconsistency.

Will add `new` as entry point.

But, WFIW, here we use the same approach as in uuid.lua where uuid() is
shortcut for uuid.new()

: 
: > Create date/time object using either string literals, or initialization
: > object. When given string literals it behaves like wrapper around
: > parse() method.
: >
: > When there is initialization object it could create date/time object
: > using these attributes below:
: >
: > | **Easy way** |
: |
: > |--------------|----------------------------------------------------------
: ---------------------------------------|
: > | secs         | Seconds since epoch
: |
: > | nsec         | Nanoseconds since midnight
: |
: > | offset       | Time-zone offset in minutes
: |
: > | **YMD part** |
: |
: > | year         | Year in range \[1..9999\]
: |
: > | month        | Month in range \[1..12\]
: |
: > | day          | Day in month in range \[1..31\]
: |
: > | **HMS part** |
: |
: > | hour         | Hour in range \[0..23\]
: |
: > | minute       | Minute in range \[0..59\]
: |
: > | second       | Seconds in range \[0..60\]. It allows to have fraction
: part in which case it goes to nsec field |
: > | tz           | Timezone offset (in minutes) for HMS part
: |
: >
: > Example:
: >
: > ```lua
: >
: > datetime = require `datetime`
: >
: > d = datetime{secs = 0, nsec = 0, offset = 0}
: > d = datetime(?1970-01-01?)
: > d = datetime{year = 1970, month = 1, day = 1}
: > ```
: >
: > `delta()` ? create time duration object
: > ---------------------------------------
: >
: > TBD
: 
: Why delta? Sci-Lua uses period, that is quite accurate term. SQL
: provides INTERVAL type. I guess you can choose something from this.

Yup, interval is much better name, will use it instead.

: 
: >
: > `parse()` ? parse full ISO-8601 string
: > --------------------------------------
: >
: > Parse full length date/time literal, which may be in ISO-8601 format of
: > any of extended formats supported by `parse_date()`, `parse_time()` or
: > `parse_timezone()`
: >
: > It deals with date/time string in format
: >
: > `date ([Tt ] time ([ ] time_zone)? )?`
: >
: > Where time or `time_zone` parts may be omitted.
: >
: > Example:
: >
: > ```lua
: >
: > datetime = require `datetime`
: >
: > d = datetime.parse(`1970-01-01`)
: > d = datetime.parse(`1970-01-01T00:00:00Z`)
: > d = datetime.parse(`1970-01-01T02:00:00+02:00`)
: > ```
: >
: > `parse_date()` ? parse ISO-8601 date literal
: > --------------------------------------------
: >
: > Parse date string literal, return partial date object which has
: > precision of up-to date period of time.
: >
: > A number of standard ISO-8601 formats supported, plus there are some
: > relaxed formats which are of frequently use:
: >
: > | 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             |
: >
: > `parse_time()` ? parse ISO-8601 time literal
: > --------------------------------------------
: >
: > Parse time string literal, return partial date/time object, which
: > defines time period inside of single date.
: >
: > A number of standard ISO-8601 formats supported, plus there are some
: > relaxed formats which are of frequently use:
: >
: > | 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.
: >
: > `parse_zone()` ? parse ISO-8601 time zone
: > -----------------------------------------
: >
: > Parse time-zone string literal, return partial date/time object, which
: > defines timezone offset in minutes sing GMT.
: >
: > A number of standard ISO-8601 formats supported, plus there are some
: > relaxed formats which are of frequently use:
: >
: > | Basic | Extended |
: > |-------|----------|
: > | Z     | N/A      |
: > | ?hh   | N/A      |
: > | ?hhmm | ?hh:mm   |
: 
: AFAIU, <parse_{date,time,zone}> are the parts of <parse> routine, right?
: Then why do you provide a separate interfaces for these purposes?

Because there are some cases when you need to parse parts of full
date-time literal, like parsing only time part. I kept them public,
similarly to how it was done in the original c-dt. I see not much
point to hide them if we already have such building blocks for external
consumption.

: 
: >
: > `tostring()` ? convert datetime object to string
: > ------------------------------------------------
: >
: > Return string representation (probably compact if there are some parts
: > missing) of a date-time objects passed
: >
: > `now()` ? return current date/time
: > ----------------------------------
: >
: > `now()` returns local date and time object. It will use local time-zone
: > and nanosecond precision.
: >
: > `strftime()` ? convert date object to string using format
: > ---------------------------------------------------------
: >
: > `strftime()` is the FFI wrapper around strftime() function in LIBC. It
: > supports all the same flags which supports strftime() from host OS.
: >
: > See
: > <https://pubs.opengroup.org/onlinepubs/000095399/functions/strftime.html>
: > for more details.
: >
: > `asctime()` ? convert date object to string using asctime predefined
: format
: > --------------------------------------------------------------------------
: -
: >
: > `asctime()` is the FFI wrapper over `asctime_r()` from a host libc.
: asctime
: > returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`
: >
: > <https://pubs.opengroup.org/onlinepubs/009695399/functions/asctime.html>
: >
: > `ctime()` ? convert local time to string using ctime() predefined format
: > ------------------------------------------------------------------------
: >
: > `ctime()` is the FFI wrapper over `ctime_r()` in the host libc. ctime
: > returns string in the form `"Sun Sep 16 01:03:52 1973\\n\\0"`
: 
: This is not quite right, considering your code, so here is the question:
: do you mean ctime(3) or ctime_r(3).

ctime_r is _reentrant_ version of ctime, which behave identically. I'll
use more careful wording then, to not confuse reader with 2 separate names.

: 
: >
: > <https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html>
: >
: > The difference of `ctime()` and `asctime()` is that former is returning
: > local time zone formatted, while the latter will deal with GMT.
: >
: > Examples:
: >
: > ```
: > tarantool> date = require 'datetime'
: > ---
: > ...
: > tarantool> T = date.now()
: > ---
: > ...
: > tarantool> T
: > ---
: > - 2021-07-14T01:36:48.554105+03:00
: > ...
: > tarantool> date.asctime(T)
: > ---
: > - 'Tue Jul 13 22:36:48 2021
: > '
: > ...
: > tarantool> date.ctime(T)
: > ---
: > - 'Wed Jul 14 01:36:48 2021
: > '
: > ...
: > ```
: >
: > Date attribute accessors
: > ------------------------
: >
: > |                |
: |
: > |----------------|--------------------------------------------------------
: ---------|
: > | `timestamp`    | Calculate timestamp with seconds and nanoseconds parts
: combined |
: > | `nanoseconds`  | Number of nanoseconds in time part
: |
: > | `microseconds` | Number of microseconds in time part
: |
: > | `milliseconds` | Number of milliseconds in time part
: |
: > | `seconds`      | Alias to timestamp
: |
: > | `minutes`      | Number of minutes in time part
: |
: > | `hours`        | Number of hours in time part
: |
: > | `days`         | Number of days in time part
: |
: >
: > ```
: > tarantool> d = date{year = 1970, month = 1, day = 1, hour = 0, minute =
: 10, second=10}
: > tarantool> d.secs
: > ---
: > - 610
: > ...
: > tarantool> d.nsec
: > ---
: > - 0
: > ...
: > tarantool> d.offset
: > ---
: > - 0
: > ...
: > tarantool> d.nanoseconds
: > ---
: > - 610000000000
: > ...
: > tarantool> d.milliseconds
: > ---
: > - 610000
: > ...
: > tarantool> d.hours
: > ---
: > - 0.16944444444444
: > ...
: > tarantool> d.minutes
: > ---
: > - 10.166666666667
: > ...
: > ```
: >
: > Date arithmetic
: > ---------------
: >
: 
: The most interesting part of the doc by the way.

Yeah, agreed. See it soon!


: 
: > TBD
: >
: >
: > https://github.com/tarantool/tarantool/issues/5941
: > https://github.com/tarantool/tarantool/issues/5946
: > https://github.com/tarantool/tarantool/tree/tsafin/gh-5941-datetime-V2
: >
: >
: >
...
: 
: [1]: https://github.com/tarantool/tarantool/wiki/Code-review-procedure
: [2]: https://www.tarantool.io/en/doc/latest/dev_guide/developer_guidelines/
: [1]: http://luajit.org/ext_ffi_semantics.html#status
: 
: --
: Best regards,
: IM

Regards,
Timur


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2021-07-22 12:54 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-15  8:18 [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 01/13] build: add Christian Hansen c-dt to the build Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 02/13] lua: built-in module datetime Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 03/13] test: datetime test Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 04/13] test: datetime string formatting Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 05/13] box: add messagepack support for datetime Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 06/13] lua: positive/negative cases in datetime test Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 07/13] lua: asctime and strfime fixed Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 08/13] box, lua: renamed t_datetime_tz structure to datetime_t Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 09/13] lua: calculated attributes for date Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 10/13] lua: tostring formatization in datetime.lua Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 11/13] test: allow relaxed date format without tz Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 12/13] lua: initial time duration support Timur Safin via Tarantool-patches
2021-07-15  8:18 ` [Tarantool-patches] [RFC PATCH 13/13] lua: complete " Timur Safin via Tarantool-patches
2021-07-15 16:56 ` [Tarantool-patches] [RFC PATCH 00/13] Initial datetime support Oleg Babin via Tarantool-patches
2021-07-15 23:03   ` Timur Safin via Tarantool-patches
2021-07-16  6:58     ` Oleg Babin via Tarantool-patches
2021-07-22 10:01 ` Igor Munkin via Tarantool-patches
2021-07-22 12:54   ` Timur Safin via Tarantool-patches

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox