[Tarantool-patches] [PATCH v5 3/8] lua, datetime: display datetime

Timur Safin tsafin at tarantool.org
Mon Aug 16 02:59:37 MSK 2021


* introduced output routine for converting datetime
  to their default output format.

* use this routine for tostring() in datetime.lua

Part of #5941
---
 extra/exports                  |   1 +
 src/lib/core/datetime.c        |  71 ++++++++++++++++++
 src/lib/core/datetime.h        |   9 +++
 src/lua/datetime.lua           |  35 +++++++++
 test/app-tap/datetime.test.lua | 131 ++++++++++++++++++---------------
 test/unit/CMakeLists.txt       |   2 +-
 test/unit/datetime.c           |  61 +++++++++++----
 7 files changed, 236 insertions(+), 74 deletions(-)

diff --git a/extra/exports b/extra/exports
index 80eb92abd..2437e175c 100644
--- a/extra/exports
+++ b/extra/exports
@@ -152,6 +152,7 @@ datetime_asctime
 datetime_ctime
 datetime_now
 datetime_strftime
+datetime_to_string
 decimal_unpack
 decimal_from_string
 decimal_unpack
diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c
index c48295a6f..c24a0df82 100644
--- a/src/lib/core/datetime.c
+++ b/src/lib/core/datetime.c
@@ -29,6 +29,8 @@
  * SUCH DAMAGE.
  */
 
+#include <assert.h>
+#include <limits.h>
 #include <string.h>
 #include <time.h>
 
@@ -94,3 +96,72 @@ datetime_strftime(const struct datetime *date, const char *fmt, char *buf,
 	struct tm *p_tm = datetime_to_tm(date);
 	return strftime(buf, len, fmt, p_tm);
 }
+
+#define SECS_EPOCH_1970_OFFSET ((int64_t)DT_EPOCH_1970_OFFSET * SECS_PER_DAY)
+
+/* NB! buf may be NULL, and we should handle it gracefully, returning
+ * calculated length of output string
+ */
+int
+datetime_to_string(const struct datetime *date, char *buf, uint32_t len)
+{
+#define ADVANCE(sz)		\
+	if (buf != NULL) { 	\
+		buf += sz; 	\
+		len -= sz; 	\
+	}			\
+	ret += sz;
+
+	int offset = date->offset;
+	/* for negative offsets around Epoch date we could get
+	 * negative secs value, which should be attributed to
+	 * 1969-12-31, not 1970-01-01, thus we first shift
+	 * epoch to Rata Die then divide by seconds per day,
+	 * not in reverse
+	 */
+	int64_t secs = (int64_t)date->secs + offset * 60 + SECS_EPOCH_1970_OFFSET;
+	assert((secs / SECS_PER_DAY) <= INT_MAX);
+	dt_t dt = dt_from_rdn(secs / SECS_PER_DAY);
+
+	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;
+
+	int ret = 0;
+	uint32_t sz = snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d",
+			       year, month, day, hour, minute);
+	ADVANCE(sz);
+	if (sec || ns) {
+		sz = snprintf(buf, len, ":%02d", sec);
+		ADVANCE(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);
+			ADVANCE(sz);
+		}
+	}
+	if (offset == 0) {
+		sz = snprintf(buf, len, "Z");
+		ADVANCE(sz);
+	}
+	else {
+		if (offset < 0)
+			sign = '-', offset = -offset;
+		else
+			sign = '+';
+
+		sz = snprintf(buf, len, "%c%02d:%02d", sign, offset / 60, offset % 60);
+		ADVANCE(sz);
+	}
+	return ret;
+}
+#undef ADVANCE
+
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
index 1a8d7e34f..964e76fcc 100644
--- a/src/lib/core/datetime.h
+++ b/src/lib/core/datetime.h
@@ -70,6 +70,15 @@ struct datetime_interval {
 	int32_t nsec;
 };
 
