From 29be5eb3b6f786d0eafe0dc50bdaa037cc7351f0 Mon Sep 17 00:00:00 2001
From: Sergey Bronnikov <sergeyb@tarantool.org>
Date: Tue, 27 Aug 2024 15:48:17 +0300
Subject: [PATCH] datetime: forbid using non-integer values in .new()

The patch forbids using non-integer values in datetime constructor
`datetime.new()` for `year`, `month`, `day`, `hour`, `min`, `sec`,
`usec`, `msec` and `nsec` keys. The type of `tzoffset` can be
integer or string, `timestamp` can be double, and integer values
allowed in timestamp if `nsec`, `usec`, or `msecs` provided.
An error will be raised when a value of incorrect type is passed.

Part of #10391

@TarantoolBot document
Title: Update types of values passed to `datetime.new()`

`datetime.new()` can accept only integer values for `year`,
`month`, `day`, `hour`, `min`, `sec`, `usec`, `msec` and `nsec`.
The type of `tzoffset` can be integer or string, `timestamp` can
be integer or double.

(cherry picked from commit cc9010a2b11477b2f16f2b2e168a6b9dcca2fb20)
---
 ...-10391-forbid-non-integers-datetime-new.md |  3 ++
 src/lua/datetime.lua                          | 10 +++++++
 test/app-tap/datetime.test.lua                | 28 +++++++++++++------
 3 files changed, 33 insertions(+), 8 deletions(-)
 create mode 100644 changelogs/unreleased/gh-10391-forbid-non-integers-datetime-new.md

diff --git a/changelogs/unreleased/gh-10391-forbid-non-integers-datetime-new.md b/changelogs/unreleased/gh-10391-forbid-non-integers-datetime-new.md
new file mode 100644
index 0000000000..d7b560a85b
--- /dev/null
+++ b/changelogs/unreleased/gh-10391-forbid-non-integers-datetime-new.md
@@ -0,0 +1,3 @@
+## bugfix/datetime
+
+- Forbid non-integers in `datetime.new()` (gh-10391).
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 024cfb73d1..c3ee775fa2 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -504,6 +504,7 @@ end
 
 local function get_timezone(offset, msg)
     if type(offset) == 'number' then
+        check_integer(offset, 'tzoffset')
         return offset
     elseif type(offset) == 'string' then
         return parse_tzoffset(offset)
@@ -527,31 +528,37 @@ local function datetime_new(obj)
     local y = obj.year
     if y ~= nil then
         check_range(y, MIN_DATE_YEAR, MAX_DATE_YEAR, 'year')
+        check_integer(y, 'year')
         ymd = true
     end
     local M = obj.month
     if M ~= nil then
         check_range(M, 1, 12, 'month')
+        check_integer(M, 'month')
         ymd = true
     end
     local d = obj.day
     if d ~= nil then
         check_range(d, 1, 31, 'day', -1)
+        check_integer(d, 'day')
         ymd = true
     end
     local h = obj.hour
     if h ~= nil then
         check_range(h, 0, 23, 'hour')
+        check_integer(h, 'hour')
         hms = true
     end
     local m = obj.min
     if m ~= nil then
         check_range(m, 0, 59, 'min')
+        check_integer(m, 'min')
         hms = true
     end
     local s = obj.sec
     if s ~= nil then
         check_range(s, 0, 60, 'sec')
+        check_integer(s, 'sec')
         hms = true
     end
 
@@ -565,12 +572,15 @@ local function datetime_new(obj)
         end
         if usec ~= nil then
             check_range(usec, 0, 1e6, 'usec')
+            check_integer(usec, 'usec')
             nsec = usec * 1e3
         elseif msec ~= nil then
             check_range(msec, 0, 1e3, 'msec')
+            check_integer(msec, 'msec')
             nsec = msec * 1e6
         else
             check_range(nsec, 0, 1e9, 'nsec')
