Improve compatibility with non-whole-minute timezone offsets

This commit is contained in:
Raúl Marín 2021-08-02 13:33:16 +02:00
parent 908505c12e
commit abeaf60e4a
3 changed files with 69 additions and 27 deletions

View File

@ -251,20 +251,25 @@ private:
}
template <typename T, typename Divisor>
static inline T roundDown(T x, Divisor divisor)
inline T roundDown(T x, Divisor divisor) const
{
static_assert(std::is_integral_v<T> && std::is_integral_v<Divisor>);
assert(divisor > 0);
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).
return (x + 1 - divisor) / divisor * divisor;
}
Time date = find(x).date;
return date + (x - date) / divisor * divisor;
}
public:
const std::string & getTimeZone() const { return time_zone; }
@ -458,6 +463,8 @@ public:
inline unsigned toSecond(Time t) const
{
if (offset_is_whole_number_of_hours_during_epoch)
{
auto res = t % 60;
if (likely(res >= 0))
@ -465,6 +472,18 @@ public:
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;
}
inline unsigned toMinute(Time t) const
{
if (t >= 0 && offset_is_whole_number_of_hours_during_epoch)
@ -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.

View File

@ -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

View File

@ -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;