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,18 +251,23 @@ private:
} }
template <typename T, typename Divisor> 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>); static_assert(std::is_integral_v<T> && std::is_integral_v<Divisor>);
assert(divisor > 0); assert(divisor > 0);
if (likely(x >= 0)) if (likely(offset_is_whole_number_of_hours_during_epoch))
return x / divisor * divisor; {
if (likely(x >= 0))
return x / divisor * divisor;
/// Integer division for negative numbers rounds them towards zero (up). /// Integer division for negative numbers rounds them towards zero (up).
/// We will shift the number so it will be rounded towards -inf (down). /// 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: public:
@ -459,7 +464,21 @@ public:
inline unsigned toSecond(Time t) const 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)) if (likely(res >= 0))
return res; return res;
return res + 60; return res + 60;
@ -486,26 +505,8 @@ public:
inline Time toStartOfMinute(Time t) const { return roundDown(t, 60); } inline Time toStartOfMinute(Time t) const { return roundDown(t, 60); }
inline Time toStartOfFiveMinute(Time t) const { return roundDown(t, 300); } inline Time toStartOfFiveMinute(Time t) const { return roundDown(t, 300); }
inline Time toStartOfFifteenMinutes(Time t) const { return roundDown(t, 900); } inline Time toStartOfFifteenMinutes(Time t) const { return roundDown(t, 900); }
inline Time toStartOfTenMinutes(Time t) const { return roundDown(t, 600); }
inline Time toStartOfTenMinutes(Time t) const inline Time toStartOfHour(Time t) const { return roundDown(t, 3600); }
{
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;
}
/** Number of calendar day since the beginning of UNIX epoch (1970-01-01 is zero) /** 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. * 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;