+            check_integer(nsec, 'nsec')
         end
     else
         nsec = 0
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 3301fc39de..ed4532efa0 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -34,9 +34,11 @@ local MAX_MSEC_RANGE = math.floor(MAX_NSEC_RANGE / 1e6)
 local incompat_types = 'incompatible types for datetime comparison'
 local only_integer_ts = 'only integer values allowed in timestamp'..
                         ' if nsec, usec, or msecs provided'
+local only_integer_msg = function(key)
+    return key .. ': integer value expected, but received number'
+end
 local only_one_of = 'only one of nsec, usec or msecs may be defined'..
                     ' simultaneously'
-local int_ival_exp = 'sec: integer value expected, but received number'
 local timestamp_and_ymd = 'timestamp is not allowed if year/month/day provided'
 local timestamp_and_hms = 'timestamp is not allowed if hour/min/sec provided'
 local str_or_num_exp = 'tzoffset: string or number expected, but received'
@@ -271,7 +273,7 @@ test:test("Simple date creation by attributes", function(test)
 end)
 
 test:test("Simple date creation by attributes - check failed", function(test)
-    test:plan(83)
+    test:plan(93)
 
     local boundary_checks = {
         {'year', {MIN_DATE_YEAR, MAX_DATE_YEAR}},
@@ -339,6 +341,16 @@ test:test("Simple date creation by attributes - check failed", function(test)
         {only_one_of, { nsec = 123456, msec = 123}},
         {only_one_of, { usec = 123, msec = 123}},
         {only_one_of, { nsec = 123456, usec = 123, msec = 123}},
+        {only_integer_msg('nsec'), { nsec = 1.1 }},
+        {only_integer_msg('msec'), { msec = 1.1 }},
+        {only_integer_msg('usec'), { usec = 1.1 }},
+        {only_integer_msg('tzoffset'), { tzoffset = 1.1 }},
+        {only_integer_msg('year'), { year = 1.1 }},
+        {only_integer_msg('month'), { month = 1.1 }},
+        {only_integer_msg('day'), { day = 1.1 }},
+        {only_integer_msg('hour'), { hour = 1.1 }},
+        {only_integer_msg('min'), { min = 1.1 }},
+        {only_integer_msg('sec'), { sec = 1.1 }},
         {only_integer_ts, { timestamp = 12345.125, usec = 123}},
         {only_integer_ts, { timestamp = 12345.125, msec = 123}},
         {only_integer_ts, { timestamp = 12345.125, nsec = 123}},
@@ -1169,9 +1181,9 @@ test:test("Time interval operations", function(test)
         {only_one_of, { nsec = 123456, msec = 123}},
         {only_one_of, { usec = 123, msec = 123}},
         {only_one_of, { nsec = 123456, usec = 123, msec = 123}},
-        {int_ival_exp, { sec = 12345.125, usec = 123}},
-        {int_ival_exp, { sec = 12345.125, msec = 123}},
-        {int_ival_exp, { sec = 12345.125, nsec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, usec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, msec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, nsec = 123}},
     }
     for _, row in pairs(specific_errors) do
         local err_msg, obj = unpack(row)
@@ -1322,9 +1334,9 @@ test:test("Time intervals creation - range checks", function(test)
         {only_one_of, { nsec = 123456, msec = 123}},
         {only_one_of, { usec = 123, msec = 123}},
         {only_one_of, { nsec = 123456, usec = 123, msec = 123}},
-        {int_ival_exp, { sec = 12345.125, usec = 123}},
-        {int_ival_exp, { sec = 12345.125, msec = 123}},
-        {int_ival_exp, { sec = 12345.125, nsec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, usec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, msec = 123}},
+        {only_integer_msg('sec'), { sec = 12345.125, nsec = 123}},
         {table_expected('interval.new()', '2001-01-01'), '2001-01-01'},
         {table_expected('interval.new()', 20010101), 20010101},
         {range_check_error('year', 1e21, {-MAX_YEAR_RANGE, MAX_YEAR_RANGE}),
-- 
GitLab