From 8e7514b9d6f0eaafee82db8efecb41f4a7d6e843 Mon Sep 17 00:00:00 2001 From: Oleg Babin <babinoleg@mail.ru> Date: Sat, 22 Apr 2023 08:11:27 +0300 Subject: [PATCH] datetime: fix invalid representation of timestamps with fraction part Sometimes we need negative timestamps to work with dates before 1970. But seems such cases were even covered in tests. So there wasn't any handling of negative timestamps with fraction part. Such datetime objects had incorrect string representation (e.g. "1963-11-22T12:30:02.-999"). This patch fixes it. Closes #8570 NO_DOC=bugfix --- .../gh-8570-fix-datetime-str-negative-nsec.md | 4 ++++ src/lua/datetime.lua | 14 ++++++++++++++ test/app-tap/datetime.test.lua | 16 +++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/gh-8570-fix-datetime-str-negative-nsec.md diff --git a/changelogs/unreleased/gh-8570-fix-datetime-str-negative-nsec.md b/changelogs/unreleased/gh-8570-fix-datetime-str-negative-nsec.md new file mode 100644 index 0000000000..d75101f383 --- /dev/null +++ b/changelogs/unreleased/gh-8570-fix-datetime-str-negative-nsec.md @@ -0,0 +1,4 @@ +## bugfix/datetime + +* Fixed errors when the string representation of a datetime object had +a negative nanosecond part (gh-8570). diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua index 7bb84badbb..60949a8e6e 100644 --- a/src/lua/datetime.lua +++ b/src/lua/datetime.lua @@ -588,6 +588,13 @@ local function datetime_new(obj) end local fraction s, fraction = math_modf(ts) + -- In case of negative fraction part we should + -- make it positive at the expense of the integer part. + -- Code below expects that "nsec" value is always positive. + if fraction < 0 then + s = s - 1 + fraction = fraction + 1 + end -- if there are separate nsec, usec, or msec provided then -- timestamp should be integer if count_usec == 0 then @@ -1085,6 +1092,13 @@ local function datetime_set(self, obj) end local sec_int, fraction sec_int, fraction = math_modf(ts) + -- In case of negative fraction part we should + -- make it positive at the expense of the integer part. + -- Code below expects that "nsec" value is always positive. + if fraction < 0 then + sec_int = sec_int - 1 + fraction = fraction + 1 + end -- if there is one of nsec, usec, msec provided -- then ignore fraction in timestamp -- otherwise - use nsec, usec, or msec diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua index a5cc8c9446..790ec29ac6 100755 --- a/test/app-tap/datetime.test.lua +++ b/test/app-tap/datetime.test.lua @@ -192,7 +192,7 @@ test:test("Default date creation and comparison", function(test) end) test:test("Simple date creation by attributes", function(test) - test:plan(14) + test:plan(15) local ts local obj = {} local attribs = { @@ -222,6 +222,8 @@ test:test("Simple date creation by attributes", function(test) '2021-08-30T21:31:11.000123Z', '{timestamp.usec}') test:is(tostring(date.new{timestamp = 1630359071, nsec = 123}), '2021-08-30T21:31:11.000000123Z', '{timestamp.nsec}') + test:is(tostring(date.new{timestamp = -0.1}), + '1969-12-31T23:59:59.900Z', '{negative timestamp}') end) test:test("Simple date creation by attributes - check failed", function(test) @@ -1779,7 +1781,7 @@ test:test("totable{}", function(test) end) test:test("Time :set{} operations", function(test) - test:plan(15) + test:plan(16) local ts = date.new{ year = 2021, month = 8, day = 31, hour = 0, min = 31, sec = 11, tzoffset = '+0300'} @@ -1813,10 +1815,12 @@ test:test("Time :set{} operations", function(test) '2021-08-30T21:31:11.000123+0800', 'timestamp + usec') test:is(tostring(ts:set{timestamp = 1630359071, nsec = 123}), '2021-08-30T21:31:11.000000123+0800', 'timestamp + nsec') + test:is(tostring(ts:set{timestamp = -0.1}), + '1969-12-31T23:59:59.900+0800', 'negative timestamp') end) test:test("Check :set{} and .new{} equal for all attributes", function(test) - test:plan(11) + test:plan(12) local ts, ts2 local obj = {} local attribs = { @@ -1845,6 +1849,12 @@ test:test("Check :set{} and .new{} equal for all attributes", function(test) ts2 = date.new():set(obj) test:is(ts, ts2, ('timestamp+tzoffset (%s = %s)'): format(tostring(ts), tostring(ts2))) + + obj = {timestamp = -0.1, tzoffset = '+0800'} + ts = date.new(obj) + ts2 = date.new():set(obj) + test:is(ts, ts2, ('negative timestamp+tzoffset (%s = %s)'): + format(tostring(ts), tostring(ts2))) end) -- GitLab