From 36bc6f8326de9d90ce3c91e7e02fccc9dfb4093d Mon Sep 17 00:00:00 2001
From: Timur Safin <tsafin@tarantool.org>
Date: Tue, 24 May 2022 18:49:13 +0300
Subject: [PATCH] datetime: do not mess with nsec in interval

Do not even try to make more readable output of secs/nsec,
but rather report them as is, without any [de]normalization.

Not the prior way:
```
tarantool> dt.interval.new{min=1, sec=59, nsec=2e9+1}
--
- +1 minutes, 61.000000001 seconds
...
```

But instead as:
```
tarantool> dt.interval.new{min=1, sec=59, nsec=2e9+1}
--
- +1 minutes, 59 seconds, 2000000001 nanoseconds
...
```

Closes #7045

NO_DOC=internal
---
 .../gh-7045-interval-nanoseconds.md           | 17 +++++
 src/lib/core/datetime.c                       | 74 +++----------------
 test/app-tap/datetime.test.lua                | 11 ++-
 test/sql-luatest/interval_test.lua            |  2 +-
 4 files changed, 37 insertions(+), 67 deletions(-)
 create mode 100644 changelogs/unreleased/gh-7045-interval-nanoseconds.md

diff --git a/changelogs/unreleased/gh-7045-interval-nanoseconds.md b/changelogs/unreleased/gh-7045-interval-nanoseconds.md
new file mode 100644
index 0000000000..9fdb28e3e4
--- /dev/null
+++ b/changelogs/unreleased/gh-7045-interval-nanoseconds.md
@@ -0,0 +1,17 @@
+## feature/datetime
+
+* Changed format of a string representation of date/time intervals.
+  There is no seconds/nano-seconds normalization done anymore, and
+  interval to be displayed as-is.
+
+  Instead of former:
+```lua
+    local ival = date.interval.new{year = 12345, hour = 48, min = 3, sec = 1,
+                                   nsec = 12345678}
+    print(tostring(ival)) -- '+12345 years, 48 hours, 3 minutes, '..
+                          -- '1.012345678 seconds'
+```
+we now output
+```
+   --  '+12345 years, 48 hours, 3 minutes, 1 seconds, 12345678 nanoseconds'
+```
diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c
index a9d9bd84aa..387db53f0e 100644
--- a/src/lib/core/datetime.c
+++ b/src/lib/core/datetime.c
@@ -558,51 +558,6 @@ datetime_totable(const struct datetime *date, struct interval *out)
 
 #define NANOS_PER_SEC 1000000000LL
 
-static inline bool
-zero_or_same_sign(int64_t sec, int64_t nsec)
-{
-	return sec == 0 || (sec < 0) == (nsec < 0);
-}
-
-/**
- * If |nsec| is larger than allowed range 1e9 then modify passed
- * sec accordingly.
- */
-static void
-denormalize_interval_nsec(int64_t *psec, int *pnsec)
-{
-	assert(psec != NULL);
-	assert(pnsec != NULL);
-	int64_t sec = *psec;
-	int nsec = *pnsec;
-	/*
-	 * There is nothing to change:
-	 * - if there is a small nsec with 0 in sec;
-	 * - or if both sec and nsec have the same sign, and nsec is
-	 *   small enough.
-	 */
-	if (nsec == 0)
-		return;
-
-	if (zero_or_same_sign(sec, nsec) && abs(nsec) < NANOS_PER_SEC)
-		return;
-
-	sec += DIV(nsec, NANOS_PER_SEC);
-	nsec = MOD(nsec, NANOS_PER_SEC);
-
-	/*
-	 * We crafted a positive nsec value, so convert it to negative, if secs
-	 * are also negative.
-	 */
-	if (sec < 0) {
-		sec++;
-		nsec -= NANOS_PER_SEC;
-	}
-
-	*psec = sec;
-	*pnsec = nsec;
-}
-
 #define SPACE() \
 	do { \
 		if (sz > 0) { \
@@ -621,7 +576,6 @@ interval_to_string(const struct interval *ival, char *buf, ssize_t len)
 		"%d",	/* false */
 		"%+d",	/* true */
 	};
-	static const char zero_secs[] = "0 seconds";
 
 	bool need_sign = true;
 	size_t sz = 0;
@@ -667,25 +621,21 @@ interval_to_string(const struct interval *ival, char *buf, ssize_t len)
 		SNPRINT(sz, snprintf, buf, len, " minutes");
 		need_sign = false;
 	}
