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)