+/**
+ * Convert datetime to string using default format
+ * @param date source datetime value
+ * @param buf output character buffer
+ * @param len size ofoutput buffer
+ */
+int
+datetime_to_string(const struct datetime *date, char *buf, uint32_t len);
+
 /**
  * Convert datetime to string using default asctime format
  * "Sun Sep 16 01:03:52 1973\n\0"
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index ce579828f..4d946f194 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -249,6 +249,37 @@ local function datetime_new(obj)
     return datetime_new_dt(dt, secs, frac, offset)
 end
 
+local function datetime_tostring(o)
+    if ffi.typeof(o) == datetime_t then
+        local sz = 48
+        local buff = ffi.new('char[?]', sz)
+        local len = builtin.datetime_to_string(o, buff, sz)
+        assert(len < sz)
+        return ffi.string(buff)
+    elseif ffi.typeof(o) == interval_t then
+        local ts = o.timestamp
+        local sign = '+'
+
+        if ts < 0 then
+            ts = -ts
+            sign = '-'
+        end
+
+        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
+
+
 --[[
     Basic      Extended
     20121224   2012-12-24   Calendar date   (ISO 8601)
@@ -457,6 +488,7 @@ local function strftime(fmt, o)
 end
 
 local datetime_mt = {
+    __tostring = datetime_tostring,
     __serialize = datetime_serialize,
     __eq = datetime_eq,
     __lt = datetime_lt,
@@ -466,6 +498,7 @@ local datetime_mt = {
 }
 
 local interval_mt = {
+    __tostring = datetime_tostring,
     __serialize = interval_serialize,
     __eq = datetime_eq,
     __lt = datetime_lt,
@@ -487,6 +520,8 @@ return setmetatable(
         parse_time  = parse_time,
         parse_zone  = parse_zone,
 
+        tostring    = datetime_tostring,
+
         now         = local_now,
         strftime    = strftime,
         asctime     = asctime,
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 464d4bd49..244ec2575 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -6,7 +6,7 @@ local date = require('datetime')
 local ffi = require('ffi')
 
 
-test:plan(6)
+test:plan(7)
 
 test:test("Simple tests for parser", function(test)
     test:plan(2)
@@ -17,74 +17,78 @@ test:test("Simple tests for parser", function(test)
 end)
 
 test:test("Multiple tests for parser (with nanoseconds)", function(test)
-    test:plan(168)
+    test:plan(193)
     -- 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 },
+        {'1970-01-01T00:00Z',                  0,         0,    0, 1},
+        {'1970-01-01T02:00+02:00',             0,         0,  120, 1},
+        {'1970-01-01T01:30+01:30',             0,         0,   90, 1},
+        {'1970-01-01T01:00+01:00',             0,         0,   60, 1},
+        {'1970-01-01T00:01+00:01',             0,         0,    1, 1},
+        {'1970-01-01T00:00Z',                  0,         0,    0, 1},
+        {'1969-12-31T23:59-00:01',             0,         0,   -1, 1},
+        {'1969-12-31T23:00-01:00',             0,         0,  -60, 1},
+        {'1969-12-31T22:30-01:30',             0,         0,  -90, 1},
+        {'1969-12-31T22:00-02:00',             0,         0, -120, 1},
+        {'1970-01-01T00:00:00.123456789Z',     0, 123456789,    0, 1},
+        {'1970-01-01T00:00:00.12345678Z',      0, 123456780,    0, 0},
+        {'1970-01-01T00:00:00.1234567Z',       0, 123456700,    0, 0},
+        {'1970-01-01T00:00:00.123456Z',        0, 123456000,    0, 1},
+        {'1970-01-01T00:00:00.12345Z',         0, 123450000,    0, 0},
+        {'1970-01-01T00:00:00.1234Z',          0, 123400000,    0, 0},
+        {'1970-01-01T00:00:00.123Z',           0, 123000000,    0, 1},
+        {'1970-01-01T00:00:00.12Z',            0, 120000000,    0, 0},
+        {'1970-01-01T00:00:00.1Z',             0, 100000000,    0, 0},
+        {'1970-01-01T00:00:00.01Z',            0,  10000000,    0, 0},
+        {'1970-01-01T00:00:00.001Z',           0,   1000000,    0, 1},
+        {'1970-01-01T00:00:00.0001Z',          0,    100000,    0, 0},
+        {'1970-01-01T00:00:00.00001Z',         0,     10000,    0, 0},
+        {'1970-01-01T00:00:00.000001Z',        0,      1000,    0, 1},
+        {'1970-01-01T00:00:00.0000001Z',       0,       100,    0, 0},
+        {'1970-01-01T00:00:00.00000001Z',      0,        10,    0, 0},
+        {'1970-01-01T00:00:00.000000001Z',     0,         1,    0, 1},
+        {'1970-01-01T00:00:00.000000009Z',     0,         9,    0, 1},
+        {'1970-01-01T00:00:00.00000009Z',      0,        90,    0, 0},
+        {'1970-01-01T00:00:00.0000009Z',       0,       900,    0, 0},
+        {'1970-01-01T00:00:00.000009Z',        0,      9000,    0, 1},
+        {'1970-01-01T00:00:00.00009Z',         0,     90000,    0, 0},
+        {'1970-01-01T00:00:00.0009Z',          0,    900000,    0, 0},
+        {'1970-01-01T00:00:00.009Z',           0,   9000000,    0, 1},
+        {'1970-01-01T00:00:00.09Z',            0,  90000000,    0, 0},
+        {'1970-01-01T00:00:00.9Z',             0, 900000000,    0, 0},
+        {'1970-01-01T00:00:00.99Z',            0, 990000000,    0, 0},
+        {'1970-01-01T00:00:00.999Z',           0, 999000000,    0, 1},
+        {'1970-01-01T00:00:00.9999Z',          0, 999900000,    0, 0},
+        {'1970-01-01T00:00:00.99999Z',         0, 999990000,    0, 0},
+        {'1970-01-01T00:00:00.999999Z',        0, 999999000,    0, 1},
+        {'1970-01-01T00:00:00.9999999Z',       0, 999999900,    0, 0},
+        {'1970-01-01T00:00:00.99999999Z',      0, 999999990,    0, 0},
+        {'1970-01-01T00:00:00.999999999Z',     0, 999999999,    0, 1},
+        {'1970-01-01T00:00:00.0Z',             0,         0,    0, 0},
+        {'1970-01-01T00:00:00.00Z',            0,         0,    0, 0},
+        {'1970-01-01T00:00:00.000Z',           0,         0,    0, 0},
+        {'1970-01-01T00:00:00.0000Z',          0,         0,    0, 0},
+        {'1970-01-01T00:00:00.00000Z',         0,         0,    0, 0},
+        {'1970-01-01T00:00:00.000000Z',        0,         0,    0, 0},
+        {'1970-01-01T00:00:00.0000000Z',       0,         0,    0, 0},
+        {'1970-01-01T00:00:00.00000000Z',      0,         0,    0, 0},
+        {'1970-01-01T00:00:00.000000000Z',     0,         0,    0, 0},
+        {'1973-11-29T21:33:09Z',       123456789,         0,    0, 1},
+        {'2013-10-28T17:51:56Z',      1382982716,         0,    0, 1},
+        {'9999-12-31T23:59:59Z',    253402300799,         0,    0, 1},
     }
     for _, value in ipairs(tests) do
-        local str, epoch, nsec, offset
-        str, epoch, nsec, offset = unpack(value)
+        local str, epoch, nsec, offset, check
+        str, epoch, nsec, offset, check = 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))
+        if check > 0 then
+            test:ok(str == tostring(dt), ('%s == tostring(%s)'):
+                    format(str, tostring(dt)))
+        end
     end
 end)
 
@@ -203,4 +207,11 @@ test:test("Parse tiny date into seconds and other parts", function(test)
     test:ok(tiny.hours == 0.00848, "hours")
 end)
 
+test:test("Stringization of date", function(test)
+    test:plan(1)
+    local str = '19700101Z'
+    local dt = date(str)
+    test:ok(tostring(dt) == '1970-01-01T00:00Z', ('tostring(%s)'):format(str))
+end)
+
 os.exit(test:check() and 0 or 1)
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 31b183a8f..8194a133f 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -57,7 +57,7 @@ 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)
+target_link_libraries(datetime.test cdt core unit)
 add_executable(bps_tree.test bps_tree.cc)
 target_link_libraries(bps_tree.test small misc)
 add_executable(bps_tree_iterator.test bps_tree_iterator.cc)
diff --git a/test/unit/datetime.c b/test/unit/datetime.c
index 64c19dac4..1ae76003b 100644
--- a/test/unit/datetime.c
+++ b/test/unit/datetime.c
@@ -5,6 +5,7 @@
 #include <time.h>
 
 #include "unit.h"
+#include "datetime.h"
 
 static const char sample[] = "2012-12-24T15:30Z";
 
@@ -136,18 +137,6 @@ exit:
 	return 0;
 }
 
-/* avoid introducing external datetime.h dependency -
-   just copy paste it for today
-*/
-#define SECS_PER_DAY      86400
-#define DT_EPOCH_1970_OFFSET 719163
-
-struct datetime {
-	double secs;
-	int32_t nsec;
-	int32_t offset;
-};
-
 static int
 local_rd(const struct datetime *dt)
 {
@@ -211,13 +200,59 @@ static void datetime_test(void)
 		is(secs, parsed_secs,
 		   "reversible seconds via strftime for '%s", buff);
 	}
