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 0000000000000000000000000000000000000000..d75101f38341e85bc72d695ed582de45d61ce02c --- /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 7bb84badbb684d3703b4a61370d0ba6b3652afcc..60949a8e6ef4795a7b451a7ba703a674968d805e 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 a5cc8c9446b863d02c28aa4500d47bff4b0a7d2b..790ec29ac65e9acde3a167c3ef933c5b912ed529 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)