+
 	int64_t secs = (int64_t)ival->sec;
-	int nsec = ival->nsec;
-	if (secs == 0 && nsec == 0) {
-		if (sz != 0)
-			return sz;
-		SNPRINT(sz, snprintf, buf, len, zero_secs);
-		return sizeof(zero_secs) - 1;
+	if (secs != 0 || sz == 0) {
+		SPACE();
+		SNPRINT(sz, snprintf, buf, len, long_signed_fmt[need_sign],
+			secs);
+		SNPRINT(sz, snprintf, buf, len, " seconds");
+		need_sign = false;
 	}
-	SPACE();
-	denormalize_interval_nsec(&secs, &nsec);
-	bool is_neg = secs < 0 || (secs == 0 && nsec < 0);
-
-	SNPRINT(sz, snprintf, buf, len, is_neg ? "-" : (need_sign ? "+" : ""));
-	secs = labs(secs);
+	int32_t nsec = ival->nsec;
 	if (nsec != 0) {
-		SNPRINT(sz, snprintf, buf, len, "%" PRId64 ".%09d seconds",
-			secs, abs(nsec));
-	} else {
-		SNPRINT(sz, snprintf, buf, len, "%" PRId64 " seconds", secs);
+		SPACE();
+		SNPRINT(sz, snprintf, buf, len, signed_fmt[need_sign],
+			nsec);
+		SNPRINT(sz, snprintf, buf, len, " nanoseconds");
 	}
 	return sz;
 }
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 0fa01b4b8f..8f0edd53a0 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -964,8 +964,9 @@ test:test("Time interval __index fields", function(test)
     local ival = date.interval.new{year = 12345, month = 123, week = 100,
                                    day = 45, hour = 48, min = 3, sec = 1,
                                    nsec = 12345678}
-    test:is(tostring(ival), '+12345 years, 123 months, 100 weeks, 45 days, 48 hours, '..
-            '3 minutes, 1.012345678 seconds', '__tostring')
+    test:is(tostring(ival), '+12345 years, 123 months, 100 weeks, 45 days, '..
+            '48 hours, 3 minutes, 1 seconds, 12345678 nanoseconds',
+            '__tostring')
 
     test:is(ival.nsec, 12345678, 'nsec')
     test:is(ival.usec, 12345, 'usec')
@@ -1115,10 +1116,12 @@ test:test("Time interval operations", function(test)
     local i1, i2
     i1 = ival_new{nsec = 1e9 - 1}
     i2 = ival_new{nsec = 2}
-    test:is(tostring(i1 + i2), '+1.000000001 seconds', 'nsec: 999999999 + 2')
+    test:is(tostring(i1 + i2), '+0 seconds, 1000000001 nanoseconds',
+            'nsec: 999999999 + 2')
     i1 = ival_new{nsec = -1e9 + 1}
     i2 = ival_new{nsec = -2}
-    test:is(tostring(i1 + i2), '-1.000000001 seconds', 'nsec: -999999999 - 2')
+    test:is(tostring(i1 + i2), '+0 seconds, -1000000001 nanoseconds',
+            'nsec: -999999999 - 2')
 
     local specific_errors = {
         {only_one_of, { nsec = 123456, usec = 123}},
diff --git a/test/sql-luatest/interval_test.lua b/test/sql-luatest/interval_test.lua
index 456346cf81..cb316b51cc 100644
--- a/test/sql-luatest/interval_test.lua
+++ b/test/sql-luatest/interval_test.lua
@@ -667,7 +667,7 @@ g.test_interval_17_3 = function()
         local t = require('luatest')
         local sql = [[SELECT CAST(itv AS STRING) FROM t0;]]
         local res = {{'+1 years, 2 months, 3 days, 4 hours'},
-                     {'+5 minutes, 6.000000007 seconds'}}
+                     {'+5 minutes, 6 seconds, 7 nanoseconds'}}
         local rows = box.execute(sql).rows
         t.assert_equals(rows, res)
     end)
-- 
GitLab