From 230aba54aaa90078eed4901290a4e2c1d542cbb7 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev <moiseev.georgii@gmail.com> Date: Fri, 14 Jul 2023 14:03:10 +0300 Subject: [PATCH] datetime: allow boundary values for interval.new Before this patch, one couldn't create new datetime interval with boundary value from Lua. At the same time, it was possible to create such interval from Lua through addition and subtraction. C range verification allow to create boundary value intervals, error message also implies that they should be allowed. (See #8878 for more info.) Closes #8878 NO_DOC=small bug fix (cherry picked from commit b2a001cc0f46fd9c53e576a74fa6263c6e6069bf) --- .../gh-8878-interval-boundaries-fix.md | 4 + src/lua/datetime.lua | 2 +- test/app-tap/datetime.test.lua | 82 ++++++++++++++++++- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/gh-8878-interval-boundaries-fix.md diff --git a/changelogs/unreleased/gh-8878-interval-boundaries-fix.md b/changelogs/unreleased/gh-8878-interval-boundaries-fix.md new file mode 100644 index 0000000000..0a5274a28e --- /dev/null +++ b/changelogs/unreleased/gh-8878-interval-boundaries-fix.md @@ -0,0 +1,4 @@ +## bugfix/datetime + +* Fixed a bug raising a false positive error when creating new intervals with + range boundary values (gh-8878). diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua index 60949a8e6e..a3fd61ea5d 100644 --- a/src/lua/datetime.lua +++ b/src/lua/datetime.lua @@ -294,7 +294,7 @@ local function checked_max_value(v, max, txt, def) error(('numeric value expected, but received %s'): format(type(v)), 2) end - if v > -max and v < max then + if v >= -max and v <= max then return v end error(('value %s of %s is out of allowed range [%s, %s]'): diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua index 790ec29ac6..2349fdf252 100755 --- a/test/app-tap/datetime.test.lua +++ b/test/app-tap/datetime.test.lua @@ -4,10 +4,13 @@ local tap = require('tap') local test = tap.test('errno') local date = require('datetime') local ffi = require('ffi') +local json = require('json') local TZ = date.TZ test:plan(39) +local INT_MAX = 2147483647 + -- minimum supported date - -5879610-06-22 local MIN_DATE_YEAR = -5879610 -- maximum supported date - 5879611-07-11 @@ -23,6 +26,9 @@ local MAX_DAY_RANGE = MAX_YEAR_RANGE * AVERAGE_DAYS_YEAR local MAX_HOUR_RANGE = MAX_DAY_RANGE * 24 local MAX_MIN_RANGE = MAX_HOUR_RANGE * 60 local MAX_SEC_RANGE = MAX_DAY_RANGE * SECS_PER_DAY +local MAX_NSEC_RANGE = INT_MAX +local MAX_USEC_RANGE = math.floor(MAX_NSEC_RANGE / 1e3) +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'.. @@ -96,6 +102,43 @@ local function assert_raises_like(test, error_msg, func, ...) ('"%s" received, "%s" expected'):format(err_tail, error_msg)) end +-- Basic interval string representation generator for interval arguments. +local fields_str = { + { 'year', 'years' }, + { 'month', 'months' }, + { 'week', 'weeks' }, + { 'day', 'days' }, + { 'hour', 'hours' }, + { 'min', 'minutes' }, + { 'sec', 'seconds' }, + { 'msec', 'nanoseconds', 1e6 }, + { 'usec', 'nanoseconds', 1e3 }, + { 'nsec', 'nanoseconds' }, +} + +local function iv_str_repr(tab_iv) + local iv_repr = '' + + for _, field in ipairs(fields_str) do + local name, field_repr, mult = unpack(field) + local value = tab_iv[name] + if value ~= nil then + if mult ~= nil then + value = value * mult + end + if iv_repr == '' then + iv_repr = ('%+d %s'):format(value, field_repr) + else + iv_repr = iv_repr .. (', %d %s'):format(value, field_repr) + end + elseif (name == 'sec') and (iv_repr == '') then + iv_repr = ('%+d %s'):format(0, field_repr) + end + end + + return iv_repr +end + test:test("Datetime API checks", function(test) test:plan(12) local ts = date.new() @@ -1269,7 +1312,7 @@ test:test("Time interval operations - different timezones", function(test) end) test:test("Time intervals creation - range checks", function(test) - test:plan(23) + test:plan(63) local inew = date.interval.new @@ -1316,6 +1359,43 @@ test:test("Time intervals creation - range checks", function(test) local err_msg, attribs = unpack(row) assert_raises(test, err_msg, function() return inew(attribs) end) end + + local range_boundary = { + year = MAX_YEAR_RANGE, + month = MAX_MONTH_RANGE, + week = MAX_WEEK_RANGE, + day = MAX_DAY_RANGE, + hour = MAX_HOUR_RANGE, + min = MAX_MIN_RANGE, + sec = MAX_SEC_RANGE, + msec = MAX_MSEC_RANGE, + usec = MAX_USEC_RANGE, + nsec = MAX_NSEC_RANGE, + } + + for name, range_max in pairs(range_boundary) do + local val_max = math.floor(range_max) + + local attrib_min = {[name] = -val_max} + test:is(tostring(inew(attrib_min)), iv_str_repr(attrib_min), + ('interval %s is allowed'):format(json.encode(attrib_min))) + + local attrib_max = {[name] = val_max} + test:is(tostring(inew(attrib_max)), iv_str_repr(attrib_max), + ('interval %s is allowed'):format(json.encode(attrib_max))) + + local attrib_over_min = {[name] = -val_max - 1} + assert_raises( + test, + range_check_error(name, attrib_over_min[name], {-range_max, range_max}), + function() inew(attrib_over_min) end) + + local attrib_over_max = {[name] = val_max + 1} + assert_raises( + test, + range_check_error(name, attrib_over_max[name], {-range_max, range_max}), + function() inew(attrib_over_max) end) + end end) test:test("Time intervals ops - huge values", function(test) -- GitLab