From abeaf60e4ae910dd88c5fd05355d5bc78f25941c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 2 Aug 2021 13:33:16 +0200 Subject: [PATCH] Improve compatibility with non-whole-minute timezone offsets --- base/common/DateLUTImpl.h | 55 ++++++++++--------- .../01958_partial_hour_timezone.reference | 20 +++++++ .../01958_partial_hour_timezone.sql | 21 +++++++ 3 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 tests/queries/0_stateless/01958_partial_hour_timezone.reference create mode 100644 tests/queries/0_stateless/01958_partial_hour_timezone.sql diff --git a/base/common/DateLUTImpl.h b/base/common/DateLUTImpl.h index 202eb88a361..06f6bbcc6fa 100644 --- a/base/common/DateLUTImpl.h +++ b/base/common/DateLUTImpl.h @@ -251,18 +251,23 @@ private: } template - static inline T roundDown(T x, Divisor divisor) + inline T roundDown(T x, Divisor divisor) const { static_assert(std::is_integral_v && std::is_integral_v); assert(divisor > 0); - if (likely(x >= 0)) - return x / divisor * divisor; + if (likely(offset_is_whole_number_of_hours_during_epoch)) + { + if (likely(x >= 0)) + return x / divisor * divisor; - /// Integer division for negative numbers rounds them towards zero (up). - /// We will shift the number so it will be rounded towards -inf (down). + /// Integer division for negative numbers rounds them towards zero (up). + /// We will shift the number so it will be rounded towards -inf (down). + return (x + 1 - divisor) / divisor * divisor; + } - return (x + 1 - divisor) / divisor * divisor; + Time date = find(x).date; + return date + (x - date) / divisor * divisor; } public: @@ -459,7 +464,21 @@ public: inline unsigned toSecond(Time t) const { - auto res = t % 60; + if (offset_is_whole_number_of_hours_during_epoch) + { + auto res = t % 60; + if (likely(res >= 0)) + return res; + return res + 60; + } + + LUTIndex index = findIndex(t); + UInt32 time = t - lut[index].date; + + if (time >= lut[index].time_at_offset_change()) + time += lut[index].amount_of_offset_change(); + + auto res = time % 60; if (likely(res >= 0)) return res; return res + 60; @@ -486,26 +505,8 @@ public: inline Time toStartOfMinute(Time t) const { return roundDown(t, 60); } inline Time toStartOfFiveMinute(Time t) const { return roundDown(t, 300); } inline Time toStartOfFifteenMinutes(Time t) const { return roundDown(t, 900); } - - inline Time toStartOfTenMinutes(Time t) const - { - if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) - return t / 600 * 600; - - /// More complex logic is for Nepal - it has offset 05:45. Australia/Eucla is also unfortunate. - Time date = find(t).date; - return date + (t - date) / 600 * 600; - } - - /// NOTE: Assuming timezone transitions are multiple of hours. Lord Howe Island in Australia is a notable exception. - inline Time toStartOfHour(Time t) const - { - if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) - return t / 3600 * 3600; - - Time date = find(t).date; - return date + (t - date) / 3600 * 3600; - } + inline Time toStartOfTenMinutes(Time t) const { return roundDown(t, 600); } + inline Time toStartOfHour(Time t) const { return roundDown(t, 3600); } /** Number of calendar day since the beginning of UNIX epoch (1970-01-01 is zero) * We use just two bytes for it. It covers the range up to 2105 and slightly more. diff --git a/tests/queries/0_stateless/01958_partial_hour_timezone.reference b/tests/queries/0_stateless/01958_partial_hour_timezone.reference new file mode 100644 index 00000000000..a86391b491c --- /dev/null +++ b/tests/queries/0_stateless/01958_partial_hour_timezone.reference @@ -0,0 +1,20 @@ +Row 1: +────── +toUnixTimestamp(t): 14459031 +timeZoneOffset(t): -2670 +formatDateTime(t, '%F %T', 'Africa/Monrovia'): 1970-06-17 07:39:21 +toString(t, 'Africa/Monrovia'): 1970-06-17 07:39:21 +toStartOfMinute(t): 1970-06-17 07:39:00 +toStartOfFiveMinute(t): 1970-06-17 07:35:00 +toStartOfFifteenMinutes(t): 1970-06-17 07:30:00 +toStartOfTenMinutes(t): 1970-06-17 07:30:00 +toStartOfHour(t): 1970-06-17 07:00:00 +toStartOfDay(t): 1970-06-17 00:00:00 +toStartOfWeek(t): 1970-06-14 +toStartOfInterval(t, toIntervalSecond(1)): 1970-06-17 07:39:21 +toStartOfInterval(t, toIntervalMinute(1)): 1970-06-17 07:39:00 +toStartOfInterval(t, toIntervalMinute(2)): 1970-06-17 07:38:00 +toStartOfInterval(t, toIntervalMinute(5)): 1970-06-17 07:35:00 +toStartOfInterval(t, toIntervalMinute(60)): 1970-06-17 07:00:00 +addMinutes(t, 1): 1970-06-17 07:40:21 +addMinutes(t, 60): 1970-06-17 08:39:21 diff --git a/tests/queries/0_stateless/01958_partial_hour_timezone.sql b/tests/queries/0_stateless/01958_partial_hour_timezone.sql new file mode 100644 index 00000000000..9bcb03ea4f2 --- /dev/null +++ b/tests/queries/0_stateless/01958_partial_hour_timezone.sql @@ -0,0 +1,21 @@ +-- Appeared in https://github.com/ClickHouse/ClickHouse/pull/26978#issuecomment-890889362 +WITH toDateTime('1970-06-17 07:39:21', 'Africa/Monrovia') as t +SELECT toUnixTimestamp(t), + timeZoneOffset(t), + formatDateTime(t, '%F %T', 'Africa/Monrovia'), + toString(t, 'Africa/Monrovia'), + toStartOfMinute(t), + toStartOfFiveMinute(t), + toStartOfFifteenMinutes(t), + toStartOfTenMinutes(t), + toStartOfHour(t), + toStartOfDay(t), + toStartOfWeek(t), + toStartOfInterval(t, INTERVAL 1 second), + toStartOfInterval(t, INTERVAL 1 minute), + toStartOfInterval(t, INTERVAL 2 minute), + toStartOfInterval(t, INTERVAL 5 minute), + toStartOfInterval(t, INTERVAL 60 minute), + addMinutes(t, 1), + addMinutes(t, 60) +FORMAT Vertical;