mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-11 00:42:29 +00:00
3bd180c416
fix toMinute function to handle special timezone
982 lines
33 KiB
C++
982 lines
33 KiB
C++
#pragma once
|
|
|
|
#include "DayNum.h"
|
|
#include "defines.h"
|
|
#include "types.h"
|
|
|
|
#include <ctime>
|
|
#include <string>
|
|
|
|
|
|
#define DATE_LUT_MAX (0xFFFFFFFFU - 86400)
|
|
#define DATE_LUT_MAX_DAY_NUM (0xFFFFFFFFU / 86400)
|
|
/// Table size is bigger than DATE_LUT_MAX_DAY_NUM to fill all indices within UInt16 range: this allows to remove extra check.
|
|
#define DATE_LUT_SIZE 0x10000
|
|
#define DATE_LUT_MIN_YEAR 1970
|
|
#define DATE_LUT_MAX_YEAR 2106 /// Last supported year (incomplete)
|
|
#define DATE_LUT_YEARS (1 + DATE_LUT_MAX_YEAR - DATE_LUT_MIN_YEAR) /// Number of years in lookup table
|
|
|
|
#if defined(__PPC__)
|
|
#if !__clang__
|
|
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
|
#endif
|
|
#endif
|
|
|
|
/// Flags for toYearWeek() function.
|
|
enum class WeekModeFlag : UInt8
|
|
{
|
|
MONDAY_FIRST = 1,
|
|
YEAR = 2,
|
|
FIRST_WEEKDAY = 4,
|
|
NEWYEAR_DAY = 8
|
|
};
|
|
using YearWeek = std::pair<UInt16, UInt8>;
|
|
|
|
/** Lookup table to conversion of time to date, and to month / year / day of week / day of month and so on.
|
|
* First time was implemented for OLAPServer, that needed to do billions of such transformations.
|
|
*/
|
|
class DateLUTImpl
|
|
{
|
|
public:
|
|
explicit DateLUTImpl(const std::string & time_zone);
|
|
|
|
DateLUTImpl(const DateLUTImpl &) = delete;
|
|
DateLUTImpl & operator=(const DateLUTImpl &) = delete;
|
|
DateLUTImpl(const DateLUTImpl &&) = delete;
|
|
DateLUTImpl & operator=(const DateLUTImpl &&) = delete;
|
|
|
|
public:
|
|
/// The order of fields matters for alignment and sizeof.
|
|
struct Values
|
|
{
|
|
/// Least significat 32 bits from time_t at beginning of the day.
|
|
/// If the unix timestamp of beginning of the day is negative (example: 1970-01-01 MSK, where time_t == -10800), then value will overflow.
|
|
/// Change to time_t; change constants above; and recompile the sources if you need to support time after 2105 year.
|
|
UInt32 date;
|
|
|
|
/// Properties of the day.
|
|
UInt16 year;
|
|
UInt8 month;
|
|
UInt8 day_of_month;
|
|
UInt8 day_of_week;
|
|
|
|
/// Total number of days in current month. Actually we can use separate table that is independent of time zone.
|
|
/// But due to alignment, this field is totally zero cost.
|
|
UInt8 days_in_month;
|
|
|
|
/// For days, when offset from UTC was changed due to daylight saving time or permanent change, following values could be non zero.
|
|
Int16 amount_of_offset_change; /// Usually -3600 or 3600, but look at Lord Howe Island.
|
|
UInt32 time_at_offset_change; /// In seconds from beginning of the day.
|
|
};
|
|
|
|
static_assert(sizeof(Values) == 16);
|
|
|
|
private:
|
|
/// Lookup table is indexed by DayNum.
|
|
/// Day nums are the same in all time zones. 1970-01-01 is 0 and so on.
|
|
/// Table is relatively large, so better not to place the object on stack.
|
|
/// In comparison to std::vector, plain array is cheaper by one indirection.
|
|
Values lut[DATE_LUT_SIZE];
|
|
|
|
/// Year number after DATE_LUT_MIN_YEAR -> day num for start of year.
|
|
DayNum years_lut[DATE_LUT_YEARS];
|
|
|
|
/// Year number after DATE_LUT_MIN_YEAR * month number starting at zero -> day num for first day of month
|
|
DayNum years_months_lut[DATE_LUT_YEARS * 12];
|
|
|
|
/// UTC offset at beginning of the Unix epoch. The same as unix timestamp of 1970-01-01 00:00:00 local time.
|
|
time_t offset_at_start_of_epoch;
|
|
bool offset_is_whole_number_of_hours_everytime;
|
|
|
|
/// Time zone name.
|
|
std::string time_zone;
|
|
|
|
|
|
/// We can correctly process only timestamps that less DATE_LUT_MAX (i.e. up to 2105 year inclusively)
|
|
/// We don't care about overflow.
|
|
inline DayNum findIndex(time_t t) const
|
|
{
|
|
/// First guess.
|
|
DayNum guess(t / 86400);
|
|
|
|
/// UTC offset is from -12 to +14 in all known time zones. This requires checking only three indices.
|
|
|
|
if ((guess == 0 || t >= lut[guess].date) && t < lut[DayNum(guess + 1)].date)
|
|
return guess;
|
|
|
|
/// Time zones that have offset 0 from UTC do daylight saving time change (if any) towards increasing UTC offset (example: British Standard Time).
|
|
if (t >= lut[DayNum(guess + 1)].date)
|
|
return DayNum(guess + 1);
|
|
|
|
return DayNum(guess - 1);
|
|
}
|
|
|
|
inline const Values & find(time_t t) const
|
|
{
|
|
return lut[findIndex(t)];
|
|
}
|
|
|
|
public:
|
|
const std::string & getTimeZone() const { return time_zone; }
|
|
|
|
/// All functions below are thread-safe; arguments are not checked.
|
|
|
|
inline time_t toDate(time_t t) const { return find(t).date; }
|
|
inline unsigned toMonth(time_t t) const { return find(t).month; }
|
|
inline unsigned toQuarter(time_t t) const { return (find(t).month - 1) / 3 + 1; }
|
|
inline unsigned toYear(time_t t) const { return find(t).year; }
|
|
inline unsigned toDayOfWeek(time_t t) const { return find(t).day_of_week; }
|
|
inline unsigned toDayOfMonth(time_t t) const { return find(t).day_of_month; }
|
|
|
|
/// Round down to start of monday.
|
|
inline time_t toFirstDayOfWeek(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
return lut[DayNum(index - (lut[index].day_of_week - 1))].date;
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfWeek(DayNum d) const
|
|
{
|
|
return DayNum(d - (lut[d].day_of_week - 1));
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfWeek(time_t t) const
|
|
{
|
|
return toFirstDayNumOfWeek(toDayNum(t));
|
|
}
|
|
|
|
/// Round down to start of month.
|
|
inline time_t toFirstDayOfMonth(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
return lut[index - (lut[index].day_of_month - 1)].date;
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfMonth(DayNum d) const
|
|
{
|
|
return DayNum(d - (lut[d].day_of_month - 1));
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfMonth(time_t t) const
|
|
{
|
|
return toFirstDayNumOfMonth(toDayNum(t));
|
|
}
|
|
|
|
/// Round down to start of quarter.
|
|
inline DayNum toFirstDayNumOfQuarter(DayNum d) const
|
|
{
|
|
DayNum index = d;
|
|
size_t month_inside_quarter = (lut[index].month - 1) % 3;
|
|
|
|
index -= lut[index].day_of_month;
|
|
while (month_inside_quarter)
|
|
{
|
|
index -= lut[index].day_of_month;
|
|
--month_inside_quarter;
|
|
}
|
|
|
|
return DayNum(index + 1);
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfQuarter(time_t t) const
|
|
{
|
|
return toFirstDayNumOfQuarter(toDayNum(t));
|
|
}
|
|
|
|
inline time_t toFirstDayOfQuarter(time_t t) const
|
|
{
|
|
return fromDayNum(toFirstDayNumOfQuarter(t));
|
|
}
|
|
|
|
/// Round down to start of year.
|
|
inline time_t toFirstDayOfYear(time_t t) const
|
|
{
|
|
return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date;
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfYear(DayNum d) const
|
|
{
|
|
return years_lut[lut[d].year - DATE_LUT_MIN_YEAR];
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfYear(time_t t) const
|
|
{
|
|
return toFirstDayNumOfYear(toDayNum(t));
|
|
}
|
|
|
|
inline time_t toFirstDayOfNextMonth(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
index += 32 - lut[index].day_of_month;
|
|
return lut[index - (lut[index].day_of_month - 1)].date;
|
|
}
|
|
|
|
inline time_t toFirstDayOfPrevMonth(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
index -= lut[index].day_of_month;
|
|
return lut[index - (lut[index].day_of_month - 1)].date;
|
|
}
|
|
|
|
inline UInt8 daysInMonth(DayNum d) const
|
|
{
|
|
return lut[d].days_in_month;
|
|
}
|
|
|
|
inline UInt8 daysInMonth(time_t t) const
|
|
{
|
|
return find(t).days_in_month;
|
|
}
|
|
|
|
inline UInt8 daysInMonth(UInt16 year, UInt8 month) const
|
|
{
|
|
UInt16 idx = year - DATE_LUT_MIN_YEAR;
|
|
if (unlikely(idx >= DATE_LUT_YEARS))
|
|
return 31; /// Implementation specific behaviour on overflow.
|
|
|
|
/// 32 makes arithmetic more simple.
|
|
DayNum any_day_of_month = DayNum(years_lut[idx] + 32 * (month - 1));
|
|
return lut[any_day_of_month].days_in_month;
|
|
}
|
|
|
|
/** Round to start of day, then shift for specified amount of days.
|
|
*/
|
|
inline time_t toDateAndShift(time_t t, Int32 days) const
|
|
{
|
|
return lut[DayNum(findIndex(t) + days)].date;
|
|
}
|
|
|
|
inline time_t toTime(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
|
|
if (unlikely(index == 0 || index > DATE_LUT_MAX_DAY_NUM))
|
|
return t + offset_at_start_of_epoch;
|
|
|
|
time_t res = t - lut[index].date;
|
|
|
|
if (res >= lut[index].time_at_offset_change)
|
|
res += lut[index].amount_of_offset_change;
|
|
|
|
return res - offset_at_start_of_epoch; /// Starting at 1970-01-01 00:00:00 local time.
|
|
}
|
|
|
|
inline unsigned toHour(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
|
|
/// If it is overflow case,
|
|
/// then limit number of hours to avoid insane results like 1970-01-01 89:28:15
|
|
if (unlikely(index == 0 || index > DATE_LUT_MAX_DAY_NUM))
|
|
return static_cast<unsigned>((t + offset_at_start_of_epoch) / 3600) % 24;
|
|
|
|
time_t time = t - lut[index].date;
|
|
|
|
if (time >= lut[index].time_at_offset_change)
|
|
time += lut[index].amount_of_offset_change;
|
|
|
|
unsigned res = time / 3600;
|
|
return res <= 23 ? res : 0;
|
|
}
|
|
|
|
/** Calculating offset from UTC in seconds.
|
|
* which means Using the same literal time of "t" to get the corresponding timestamp in UTC,
|
|
* then subtract the former from the latter to get the offset result.
|
|
* The boundaries when meets DST(daylight saving time) change should be handled very carefully.
|
|
*/
|
|
inline time_t timezoneOffset(time_t t) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
|
|
/// Calculate daylight saving offset first.
|
|
/// Because the "amount_of_offset_change" in LUT entry only exists in the change day, it's costly to scan it from the very begin.
|
|
/// but we can figure out all the accumulated offsets from 1970-01-01 to that day just by get the whole difference between lut[].date,
|
|
/// and then, we can directly subtract multiple 86400s to get the real DST offsets for the leap seconds is not considered now.
|
|
time_t res = (lut[index].date - lut[0].date) % 86400;
|
|
/// As so far to know, the maximal DST offset couldn't be more than 2 hours, so after the modulo operation the remainder
|
|
/// will sits between [-offset --> 0 --> offset] which respectively corresponds to moving clock forward or backward.
|
|
res = res > 43200 ? (86400 - res) : (0 - res);
|
|
|
|
/// Check if has a offset change during this day. Add the change when cross the line
|
|
if (lut[index].amount_of_offset_change != 0 && t >= lut[index].date + lut[index].time_at_offset_change)
|
|
res += lut[index].amount_of_offset_change;
|
|
|
|
return res + offset_at_start_of_epoch;
|
|
}
|
|
|
|
/** Only for time zones with/when offset from UTC is multiple of five minutes.
|
|
* This is true for all time zones: right now, all time zones have an offset that is multiple of 15 minutes.
|
|
*
|
|
* "By 1929, most major countries had adopted hourly time zones. Nepal was the last
|
|
* country to adopt a standard offset, shifting slightly to UTC+5:45 in 1986."
|
|
* - https://en.wikipedia.org/wiki/Time_zone#Offsets_from_UTC
|
|
*
|
|
* Also please note, that unix timestamp doesn't count "leap seconds":
|
|
* each minute, with added or subtracted leap second, spans exactly 60 unix timestamps.
|
|
*/
|
|
|
|
inline unsigned toSecond(time_t t) const { return UInt32(t) % 60; }
|
|
|
|
inline unsigned toMinute(time_t t) const
|
|
{
|
|
if (offset_is_whole_number_of_hours_everytime)
|
|
return (UInt32(t) / 60) % 60;
|
|
|
|
/// To consider the DST changing situation within this day.
|
|
/// also make the special timezones with no whole hour offset such as 'Australia/Lord_Howe' been taken into account
|
|
DayNum index = findIndex(t);
|
|
UInt32 res = t - lut[index].date;
|
|
if (lut[index].amount_of_offset_change != 0 && t >= lut[index].date + lut[index].time_at_offset_change)
|
|
res += lut[index].amount_of_offset_change;
|
|
|
|
return res / 60 % 60;
|
|
}
|
|
|
|
inline time_t toStartOfMinute(time_t t) const { return t / 60 * 60; }
|
|
inline time_t toStartOfFiveMinute(time_t t) const { return t / 300 * 300; }
|
|
inline time_t toStartOfFifteenMinutes(time_t t) const { return t / 900 * 900; }
|
|
inline time_t toStartOfTenMinutes(time_t t) const { return t / 600 * 600; }
|
|
|
|
inline time_t toStartOfHour(time_t t) const
|
|
{
|
|
if (offset_is_whole_number_of_hours_everytime)
|
|
return t / 3600 * 3600;
|
|
|
|
UInt32 date = find(t).date;
|
|
return date + (UInt32(t) - date) / 3600 * 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.
|
|
*
|
|
* This is "calendar" day, it itself is independent of time zone
|
|
* (conversion from/to unix timestamp will depend on time zone,
|
|
* because the same calendar day starts/ends at different timestamps in different time zones)
|
|
*/
|
|
|
|
inline DayNum toDayNum(time_t t) const { return findIndex(t); }
|
|
inline time_t fromDayNum(DayNum d) const { return lut[d].date; }
|
|
|
|
inline time_t toDate(DayNum d) const { return lut[d].date; }
|
|
inline unsigned toMonth(DayNum d) const { return lut[d].month; }
|
|
inline unsigned toQuarter(DayNum d) const { return (lut[d].month - 1) / 3 + 1; }
|
|
inline unsigned toYear(DayNum d) const { return lut[d].year; }
|
|
inline unsigned toDayOfWeek(DayNum d) const { return lut[d].day_of_week; }
|
|
inline unsigned toDayOfMonth(DayNum d) const { return lut[d].day_of_month; }
|
|
inline unsigned toDayOfYear(DayNum d) const { return d + 1 - toFirstDayNumOfYear(d); }
|
|
|
|
inline unsigned toDayOfYear(time_t t) const { return toDayOfYear(toDayNum(t)); }
|
|
|
|
/// Number of week from some fixed moment in the past. Week begins at monday.
|
|
/// (round down to monday and divide DayNum by 7; we made an assumption,
|
|
/// that in domain of the function there was no weeks with any other number of days than 7)
|
|
inline unsigned toRelativeWeekNum(DayNum d) const
|
|
{
|
|
/// We add 8 to avoid underflow at beginning of unix epoch.
|
|
return (d + 8 - toDayOfWeek(d)) / 7;
|
|
}
|
|
|
|
inline unsigned toRelativeWeekNum(time_t t) const
|
|
{
|
|
return toRelativeWeekNum(toDayNum(t));
|
|
}
|
|
|
|
/// Get year that contains most of the current week. Week begins at monday.
|
|
inline unsigned toISOYear(DayNum d) const
|
|
{
|
|
/// That's effectively the year of thursday of current week.
|
|
return toYear(DayNum(d + 4 - toDayOfWeek(d)));
|
|
}
|
|
|
|
inline unsigned toISOYear(time_t t) const
|
|
{
|
|
return toISOYear(toDayNum(t));
|
|
}
|
|
|
|
/// ISO year begins with a monday of the week that is contained more than by half in the corresponding calendar year.
|
|
/// Example: ISO year 2019 begins at 2018-12-31. And ISO year 2017 begins at 2017-01-02.
|
|
/// https://en.wikipedia.org/wiki/ISO_week_date
|
|
inline DayNum toFirstDayNumOfISOYear(DayNum d) const
|
|
{
|
|
auto iso_year = toISOYear(d);
|
|
|
|
DayNum first_day_of_year = years_lut[iso_year - DATE_LUT_MIN_YEAR];
|
|
auto first_day_of_week_of_year = lut[first_day_of_year].day_of_week;
|
|
|
|
return DayNum(first_day_of_week_of_year <= 4
|
|
? first_day_of_year + 1 - first_day_of_week_of_year
|
|
: first_day_of_year + 8 - first_day_of_week_of_year);
|
|
}
|
|
|
|
inline DayNum toFirstDayNumOfISOYear(time_t t) const
|
|
{
|
|
return toFirstDayNumOfISOYear(toDayNum(t));
|
|
}
|
|
|
|
inline time_t toFirstDayOfISOYear(time_t t) const
|
|
{
|
|
return fromDayNum(toFirstDayNumOfISOYear(t));
|
|
}
|
|
|
|
/// ISO 8601 week number. Week begins at monday.
|
|
/// The week number 1 is the first week in year that contains 4 or more days (that's more than half).
|
|
inline unsigned toISOWeek(DayNum d) const
|
|
{
|
|
return 1 + DayNum(toFirstDayNumOfWeek(d) - toFirstDayNumOfISOYear(d)) / 7;
|
|
}
|
|
|
|
inline unsigned toISOWeek(time_t t) const
|
|
{
|
|
return toISOWeek(toDayNum(t));
|
|
}
|
|
|
|
/*
|
|
The bits in week_mode has the following meaning:
|
|
WeekModeFlag::MONDAY_FIRST (0) If not set Sunday is first day of week
|
|
If set Monday is first day of week
|
|
WeekModeFlag::YEAR (1) If not set Week is in range 0-53
|
|
|
|
Week 0 is returned for the the last week of the previous year (for
|
|
a date at start of january) In this case one can get 53 for the
|
|
first week of next year. This flag ensures that the week is
|
|
relevant for the given year. Note that this flag is only
|
|
relevant if WeekModeFlag::JANUARY is not set.
|
|
|
|
If set Week is in range 1-53.
|
|
|
|
In this case one may get week 53 for a date in January (when
|
|
the week is that last week of previous year) and week 1 for a
|
|
date in December.
|
|
|
|
WeekModeFlag::FIRST_WEEKDAY (2) If not set Weeks are numbered according
|
|
to ISO 8601:1988
|
|
If set The week that contains the first
|
|
'first-day-of-week' is week 1.
|
|
|
|
WeekModeFlag::NEWYEAR_DAY (3) If not set no meaning
|
|
If set The week that contains the January 1 is week 1.
|
|
Week is in range 1-53.
|
|
And ignore WeekModeFlag::YEAR, WeekModeFlag::FIRST_WEEKDAY
|
|
|
|
ISO 8601:1988 means that if the week containing January 1 has
|
|
four or more days in the new year, then it is week 1;
|
|
Otherwise it is the last week of the previous year, and the
|
|
next week is week 1.
|
|
*/
|
|
inline YearWeek toYearWeek(DayNum d, UInt8 week_mode) const
|
|
{
|
|
bool newyear_day_mode = week_mode & static_cast<UInt8>(WeekModeFlag::NEWYEAR_DAY);
|
|
week_mode = check_week_mode(week_mode);
|
|
bool monday_first_mode = week_mode & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST);
|
|
bool week_year_mode = week_mode & static_cast<UInt8>(WeekModeFlag::YEAR);
|
|
bool first_weekday_mode = week_mode & static_cast<UInt8>(WeekModeFlag::FIRST_WEEKDAY);
|
|
|
|
// Calculate week number of WeekModeFlag::NEWYEAR_DAY mode
|
|
if (newyear_day_mode)
|
|
{
|
|
return toYearWeekOfNewyearMode(d, monday_first_mode);
|
|
}
|
|
|
|
YearWeek yw(toYear(d), 0);
|
|
UInt16 days = 0;
|
|
UInt16 daynr = makeDayNum(yw.first, toMonth(d), toDayOfMonth(d));
|
|
UInt16 first_daynr = makeDayNum(yw.first, 1, 1);
|
|
|
|
// 0 for monday, 1 for tuesday ...
|
|
// get weekday from first day in year.
|
|
UInt16 weekday = calc_weekday(DayNum(first_daynr), !monday_first_mode);
|
|
|
|
if (toMonth(d) == 1 && toDayOfMonth(d) <= static_cast<UInt32>(7 - weekday))
|
|
{
|
|
if (!week_year_mode && ((first_weekday_mode && weekday != 0) || (!first_weekday_mode && weekday >= 4)))
|
|
return yw;
|
|
week_year_mode = 1;
|
|
(yw.first)--;
|
|
first_daynr -= (days = calc_days_in_year(yw.first));
|
|
weekday = (weekday + 53 * 7 - days) % 7;
|
|
}
|
|
|
|
if ((first_weekday_mode && weekday != 0) || (!first_weekday_mode && weekday >= 4))
|
|
days = daynr - (first_daynr + (7 - weekday));
|
|
else
|
|
days = daynr - (first_daynr - weekday);
|
|
|
|
if (week_year_mode && days >= 52 * 7)
|
|
{
|
|
weekday = (weekday + calc_days_in_year(yw.first)) % 7;
|
|
if ((!first_weekday_mode && weekday < 4) || (first_weekday_mode && weekday == 0))
|
|
{
|
|
(yw.first)++;
|
|
yw.second = 1;
|
|
return yw;
|
|
}
|
|
}
|
|
yw.second = days / 7 + 1;
|
|
return yw;
|
|
}
|
|
|
|
/// Calculate week number of WeekModeFlag::NEWYEAR_DAY mode
|
|
/// The week number 1 is the first week in year that contains January 1,
|
|
inline YearWeek toYearWeekOfNewyearMode(DayNum d, bool monday_first_mode) const
|
|
{
|
|
YearWeek yw(0, 0);
|
|
UInt16 offset_day = monday_first_mode ? 0U : 1U;
|
|
|
|
// Checking the week across the year
|
|
yw.first = toYear(DayNum(d + 7 - toDayOfWeek(DayNum(d + offset_day))));
|
|
|
|
DayNum first_day = makeDayNum(yw.first, 1, 1);
|
|
DayNum this_day = d;
|
|
|
|
if (monday_first_mode)
|
|
{
|
|
// Rounds down a date to the nearest Monday.
|
|
first_day = toFirstDayNumOfWeek(first_day);
|
|
this_day = toFirstDayNumOfWeek(d);
|
|
}
|
|
else
|
|
{
|
|
// Rounds down a date to the nearest Sunday.
|
|
if (toDayOfWeek(first_day) != 7)
|
|
first_day = DayNum(first_day - toDayOfWeek(first_day));
|
|
if (toDayOfWeek(d) != 7)
|
|
this_day = DayNum(d - toDayOfWeek(d));
|
|
}
|
|
yw.second = (this_day - first_day) / 7 + 1;
|
|
return yw;
|
|
}
|
|
|
|
/**
|
|
* get first day of week with week_mode, return Sunday or Monday
|
|
*/
|
|
inline DayNum toFirstDayNumOfWeek(DayNum d, UInt8 week_mode) const
|
|
{
|
|
bool monday_first_mode = week_mode & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST);
|
|
if (monday_first_mode)
|
|
{
|
|
return toFirstDayNumOfWeek(d);
|
|
}
|
|
else
|
|
{
|
|
return (toDayOfWeek(d) != 7) ? DayNum(d - toDayOfWeek(d)) : d;
|
|
}
|
|
}
|
|
|
|
/// Check and change mode to effective.
|
|
inline UInt8 check_week_mode(UInt8 mode) const
|
|
{
|
|
UInt8 week_format = (mode & 7);
|
|
if (!(week_format & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST)))
|
|
week_format ^= static_cast<UInt8>(WeekModeFlag::FIRST_WEEKDAY);
|
|
return week_format;
|
|
}
|
|
|
|
/** Calculate weekday from d.
|
|
* Returns 0 for monday, 1 for tuesday...
|
|
*/
|
|
inline unsigned calc_weekday(DayNum d, bool sunday_first_day_of_week) const
|
|
{
|
|
if (!sunday_first_day_of_week)
|
|
return toDayOfWeek(d) - 1;
|
|
else
|
|
return toDayOfWeek(DayNum(d + 1)) - 1;
|
|
}
|
|
|
|
/// Calculate days in one year.
|
|
inline unsigned calc_days_in_year(UInt16 year) const
|
|
{
|
|
return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)) ? 366 : 365);
|
|
}
|
|
|
|
/// Number of month from some fixed moment in the past (year * 12 + month)
|
|
inline unsigned toRelativeMonthNum(DayNum d) const
|
|
{
|
|
return lut[d].year * 12 + lut[d].month;
|
|
}
|
|
|
|
inline unsigned toRelativeMonthNum(time_t t) const
|
|
{
|
|
return toRelativeMonthNum(toDayNum(t));
|
|
}
|
|
|
|
inline unsigned toRelativeQuarterNum(DayNum d) const
|
|
{
|
|
return lut[d].year * 4 + (lut[d].month - 1) / 3;
|
|
}
|
|
|
|
inline unsigned toRelativeQuarterNum(time_t t) const
|
|
{
|
|
return toRelativeQuarterNum(toDayNum(t));
|
|
}
|
|
|
|
/// We count all hour-length intervals, unrelated to offset changes.
|
|
inline time_t toRelativeHourNum(time_t t) const
|
|
{
|
|
if (offset_is_whole_number_of_hours_everytime)
|
|
return t / 3600;
|
|
|
|
/// Assume that if offset was fractional, then the fraction is the same as at the beginning of epoch.
|
|
/// NOTE This assumption is false for "Pacific/Pitcairn" and "Pacific/Kiritimati" time zones.
|
|
return (t + 86400 - offset_at_start_of_epoch) / 3600;
|
|
}
|
|
|
|
inline time_t toRelativeHourNum(DayNum d) const
|
|
{
|
|
return toRelativeHourNum(lut[d].date);
|
|
}
|
|
|
|
inline time_t toRelativeMinuteNum(time_t t) const
|
|
{
|
|
return t / 60;
|
|
}
|
|
|
|
inline time_t toRelativeMinuteNum(DayNum d) const
|
|
{
|
|
return toRelativeMinuteNum(lut[d].date);
|
|
}
|
|
|
|
inline DayNum toStartOfYearInterval(DayNum d, UInt64 years) const
|
|
{
|
|
if (years == 1)
|
|
return toFirstDayNumOfYear(d);
|
|
return years_lut[(lut[d].year - DATE_LUT_MIN_YEAR) / years * years];
|
|
}
|
|
|
|
inline DayNum toStartOfQuarterInterval(DayNum d, UInt64 quarters) const
|
|
{
|
|
if (quarters == 1)
|
|
return toFirstDayNumOfQuarter(d);
|
|
return toStartOfMonthInterval(d, quarters * 3);
|
|
}
|
|
|
|
inline DayNum toStartOfMonthInterval(DayNum d, UInt64 months) const
|
|
{
|
|
if (months == 1)
|
|
return toFirstDayNumOfMonth(d);
|
|
const auto & date = lut[d];
|
|
UInt32 month_total_index = (date.year - DATE_LUT_MIN_YEAR) * 12 + date.month - 1;
|
|
return years_months_lut[month_total_index / months * months];
|
|
}
|
|
|
|
inline DayNum toStartOfWeekInterval(DayNum d, UInt64 weeks) const
|
|
{
|
|
if (weeks == 1)
|
|
return toFirstDayNumOfWeek(d);
|
|
UInt64 days = weeks * 7;
|
|
// January 1st 1970 was Thursday so we need this 4-days offset to make weeks start on Monday.
|
|
return DayNum(4 + (d - 4) / days * days);
|
|
}
|
|
|
|
inline time_t toStartOfDayInterval(DayNum d, UInt64 days) const
|
|
{
|
|
if (days == 1)
|
|
return toDate(d);
|
|
return lut[d / days * days].date;
|
|
}
|
|
|
|
inline time_t toStartOfHourInterval(time_t t, UInt64 hours) const
|
|
{
|
|
if (hours == 1)
|
|
return toStartOfHour(t);
|
|
UInt64 seconds = hours * 3600;
|
|
t = t / seconds * seconds;
|
|
if (offset_is_whole_number_of_hours_everytime)
|
|
return t;
|
|
return toStartOfHour(t);
|
|
}
|
|
|
|
inline time_t toStartOfMinuteInterval(time_t t, UInt64 minutes) const
|
|
{
|
|
if (minutes == 1)
|
|
return toStartOfMinute(t);
|
|
UInt64 seconds = 60 * minutes;
|
|
return t / seconds * seconds;
|
|
}
|
|
|
|
inline time_t toStartOfSecondInterval(time_t t, UInt64 seconds) const
|
|
{
|
|
if (seconds == 1)
|
|
return t;
|
|
return t / seconds * seconds;
|
|
}
|
|
|
|
/// Create DayNum from year, month, day of month.
|
|
inline DayNum makeDayNum(UInt16 year, UInt8 month, UInt8 day_of_month) const
|
|
{
|
|
if (unlikely(year < DATE_LUT_MIN_YEAR || year > DATE_LUT_MAX_YEAR || month < 1 || month > 12 || day_of_month < 1 || day_of_month > 31))
|
|
return DayNum(0); // TODO (nemkov, DateTime64 phase 2): implement creating real date for year outside of LUT range.
|
|
|
|
// The day after 2106-02-07 will not stored fully as struct Values, so just overflow it as 0
|
|
if (unlikely(year == DATE_LUT_MAX_YEAR && (month > 2 || (month == 2 && day_of_month > 7))))
|
|
return DayNum(0);
|
|
|
|
return DayNum(years_months_lut[(year - DATE_LUT_MIN_YEAR) * 12 + month - 1] + day_of_month - 1);
|
|
}
|
|
|
|
inline time_t makeDate(UInt16 year, UInt8 month, UInt8 day_of_month) const
|
|
{
|
|
return lut[makeDayNum(year, month, day_of_month)].date;
|
|
}
|
|
|
|
/** Does not accept daylight saving time as argument: in case of ambiguity, it choose greater timestamp.
|
|
*/
|
|
inline time_t makeDateTime(UInt16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const
|
|
{
|
|
size_t index = makeDayNum(year, month, day_of_month);
|
|
UInt32 time_offset = hour * 3600 + minute * 60 + second;
|
|
|
|
if (time_offset >= lut[index].time_at_offset_change)
|
|
time_offset -= lut[index].amount_of_offset_change;
|
|
|
|
UInt32 res = lut[index].date + time_offset;
|
|
|
|
if (unlikely(res > DATE_LUT_MAX))
|
|
return 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
inline const Values & getValues(DayNum d) const { return lut[d]; }
|
|
inline const Values & getValues(time_t t) const { return lut[findIndex(t)]; }
|
|
|
|
inline UInt32 toNumYYYYMM(time_t t) const
|
|
{
|
|
const Values & values = find(t);
|
|
return values.year * 100 + values.month;
|
|
}
|
|
|
|
inline UInt32 toNumYYYYMM(DayNum d) const
|
|
{
|
|
const Values & values = lut[d];
|
|
return values.year * 100 + values.month;
|
|
}
|
|
|
|
inline UInt32 toNumYYYYMMDD(time_t t) const
|
|
{
|
|
const Values & values = find(t);
|
|
return values.year * 10000 + values.month * 100 + values.day_of_month;
|
|
}
|
|
|
|
inline UInt32 toNumYYYYMMDD(DayNum d) const
|
|
{
|
|
const Values & values = lut[d];
|
|
return values.year * 10000 + values.month * 100 + values.day_of_month;
|
|
}
|
|
|
|
inline time_t YYYYMMDDToDate(UInt32 num) const
|
|
{
|
|
return makeDate(num / 10000, num / 100 % 100, num % 100);
|
|
}
|
|
|
|
inline DayNum YYYYMMDDToDayNum(UInt32 num) const
|
|
{
|
|
return makeDayNum(num / 10000, num / 100 % 100, num % 100);
|
|
}
|
|
|
|
|
|
inline UInt64 toNumYYYYMMDDhhmmss(time_t t) const
|
|
{
|
|
const Values & values = find(t);
|
|
return
|
|
toSecond(t)
|
|
+ toMinute(t) * 100
|
|
+ toHour(t) * 10000
|
|
+ UInt64(values.day_of_month) * 1000000
|
|
+ UInt64(values.month) * 100000000
|
|
+ UInt64(values.year) * 10000000000;
|
|
}
|
|
|
|
inline time_t YYYYMMDDhhmmssToTime(UInt64 num) const
|
|
{
|
|
return makeDateTime(
|
|
num / 10000000000,
|
|
num / 100000000 % 100,
|
|
num / 1000000 % 100,
|
|
num / 10000 % 100,
|
|
num / 100 % 100,
|
|
num % 100);
|
|
}
|
|
|
|
/// Adding calendar intervals.
|
|
/// Implementation specific behaviour when delta is too big.
|
|
|
|
inline NO_SANITIZE_UNDEFINED time_t addDays(time_t t, Int64 delta) const
|
|
{
|
|
DayNum index = findIndex(t);
|
|
time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
|
|
|
|
index += delta;
|
|
|
|
if (time_offset >= lut[index].time_at_offset_change)
|
|
time_offset -= lut[index].amount_of_offset_change;
|
|
|
|
return lut[index].date + time_offset;
|
|
}
|
|
|
|
inline NO_SANITIZE_UNDEFINED time_t addWeeks(time_t t, Int64 delta) const
|
|
{
|
|
return addDays(t, delta * 7);
|
|
}
|
|
|
|
inline UInt8 saturateDayOfMonth(UInt16 year, UInt8 month, UInt8 day_of_month) const
|
|
{
|
|
if (likely(day_of_month <= 28))
|
|
return day_of_month;
|
|
|
|
UInt8 days_in_month = daysInMonth(year, month);
|
|
|
|
if (day_of_month > days_in_month)
|
|
day_of_month = days_in_month;
|
|
|
|
return day_of_month;
|
|
}
|
|
|
|
/// If resulting month has less deys than source month, then saturation can happen.
|
|
/// Example: 31 Aug + 1 month = 30 Sep.
|
|
inline time_t addMonths(time_t t, Int64 delta) const
|
|
{
|
|
DayNum result_day = addMonths(toDayNum(t), delta);
|
|
|
|
time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
|
|
|
|
if (time_offset >= lut[result_day].time_at_offset_change)
|
|
time_offset -= lut[result_day].amount_of_offset_change;
|
|
|
|
return lut[result_day].date + time_offset;
|
|
}
|
|
|
|
inline NO_SANITIZE_UNDEFINED DayNum addMonths(DayNum d, Int64 delta) const
|
|
{
|
|
const Values & values = lut[d];
|
|
|
|
Int64 month = static_cast<Int64>(values.month) + delta;
|
|
|
|
if (month > 0)
|
|
{
|
|
auto year = values.year + (month - 1) / 12;
|
|
month = ((month - 1) % 12) + 1;
|
|
auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);
|
|
|
|
return makeDayNum(year, month, day_of_month);
|
|
}
|
|
else
|
|
{
|
|
auto year = values.year - (12 - month) / 12;
|
|
month = 12 - (-month % 12);
|
|
auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);
|
|
|
|
return makeDayNum(year, month, day_of_month);
|
|
}
|
|
}
|
|
|
|
inline NO_SANITIZE_UNDEFINED time_t addQuarters(time_t t, Int64 delta) const
|
|
{
|
|
return addMonths(t, delta * 3);
|
|
}
|
|
|
|
inline NO_SANITIZE_UNDEFINED DayNum addQuarters(DayNum d, Int64 delta) const
|
|
{
|
|
return addMonths(d, delta * 3);
|
|
}
|
|
|
|
/// Saturation can occur if 29 Feb is mapped to non-leap year.
|
|
inline NO_SANITIZE_UNDEFINED time_t addYears(time_t t, Int64 delta) const
|
|
{
|
|
DayNum result_day = addYears(toDayNum(t), delta);
|
|
|
|
time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
|
|
|
|
if (time_offset >= lut[result_day].time_at_offset_change)
|
|
time_offset -= lut[result_day].amount_of_offset_change;
|
|
|
|
return lut[result_day].date + time_offset;
|
|
}
|
|
|
|
inline NO_SANITIZE_UNDEFINED DayNum addYears(DayNum d, Int64 delta) const
|
|
{
|
|
const Values & values = lut[d];
|
|
|
|
auto year = values.year + delta;
|
|
auto month = values.month;
|
|
auto day_of_month = values.day_of_month;
|
|
|
|
/// Saturation to 28 Feb can happen.
|
|
if (unlikely(day_of_month == 29 && month == 2))
|
|
day_of_month = saturateDayOfMonth(year, month, day_of_month);
|
|
|
|
return makeDayNum(year, month, day_of_month);
|
|
}
|
|
|
|
|
|
inline std::string timeToString(time_t t) const
|
|
{
|
|
const Values & values = find(t);
|
|
|
|
std::string s {"0000-00-00 00:00:00"};
|
|
|
|
s[0] += values.year / 1000;
|
|
s[1] += (values.year / 100) % 10;
|
|
s[2] += (values.year / 10) % 10;
|
|
s[3] += values.year % 10;
|
|
s[5] += values.month / 10;
|
|
s[6] += values.month % 10;
|
|
s[8] += values.day_of_month / 10;
|
|
s[9] += values.day_of_month % 10;
|
|
|
|
auto hour = toHour(t);
|
|
auto minute = toMinute(t);
|
|
auto second = toSecond(t);
|
|
|
|
s[11] += hour / 10;
|
|
s[12] += hour % 10;
|
|
s[14] += minute / 10;
|
|
s[15] += minute % 10;
|
|
s[17] += second / 10;
|
|
s[18] += second % 10;
|
|
|
|
return s;
|
|
}
|
|
|
|
inline std::string dateToString(time_t t) const
|
|
{
|
|
const Values & values = find(t);
|
|
|
|
std::string s {"0000-00-00"};
|
|
|
|
s[0] += values.year / 1000;
|
|
s[1] += (values.year / 100) % 10;
|
|
s[2] += (values.year / 10) % 10;
|
|
s[3] += values.year % 10;
|
|
s[5] += values.month / 10;
|
|
s[6] += values.month % 10;
|
|
s[8] += values.day_of_month / 10;
|
|
s[9] += values.day_of_month % 10;
|
|
|
|
return s;
|
|
}
|
|
|
|
inline std::string dateToString(DayNum d) const
|
|
{
|
|
const Values & values = lut[d];
|
|
|
|
std::string s {"0000-00-00"};
|
|
|
|
s[0] += values.year / 1000;
|
|
s[1] += (values.year / 100) % 10;
|
|
s[2] += (values.year / 10) % 10;
|
|
s[3] += values.year % 10;
|
|
s[5] += values.month / 10;
|
|
s[6] += values.month % 10;
|
|
s[8] += values.day_of_month / 10;
|
|
s[9] += values.day_of_month % 10;
|
|
|
|
return s;
|
|
}
|
|
};
|
|
|
|
#if defined(__PPC__)
|
|
#if !__clang__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
#endif
|