From 64faabe88f8f0aeb246e55ca8da82d50dbe1e056 Mon Sep 17 00:00:00 2001
From: Timur Safin <tsafin@tarantool.org>
Date: Sat, 5 Mar 2022 22:36:33 +0300
Subject: [PATCH] datetime: simplify boundary checks

We used to use very ugly and tricky approach to check that passed years,
months and days were not exceeding supported range of values. Now we have
introduced to `c-dt` library the new function `dt_from_ymd_checked` for
that purpose (i.e. check that values are valid, and construct dt from
them). So rewrite/simplify Lua code to use that entry as
`tnt_dt_from_ymd_checked`.

Part of #6731

NO_DOC=refactoring
NO_CHANGELOG=refactoring
---
 src/lua/datetime.lua           | 48 +++++++++-------------------------
 test/app-tap/datetime.test.lua | 30 +++++++--------------
 2 files changed, 22 insertions(+), 56 deletions(-)

diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 7b494fe92b..c581e170d3 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -42,7 +42,7 @@ typedef enum {
 } dt_dow_t;
 
 dt_t   tnt_dt_from_rdn     (int n);
-dt_t   tnt_dt_from_ymd     (int y, int m, int d);
+bool   tnt_dt_from_ymd_checked(int y, int m, int d, dt_t *val);
 void   tnt_dt_to_ymd       (dt_t dt, int *y, int *m, int *d);
 
 dt_dow_t tnt_dt_dow        (dt_t dt);
@@ -287,38 +287,16 @@ local function check_dt(dt_v, direction, v, unit)
     end
 end
 
-local function check_ymd_range(y, M, d)
-    -- Fast path. Max/min year is rather theoretical. Nobody is going to
-    -- actually use them.
-    if y > MIN_DATE_YEAR and y < MAX_DATE_YEAR then
-        return
-    end
-    -- Slow path.
-    if y < MIN_DATE_YEAR then
-        goto min_err
-    elseif y > MAX_DATE_YEAR then
-        goto max_err
-    elseif y == MIN_DATE_YEAR then
-        if M < MIN_DATE_MONTH then
-            goto min_err
-        elseif M == MIN_DATE_MONTH and d < MIN_DATE_DAY then
-            goto min_err
-        end
-        return
-    -- y == MAX_DATE_YEAR
-    elseif M > MAX_DATE_MONTH then
-        goto max_err
-    elseif M == MAX_DATE_MONTH and d > MAX_DATE_DAY then
-        goto max_err
-    else
-        return
+local function dt_from_ymd_checked(y, M, d)
+    local pdt = date_dt_stash_take()
+    local is_valid = builtin.tnt_dt_from_ymd_checked(y, M, d, pdt)
+    if not is_valid then
+        date_dt_stash_put(pdt)
+        error(('date %4d-%02d-%02d is invalid'):format(y, M, d))
     end
-::min_err::
-    error(('date %d-%02d-%02d is less than minimum allowed %s'):
-        format(y, M, d, MIN_DATE_TEXT))
-::max_err::
-    error(('date %d-%02d-%02d is greater than maximum allowed %s'):
-        format(y, M, d, MAX_DATE_TEXT))
+    local dt = pdt[0]
+    date_dt_stash_put(pdt)
+    return dt
 end
 
 -- check v value against maximum/minimum possible values
@@ -619,8 +597,7 @@ local function datetime_new(obj)
                     format(d, M, y), 3)
             end
         end
-        check_ymd_range(y, M, d)
-        dt = builtin.tnt_dt_from_ymd(y, M, d)
+        dt = dt_from_ymd_checked(y, M, d)
     end
 
     -- .hour, .minute, .second
@@ -1036,7 +1013,7 @@ local function datetime_ymd_update(self, y, M, d, new_offset)
                   format(d, M, y), 3)
         end
     end
-    local dt = builtin.tnt_dt_from_ymd(y, M, d)
+    local dt = dt_from_ymd_checked(y, M, d)
     datetime_update_dt(self, dt, new_offset)
 end
 
@@ -1159,7 +1136,6 @@ local function datetime_set(self, obj)
         y = y or y0
         M = M or M0
         d = d or d0
-        check_ymd_range(y, M, d)
         datetime_ymd_update(self, y, M, d, offset)
     end
 
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 6d60aa5dd1..5036499b5f 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -15,12 +15,8 @@ test:plan(32)
 
 -- minimum supported date - -5879610-06-22
 local MIN_DATE_YEAR = -5879610
