From 5a2f2e0cc846fa3b6a4eb02f8189df71cda3e2fe Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 24 Mar 2021 01:53:19 +0300 Subject: [PATCH] Change behaviour of `toStartOfInterval` in backward incompatible way --- base/common/DateLUTImpl.h | 46 ++++++++-- .../01772_to_start_of_hour_align.reference | 86 +++++++++++++++++++ .../01772_to_start_of_hour_align.sql | 21 +++++ 3 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 tests/queries/0_stateless/01772_to_start_of_hour_align.reference create mode 100644 tests/queries/0_stateless/01772_to_start_of_hour_align.sql diff --git a/base/common/DateLUTImpl.h b/base/common/DateLUTImpl.h index 43fc1b8befd..6237963fd7f 100644 --- a/base/common/DateLUTImpl.h +++ b/base/common/DateLUTImpl.h @@ -853,15 +853,43 @@ public: { if (hours == 1) return toStartOfHour(t); + + /** We will round the hour number since the midnight. + * It may split the day into non-equal intervals. + * For example, if we will round to 11-hour interval, + * the day will be split to the intervals 00:00:00..10:59:59, 11:00:00..21:59:59, 22:00:00..23:59:59. + * In case of daylight saving time or other transitions, + * the intervals can be shortened or prolonged to the amount of transition. + */ + UInt64 seconds = hours * 3600; - t = roundDown(t, seconds); + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; - if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) - return t; + time_t time = t - values.date; + if (time >= values.time_at_offset_change()) + { + /// Align to new hour numbers before rounding. + time += values.amount_of_offset_change(); + time = time / seconds * seconds; - /// TODO check if it's correct. - return toStartOfHour(t); + /// Should substract the shift back but only if rounded time is not before shift. + if (time >= values.time_at_offset_change()) + { + time -= values.amount_of_offset_change(); + + /// With cutoff at the time of the shift. Otherwise we may end up with something like 23:00 previous day. + if (time < values.time_at_offset_change()) + time = values.time_at_offset_change(); + } + } + else + { + time = time / seconds * seconds; + } + + return values.date + time; } inline time_t toStartOfMinuteInterval(time_t t, UInt64 minutes) const @@ -869,6 +897,14 @@ public: if (minutes == 1) return toStartOfMinute(t); + /** In contrast to "toStartOfHourInterval" function above, + * the minute intervals are not aligned to the midnight. + * You will get unexpected results if for example, you round down to 60 minute interval + * and there was a time shift to 30 minutes. + * + * But this is not specified in docs and can be changed in future. + */ + UInt64 seconds = 60 * minutes; return roundDown(t, seconds); } diff --git a/tests/queries/0_stateless/01772_to_start_of_hour_align.reference b/tests/queries/0_stateless/01772_to_start_of_hour_align.reference new file mode 100644 index 00000000000..f130df3bef5 --- /dev/null +++ b/tests/queries/0_stateless/01772_to_start_of_hour_align.reference @@ -0,0 +1,86 @@ +2021-03-23 00:00:00 +2021-03-23 11:00:00 +2021-03-23 22:00:00 +2021-03-23 13:00:00 +2021-03-23 12:00:00 +2021-03-23 00:00:00 +2010-03-28 00:00:00 2010-03-28 00:00:00 1269723600 +2010-03-28 00:15:00 2010-03-28 00:00:00 1269724500 +2010-03-28 00:30:00 2010-03-28 00:00:00 1269725400 +2010-03-28 00:45:00 2010-03-28 00:00:00 1269726300 +2010-03-28 01:00:00 2010-03-28 00:00:00 1269727200 +2010-03-28 01:15:00 2010-03-28 00:00:00 1269728100 +2010-03-28 01:30:00 2010-03-28 00:00:00 1269729000 +2010-03-28 01:45:00 2010-03-28 00:00:00 1269729900 +2010-03-28 03:00:00 2010-03-28 03:00:00 1269730800 +2010-03-28 03:15:00 2010-03-28 03:00:00 1269731700 +2010-03-28 03:30:00 2010-03-28 03:00:00 1269732600 +2010-03-28 03:45:00 2010-03-28 03:00:00 1269733500 +2010-03-28 04:00:00 2010-03-28 04:00:00 1269734400 +2010-03-28 04:15:00 2010-03-28 04:00:00 1269735300 +2010-03-28 04:30:00 2010-03-28 04:00:00 1269736200 +2010-03-28 04:45:00 2010-03-28 04:00:00 1269737100 +2010-03-28 05:00:00 2010-03-28 04:00:00 1269738000 +2010-03-28 05:15:00 2010-03-28 04:00:00 1269738900 +2010-03-28 05:30:00 2010-03-28 04:00:00 1269739800 +2010-03-28 05:45:00 2010-03-28 04:00:00 1269740700 +2010-10-31 00:00:00 2010-10-31 00:00:00 1288468800 +2010-10-31 00:15:00 2010-10-31 00:00:00 1288469700 +2010-10-31 00:30:00 2010-10-31 00:00:00 1288470600 +2010-10-31 00:45:00 2010-10-31 00:00:00 1288471500 +2010-10-31 01:00:00 2010-10-31 00:00:00 1288472400 +2010-10-31 01:15:00 2010-10-31 00:00:00 1288473300 +2010-10-31 01:30:00 2010-10-31 00:00:00 1288474200 +2010-10-31 01:45:00 2010-10-31 00:00:00 1288475100 +2010-10-31 02:00:00 2010-10-31 02:00:00 1288476000 +2010-10-31 02:15:00 2010-10-31 02:00:00 1288476900 +2010-10-31 02:30:00 2010-10-31 02:00:00 1288477800 +2010-10-31 02:45:00 2010-10-31 02:00:00 1288478700 +2010-10-31 02:00:00 2010-10-31 02:00:00 1288479600 +2010-10-31 02:15:00 2010-10-31 02:00:00 1288480500 +2010-10-31 02:30:00 2010-10-31 02:00:00 1288481400 +2010-10-31 02:45:00 2010-10-31 02:00:00 1288482300 +2010-10-31 03:00:00 2010-10-31 02:00:00 1288483200 +2010-10-31 03:15:00 2010-10-31 02:00:00 1288484100 +2010-10-31 03:30:00 2010-10-31 02:00:00 1288485000 +2010-10-31 03:45:00 2010-10-31 02:00:00 1288485900 +2020-04-05 00:00:00 2020-04-05 00:00:00 1586005200 +2020-04-05 00:15:00 2020-04-05 00:00:00 1586006100 +2020-04-05 00:30:00 2020-04-05 00:00:00 1586007000 +2020-04-05 00:45:00 2020-04-05 00:00:00 1586007900 +2020-04-05 01:00:00 2020-04-05 00:00:00 1586008800 +2020-04-05 01:15:00 2020-04-05 00:00:00 1586009700 +2020-04-05 01:30:00 2020-04-05 00:00:00 1586010600 +2020-04-05 01:45:00 2020-04-05 00:00:00 1586011500 +2020-04-05 01:30:00 2020-04-05 00:00:00 1586012400 +2020-04-05 01:45:00 2020-04-05 00:00:00 1586013300 +2020-04-05 02:00:00 2020-04-05 02:00:00 1586014200 +2020-04-05 02:15:00 2020-04-05 02:00:00 1586015100 +2020-04-05 02:30:00 2020-04-05 02:00:00 1586016000 +2020-04-05 02:45:00 2020-04-05 02:00:00 1586016900 +2020-04-05 03:00:00 2020-04-05 02:00:00 1586017800 +2020-04-05 03:15:00 2020-04-05 02:00:00 1586018700 +2020-04-05 03:30:00 2020-04-05 02:00:00 1586019600 +2020-04-05 03:45:00 2020-04-05 02:00:00 1586020500 +2020-04-05 04:00:00 2020-04-05 04:00:00 1586021400 +2020-04-05 04:15:00 2020-04-05 04:00:00 1586022300 +2020-10-04 00:00:00 2020-10-04 00:00:00 1601731800 +2020-10-04 00:15:00 2020-10-04 00:00:00 1601732700 +2020-10-04 00:30:00 2020-10-04 00:00:00 1601733600 +2020-10-04 00:45:00 2020-10-04 00:00:00 1601734500 +2020-10-04 01:00:00 2020-10-04 00:00:00 1601735400 +2020-10-04 01:15:00 2020-10-04 00:00:00 1601736300 +2020-10-04 01:30:00 2020-10-04 00:00:00 1601737200 +2020-10-04 01:45:00 2020-10-04 00:00:00 1601738100 +2020-10-04 02:30:00 2020-10-04 02:30:00 1601739000 +2020-10-04 02:45:00 2020-10-04 02:30:00 1601739900 +2020-10-04 03:00:00 2020-10-04 02:30:00 1601740800 +2020-10-04 03:15:00 2020-10-04 02:30:00 1601741700 +2020-10-04 03:30:00 2020-10-04 02:30:00 1601742600 +2020-10-04 03:45:00 2020-10-04 02:30:00 1601743500 +2020-10-04 04:00:00 2020-10-04 04:00:00 1601744400 +2020-10-04 04:15:00 2020-10-04 04:00:00 1601745300 +2020-10-04 04:30:00 2020-10-04 04:00:00 1601746200 +2020-10-04 04:45:00 2020-10-04 04:00:00 1601747100 +2020-10-04 05:00:00 2020-10-04 04:00:00 1601748000 +2020-10-04 05:15:00 2020-10-04 04:00:00 1601748900 diff --git a/tests/queries/0_stateless/01772_to_start_of_hour_align.sql b/tests/queries/0_stateless/01772_to_start_of_hour_align.sql new file mode 100644 index 00000000000..6d1bb460f90 --- /dev/null +++ b/tests/queries/0_stateless/01772_to_start_of_hour_align.sql @@ -0,0 +1,21 @@ +-- Rounding down to hour intervals is aligned to midnight even if the interval length does not divide the whole day. +SELECT toStartOfInterval(toDateTime('2021-03-23 03:58:00'), INTERVAL 11 HOUR); +SELECT toStartOfInterval(toDateTime('2021-03-23 13:58:00'), INTERVAL 11 HOUR); +SELECT toStartOfInterval(toDateTime('2021-03-23 23:58:00'), INTERVAL 11 HOUR); + +-- It should work correctly even in timezones with non-whole hours offset. India have +05:30. +SELECT toStartOfHour(toDateTime('2021-03-23 13:58:00', 'Asia/Kolkata')); +SELECT toStartOfInterval(toDateTime('2021-03-23 13:58:00', 'Asia/Kolkata'), INTERVAL 6 HOUR); + +-- Specifying the interval longer than 24 hours is not correct, but it works as expected by just rounding to midnight. +SELECT toStartOfInterval(toDateTime('2021-03-23 13:58:00', 'Asia/Kolkata'), INTERVAL 66 HOUR); + +-- In case of timezone shifts, rounding is performed to the hour number on "wall clock" time. +-- The intervals may become shorter or longer due to time shifts. For example, the three hour interval may actually last two hours. +-- If the same hour number on "wall clock" time correspond to multiple time points due to shifting backwards, the unspecified time point is selected among the candidates. +SELECT toDateTime('2010-03-28 00:00:00', 'Europe/Moscow') + INTERVAL 15 * number MINUTE AS src, toStartOfInterval(src, INTERVAL 2 HOUR) AS rounded, toUnixTimestamp(src) AS t FROM numbers(20); +SELECT toDateTime('2010-10-31 00:00:00', 'Europe/Moscow') + INTERVAL 15 * number MINUTE AS src, toStartOfInterval(src, INTERVAL 2 HOUR) AS rounded, toUnixTimestamp(src) AS t FROM numbers(20); + +-- And this should work even for non whole number of hours shifts. +SELECT toDateTime('2020-04-05 00:00:00', 'Australia/Lord_Howe') + INTERVAL 15 * number MINUTE AS src, toStartOfInterval(src, INTERVAL 2 HOUR) AS rounded, toUnixTimestamp(src) AS t FROM numbers(20); +SELECT toDateTime('2020-10-04 00:00:00', 'Australia/Lord_Howe') + INTERVAL 15 * number MINUTE AS src, toStartOfInterval(src, INTERVAL 2 HOUR) AS rounded, toUnixTimestamp(src) AS t FROM numbers(20);