diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index 7b494fe92bd666b103a7820aa2f9718f848501d3..c581e170d37f3751f0c37f5170f3d71619fae86a 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 6d60aa5dd13300fae4a77b1e418232a278609471..5036499b5f69c4dfa3a73665d6b1581cdc7f6b4e 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