-local MIN_DATE_MONTH = 6
-local MIN_DATE_DAY = 22
 -- maximum supported date - 5879611-07-11
 local MAX_DATE_YEAR = 5879611
-local MAX_DATE_MONTH = 7
-local MAX_DATE_DAY = 11
 
 local SECS_PER_DAY      = 86400
 local AVERAGE_DAYS_YEAR = 365.25
@@ -78,14 +74,8 @@ local function range_check_3_error(name, value, range)
             format(value, name, range[1], range[2], range[3])
 end
 
-local function less_than_min(y, M, d)
-    return ('date %d-%02d-%02d is less than minimum allowed %d-%02d-%02d'):
-            format(y, M, d, MIN_DATE_YEAR, MIN_DATE_MONTH, MIN_DATE_DAY)
-end
-
-local function greater_than_max(y, M, d)
-    return ('date %d-%02d-%02d is greater than maximum allowed %d-%02d-%02d'):
-            format(y, M, d, MAX_DATE_YEAR, MAX_DATE_MONTH, MAX_DATE_DAY)
+local function invalid_date(y, M, d)
+    return ('date %d-%02d-%02d is invalid'):format(y, M, d)
 end
 
 local function invalid_tz_fmt_error(val)
@@ -320,17 +310,17 @@ test:test("Simple date creation by attributes - check failed", function(test)
         {range_check_3_error('day', 32, {-1, 1, 31}),
             {year = 2021, month = 6, day = 32}},
         {invalid_days_in_mon(31, 6, 2021), { year = 2021, month = 6, day = 31}},
-        {less_than_min(-5879610, 6, 21),
+        {invalid_date(-5879610, 6, 21),
             {year = -5879610, month = 6, day = 21}},
-        {less_than_min(-5879610, 1, 1),
+        {invalid_date(-5879610, 1, 1),
             {year = -5879610, month = 1, day = 1}},
         {range_check_error('year', -16009610, {MIN_DATE_YEAR, MAX_DATE_YEAR}),
             {year = -16009610, month = 12, day = 31}},
         {range_check_error('year', 16009610, {MIN_DATE_YEAR, MAX_DATE_YEAR}),
             {year = 16009610, month = 1, day = 1}},
-        {greater_than_max(MAX_DATE_YEAR, 9, 1),
+        {invalid_date(MAX_DATE_YEAR, 9, 1),
             {year = MAX_DATE_YEAR, month = 9, day = 1}},
-        {greater_than_max(MAX_DATE_YEAR, 7, 12),
+        {invalid_date(MAX_DATE_YEAR, 7, 12),
             {year = MAX_DATE_YEAR, month = 7, day = 12}},
     }
     for _, row in pairs(specific_errors) do
@@ -1610,17 +1600,17 @@ test:test("Time invalid :set{} operations", function(test)
         {range_check_3_error('day', 32, {-1, 1, 31}),
             {year = 2021, month = 6, day = 32}},
         {invalid_days_in_mon(31, 6, 2021), { month = 6, day = 31}},
-        {less_than_min(-5879610, 6, 21),
+        {invalid_date(-5879610, 6, 21),
             {year = -5879610, month = 6, day = 21}},
-        {less_than_min(-5879610, 1, 1),
+        {invalid_date(-5879610, 1, 1),
             {year = -5879610, month = 1, day = 1}},
         {range_check_error('year', -16009610, {MIN_DATE_YEAR, MAX_DATE_YEAR}),
             {year = -16009610, month = 12, day = 31}},
         {range_check_error('year', 16009610, {MIN_DATE_YEAR, MAX_DATE_YEAR}),
             {year = 16009610, month = 1, day = 1}},
-        {greater_than_max(MAX_DATE_YEAR, 9, 1),
+        {invalid_date(MAX_DATE_YEAR, 9, 1),
             {year = MAX_DATE_YEAR, month = 9, day = 1}},
-        {greater_than_max(MAX_DATE_YEAR, 7, 12),
+        {invalid_date(MAX_DATE_YEAR, 7, 12),
             {year = MAX_DATE_YEAR, month = 7, day = 12}},
     }
     for _, row in pairs(specific_errors) do
-- 
GitLab