+	check_plan();
+}
+
+
+static void
+tostring_datetime_test(void)
+{
+	static struct {
+		const char *string;
+		int64_t     secs;
+		uint32_t    nsec;
+		uint32_t    offset;
+	} tests[] = {
+		{"1970-01-01T02:00+02:00",          0,         0,  120},
+		{"1970-01-01T01:30+01:30",          0,         0,   90},
+		{"1970-01-01T01:00+01:00",          0,         0,   60},
+		{"1970-01-01T00:01+00:01",          0,         0,    1},
+		{"1970-01-01T00:00Z",               0,         0,    0},
+		{"1969-12-31T23:59-00:01",          0,         0,   -1},
+		{"1969-12-31T23:00-01:00",          0,         0,  -60},
+		{"1969-12-31T22:30-01:30",          0,         0,  -90},
+		{"1969-12-31T22:00-02:00",          0,         0, -120},
+		{"1970-01-01T00:00:00.123456789Z",  0, 123456789,    0},
+		{"1970-01-01T00:00:00.123456Z",     0, 123456000,    0},
+		{"1970-01-01T00:00:00.123Z",        0, 123000000,    0},
+		{"1973-11-29T21:33:09Z",    123456789,         0,    0},
+		{"2013-10-28T17:51:56Z",   1382982716,         0,    0},
+		{"9999-12-31T23:59:59Z", 253402300799,         0,    0},
+	};
+	size_t index;
+
+	plan(15);
+	for (index = 0; index < DIM(tests); index++) {
+		struct datetime date = {
+			tests[index].secs,
+			tests[index].nsec,
+			tests[index].offset
+		};
+		char buf[48];
+		datetime_to_string(&date, buf, sizeof buf);
+		is(strcmp(buf, tests[index].string), 0,
+		   "string '%s' expected, received '%s'",
+		   tests[index].string, buf);
+	}
+	check_plan();
 }
 
 int
 main(void)
 {
-	plan(1);
+	plan(2);
 	datetime_test();
+	tostring_datetime_test();
 
 	return check_plan();
 }
-- 
2.29.2



More information about the Tarantool-patches mailing list