Fix DateLUT on Darwin

This commit is contained in:
Alexey Milovidov 2021-06-29 02:16:19 +03:00
parent a7ee0b91b1
commit f4f85a387d

View File

@ -119,11 +119,13 @@ private:
} }
public: public:
using Time = Int64;
/// The order of fields matters for alignment and sizeof. /// The order of fields matters for alignment and sizeof.
struct Values struct Values
{ {
/// time_t at beginning of the day. /// Time at beginning of the day.
Int64 date; Time date;
/// Properties of the day. /// Properties of the day.
UInt16 year; UInt16 year;
@ -182,20 +184,20 @@ private:
LUTIndex years_months_lut[DATE_LUT_YEARS * 12]; LUTIndex 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. /// 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; Time offset_at_start_of_epoch;
/// UTC offset at the beginning of the first supported year. /// UTC offset at the beginning of the first supported year.
time_t offset_at_start_of_lut; Time offset_at_start_of_lut;
bool offset_is_whole_number_of_hours_during_epoch; bool offset_is_whole_number_of_hours_during_epoch;
/// Time zone name. /// Time zone name.
std::string time_zone; std::string time_zone;
inline LUTIndex findIndex(time_t t) const inline LUTIndex findIndex(Time t) const
{ {
/// First guess. /// First guess.
Int64 guess = (t / 86400) + daynum_offset_epoch; Time guess = (t / 86400) + daynum_offset_epoch;
/// For negative time_t the integer division was rounded up, so the guess is offset by one. /// For negative Time the integer division was rounded up, so the guess is offset by one.
if (unlikely(t < 0)) if (unlikely(t < 0))
--guess; --guess;
@ -227,7 +229,7 @@ private:
return LUTIndex{static_cast<UInt32>(d + daynum_offset_epoch) & date_lut_mask}; return LUTIndex{static_cast<UInt32>(d + daynum_offset_epoch) & date_lut_mask};
} }
inline LUTIndex toLUTIndex(time_t t) const inline LUTIndex toLUTIndex(Time t) const
{ {
return findIndex(t); return findIndex(t);
} }
@ -280,7 +282,7 @@ public:
/// Round down to start of monday. /// Round down to start of monday.
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toFirstDayOfWeek(DateOrTime v) const inline Time toFirstDayOfWeek(DateOrTime v) const
{ {
const LUTIndex i = toLUTIndex(v); const LUTIndex i = toLUTIndex(v);
return lut[i - (lut[i].day_of_week - 1)].date; return lut[i - (lut[i].day_of_week - 1)].date;
@ -295,7 +297,7 @@ public:
/// Round down to start of month. /// Round down to start of month.
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toFirstDayOfMonth(DateOrTime v) const inline Time toFirstDayOfMonth(DateOrTime v) const
{ {
const LUTIndex i = toLUTIndex(v); const LUTIndex i = toLUTIndex(v);
return lut[i - (lut[i].day_of_month - 1)].date; return lut[i - (lut[i].day_of_month - 1)].date;
@ -332,13 +334,13 @@ public:
} }
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toFirstDayOfQuarter(DateOrTime v) const inline Time toFirstDayOfQuarter(DateOrTime v) const
{ {
return toDate(toFirstDayOfQuarterIndex(v)); return toDate(toFirstDayOfQuarterIndex(v));
} }
/// Round down to start of year. /// Round down to start of year.
inline time_t toFirstDayOfYear(time_t t) const inline Time toFirstDayOfYear(Time t) const
{ {
return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date; return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date;
} }
@ -355,14 +357,14 @@ public:
return toDayNum(toFirstDayNumOfYearIndex(v)); return toDayNum(toFirstDayNumOfYearIndex(v));
} }
inline time_t toFirstDayOfNextMonth(time_t t) const inline Time toFirstDayOfNextMonth(Time t) const
{ {
LUTIndex index = findIndex(t); LUTIndex index = findIndex(t);
index += 32 - lut[index].day_of_month; index += 32 - lut[index].day_of_month;
return lut[index - (lut[index].day_of_month - 1)].date; return lut[index - (lut[index].day_of_month - 1)].date;
} }
inline time_t toFirstDayOfPrevMonth(time_t t) const inline Time toFirstDayOfPrevMonth(Time t) const
{ {
LUTIndex index = findIndex(t); LUTIndex index = findIndex(t);
index -= lut[index].day_of_month; index -= lut[index].day_of_month;
@ -389,16 +391,16 @@ public:
/** Round to start of day, then shift for specified amount of days. /** Round to start of day, then shift for specified amount of days.
*/ */
inline time_t toDateAndShift(time_t t, Int32 days) const inline Time toDateAndShift(Time t, Int32 days) const
{ {
return lut[findIndex(t) + days].date; return lut[findIndex(t) + days].date;
} }
inline time_t toTime(time_t t) const inline Time toTime(Time t) const
{ {
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
time_t res = t - lut[index].date; Time res = t - lut[index].date;
if (res >= lut[index].time_at_offset_change()) if (res >= lut[index].time_at_offset_change())
res += lut[index].amount_of_offset_change(); res += lut[index].amount_of_offset_change();
@ -406,11 +408,11 @@ public:
return res - offset_at_start_of_epoch; /// Starting at 1970-01-01 00:00:00 local time. return res - offset_at_start_of_epoch; /// Starting at 1970-01-01 00:00:00 local time.
} }
inline unsigned toHour(time_t t) const inline unsigned toHour(Time t) const
{ {
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
time_t time = t - lut[index].date; Time time = t - lut[index].date;
if (time >= lut[index].time_at_offset_change()) if (time >= lut[index].time_at_offset_change())
time += lut[index].amount_of_offset_change(); time += lut[index].amount_of_offset_change();
@ -426,7 +428,7 @@ public:
* then subtract the former from the latter to get the offset result. * 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. * The boundaries when meets DST(daylight saving time) change should be handled very carefully.
*/ */
inline time_t timezoneOffset(time_t t) const inline Time timezoneOffset(Time t) const
{ {
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
@ -434,7 +436,7 @@ public:
/// 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. /// 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, /// 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. /// 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[daynum_offset_epoch].date) % 86400; Time res = (lut[index].date - lut[daynum_offset_epoch].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 /// 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. /// will sits between [-offset --> 0 --> offset] which respectively corresponds to moving clock forward or backward.
@ -448,7 +450,7 @@ public:
} }
inline unsigned toSecond(time_t t) const inline unsigned toSecond(Time t) const
{ {
auto res = t % 60; auto res = t % 60;
if (likely(res >= 0)) if (likely(res >= 0))
@ -456,7 +458,7 @@ public:
return res + 60; return res + 60;
} }
inline unsigned toMinute(time_t t) const inline unsigned toMinute(Time t) const
{ {
if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) if (t >= 0 && offset_is_whole_number_of_hours_during_epoch)
return (t / 60) % 60; return (t / 60) % 60;
@ -474,27 +476,27 @@ public:
} }
/// NOTE: Assuming timezone offset is a multiple of 15 minutes. /// NOTE: Assuming timezone offset is a multiple of 15 minutes.
inline time_t toStartOfMinute(time_t t) const { return roundDown(t, 60); } inline Time toStartOfMinute(Time t) const { return roundDown(t, 60); }
inline time_t toStartOfFiveMinute(time_t t) const { return roundDown(t, 300); } inline Time toStartOfFiveMinute(Time t) const { return roundDown(t, 300); }
inline time_t toStartOfFifteenMinutes(time_t t) const { return roundDown(t, 900); } inline Time toStartOfFifteenMinutes(Time t) const { return roundDown(t, 900); }
inline time_t toStartOfTenMinutes(time_t t) const inline Time toStartOfTenMinutes(Time t) const
{ {
if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) if (t >= 0 && offset_is_whole_number_of_hours_during_epoch)
return t / 600 * 600; return t / 600 * 600;
/// More complex logic is for Nepal - it has offset 05:45. Australia/Eucla is also unfortunate. /// More complex logic is for Nepal - it has offset 05:45. Australia/Eucla is also unfortunate.
Int64 date = find(t).date; Time date = find(t).date;
return date + (t - date) / 600 * 600; return date + (t - date) / 600 * 600;
} }
/// NOTE: Assuming timezone transitions are multiple of hours. Lord Howe Island in Australia is a notable exception. /// NOTE: Assuming timezone transitions are multiple of hours. Lord Howe Island in Australia is a notable exception.
inline time_t toStartOfHour(time_t t) const inline Time toStartOfHour(Time t) const
{ {
if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) if (t >= 0 && offset_is_whole_number_of_hours_during_epoch)
return t / 3600 * 3600; return t / 3600 * 3600;
Int64 date = find(t).date; Time date = find(t).date;
return date + (t - date) / 3600 * 3600; return date + (t - date) / 3600 * 3600;
} }
@ -506,11 +508,11 @@ public:
* because the same calendar day starts/ends at different timestamps in different time zones) * because the same calendar day starts/ends at different timestamps in different time zones)
*/ */
inline time_t fromDayNum(DayNum d) const { return lut[toLUTIndex(d)].date; } inline Time fromDayNum(DayNum d) const { return lut[toLUTIndex(d)].date; }
inline time_t fromDayNum(ExtendedDayNum d) const { return lut[toLUTIndex(d)].date; } inline Time fromDayNum(ExtendedDayNum d) const { return lut[toLUTIndex(d)].date; }
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toDate(DateOrTime v) const { return lut[toLUTIndex(v)].date; } inline Time toDate(DateOrTime v) const { return lut[toLUTIndex(v)].date; }
template <typename DateOrTime> template <typename DateOrTime>
inline unsigned toMonth(DateOrTime v) const { return lut[toLUTIndex(v)].month; } inline unsigned toMonth(DateOrTime v) const { return lut[toLUTIndex(v)].month; }
@ -578,7 +580,7 @@ public:
return toDayNum(toFirstDayNumOfISOYearIndex(v)); return toDayNum(toFirstDayNumOfISOYearIndex(v));
} }
inline time_t toFirstDayOfISOYear(time_t t) const inline Time toFirstDayOfISOYear(Time t) const
{ {
return lut[toFirstDayNumOfISOYearIndex(t)].date; return lut[toFirstDayNumOfISOYearIndex(t)].date;
} }
@ -773,7 +775,7 @@ public:
} }
/// We count all hour-length intervals, unrelated to offset changes. /// We count all hour-length intervals, unrelated to offset changes.
inline time_t toRelativeHourNum(time_t t) const inline Time toRelativeHourNum(Time t) const
{ {
if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) if (t >= 0 && offset_is_whole_number_of_hours_during_epoch)
return t / 3600; return t / 3600;
@ -784,18 +786,18 @@ public:
} }
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toRelativeHourNum(DateOrTime v) const inline Time toRelativeHourNum(DateOrTime v) const
{ {
return toRelativeHourNum(lut[toLUTIndex(v)].date); return toRelativeHourNum(lut[toLUTIndex(v)].date);
} }
inline time_t toRelativeMinuteNum(time_t t) const inline Time toRelativeMinuteNum(Time t) const
{ {
return (t + DATE_LUT_ADD) / 60 - (DATE_LUT_ADD / 60); return (t + DATE_LUT_ADD) / 60 - (DATE_LUT_ADD / 60);
} }
template <typename DateOrTime> template <typename DateOrTime>
inline time_t toRelativeMinuteNum(DateOrTime v) const inline Time toRelativeMinuteNum(DateOrTime v) const
{ {
return toRelativeMinuteNum(lut[toLUTIndex(v)].date); return toRelativeMinuteNum(lut[toLUTIndex(v)].date);
} }
@ -842,14 +844,14 @@ public:
return ExtendedDayNum(4 + (d - 4) / days * days); return ExtendedDayNum(4 + (d - 4) / days * days);
} }
inline time_t toStartOfDayInterval(ExtendedDayNum d, UInt64 days) const inline Time toStartOfDayInterval(ExtendedDayNum d, UInt64 days) const
{ {
if (days == 1) if (days == 1)
return toDate(d); return toDate(d);
return lut[toLUTIndex(ExtendedDayNum(d / days * days))].date; return lut[toLUTIndex(ExtendedDayNum(d / days * days))].date;
} }
inline time_t toStartOfHourInterval(time_t t, UInt64 hours) const inline Time toStartOfHourInterval(Time t, UInt64 hours) const
{ {
if (hours == 1) if (hours == 1)
return toStartOfHour(t); return toStartOfHour(t);
@ -867,7 +869,7 @@ public:
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
const Values & values = lut[index]; const Values & values = lut[index];
time_t time = t - values.date; Time time = t - values.date;
if (time >= values.time_at_offset_change()) if (time >= values.time_at_offset_change())
{ {
/// Align to new hour numbers before rounding. /// Align to new hour numbers before rounding.
@ -892,7 +894,7 @@ public:
return values.date + time; return values.date + time;
} }
inline time_t toStartOfMinuteInterval(time_t t, UInt64 minutes) const inline Time toStartOfMinuteInterval(Time t, UInt64 minutes) const
{ {
if (minutes == 1) if (minutes == 1)
return toStartOfMinute(t); return toStartOfMinute(t);
@ -909,7 +911,7 @@ public:
return roundDown(t, seconds); return roundDown(t, seconds);
} }
inline time_t toStartOfSecondInterval(time_t t, UInt64 seconds) const inline Time toStartOfSecondInterval(Time t, UInt64 seconds) const
{ {
if (seconds == 1) if (seconds == 1)
return t; return t;
@ -934,14 +936,14 @@ public:
return toDayNum(makeLUTIndex(year, month, day_of_month)); return toDayNum(makeLUTIndex(year, month, day_of_month));
} }
inline time_t makeDate(Int16 year, UInt8 month, UInt8 day_of_month) const inline Time makeDate(Int16 year, UInt8 month, UInt8 day_of_month) const
{ {
return lut[makeLUTIndex(year, month, day_of_month)].date; return lut[makeLUTIndex(year, month, day_of_month)].date;
} }
/** Does not accept daylight saving time as argument: in case of ambiguity, it choose greater timestamp. /** Does not accept daylight saving time as argument: in case of ambiguity, it choose greater timestamp.
*/ */
inline time_t makeDateTime(Int16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const inline Time makeDateTime(Int16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const
{ {
size_t index = makeLUTIndex(year, month, day_of_month); size_t index = makeLUTIndex(year, month, day_of_month);
UInt32 time_offset = hour * 3600 + minute * 60 + second; UInt32 time_offset = hour * 3600 + minute * 60 + second;
@ -969,7 +971,7 @@ public:
return values.year * 10000 + values.month * 100 + values.day_of_month; return values.year * 10000 + values.month * 100 + values.day_of_month;
} }
inline time_t YYYYMMDDToDate(UInt32 num) const inline Time YYYYMMDDToDate(UInt32 num) const
{ {
return makeDate(num / 10000, num / 100 % 100, num % 100); return makeDate(num / 10000, num / 100 % 100, num % 100);
} }
@ -1000,13 +1002,13 @@ public:
TimeComponents time; TimeComponents time;
}; };
inline DateComponents toDateComponents(time_t t) const inline DateComponents toDateComponents(Time t) const
{ {
const Values & values = getValues(t); const Values & values = getValues(t);
return { values.year, values.month, values.day_of_month }; return { values.year, values.month, values.day_of_month };
} }
inline DateTimeComponents toDateTimeComponents(time_t t) const inline DateTimeComponents toDateTimeComponents(Time t) const
{ {
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
const Values & values = lut[index]; const Values & values = lut[index];
@ -1017,7 +1019,7 @@ public:
res.date.month = values.month; res.date.month = values.month;
res.date.day = values.day_of_month; res.date.day = values.day_of_month;
time_t time = t - values.date; Time time = t - values.date;
if (time >= values.time_at_offset_change()) if (time >= values.time_at_offset_change())
time += values.amount_of_offset_change(); time += values.amount_of_offset_change();
@ -1042,7 +1044,7 @@ public:
} }
inline UInt64 toNumYYYYMMDDhhmmss(time_t t) const inline UInt64 toNumYYYYMMDDhhmmss(Time t) const
{ {
DateTimeComponents components = toDateTimeComponents(t); DateTimeComponents components = toDateTimeComponents(t);
@ -1055,7 +1057,7 @@ public:
+ UInt64(components.date.year) * 10000000000; + UInt64(components.date.year) * 10000000000;
} }
inline time_t YYYYMMDDhhmmssToTime(UInt64 num) const inline Time YYYYMMDDhhmmssToTime(UInt64 num) const
{ {
return makeDateTime( return makeDateTime(
num / 10000000000, num / 10000000000,
@ -1069,12 +1071,12 @@ public:
/// Adding calendar intervals. /// Adding calendar intervals.
/// Implementation specific behaviour when delta is too big. /// Implementation specific behaviour when delta is too big.
inline NO_SANITIZE_UNDEFINED time_t addDays(time_t t, Int64 delta) const inline NO_SANITIZE_UNDEFINED Time addDays(Time t, Int64 delta) const
{ {
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
const Values & values = lut[index]; const Values & values = lut[index];
time_t time = t - values.date; Time time = t - values.date;
if (time >= values.time_at_offset_change()) if (time >= values.time_at_offset_change())
time += values.amount_of_offset_change(); time += values.amount_of_offset_change();
@ -1086,7 +1088,7 @@ public:
return lut[new_index].date + time; return lut[new_index].date + time;
} }
inline NO_SANITIZE_UNDEFINED time_t addWeeks(time_t t, Int64 delta) const inline NO_SANITIZE_UNDEFINED Time addWeeks(Time t, Int64 delta) const
{ {
return addDays(t, delta * 7); return addDays(t, delta * 7);
} }
@ -1131,14 +1133,14 @@ public:
/// If resulting month has less deys than source month, then saturation can happen. /// If resulting month has less deys than source month, then saturation can happen.
/// Example: 31 Aug + 1 month = 30 Sep. /// Example: 31 Aug + 1 month = 30 Sep.
inline time_t NO_SANITIZE_UNDEFINED addMonths(time_t t, Int64 delta) const inline Time NO_SANITIZE_UNDEFINED addMonths(Time t, Int64 delta) const
{ {
const auto result_day = addMonthsIndex(t, delta); const auto result_day = addMonthsIndex(t, delta);
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
const Values & values = lut[index]; const Values & values = lut[index];
time_t time = t - values.date; Time time = t - values.date;
if (time >= values.time_at_offset_change()) if (time >= values.time_at_offset_change())
time += values.amount_of_offset_change(); time += values.amount_of_offset_change();
@ -1153,7 +1155,7 @@ public:
return toDayNum(addMonthsIndex(d, delta)); return toDayNum(addMonthsIndex(d, delta));
} }
inline time_t NO_SANITIZE_UNDEFINED addQuarters(time_t t, Int64 delta) const inline Time NO_SANITIZE_UNDEFINED addQuarters(Time t, Int64 delta) const
{ {
return addMonths(t, delta * 3); return addMonths(t, delta * 3);
} }
@ -1180,14 +1182,14 @@ public:
} }
/// Saturation can occur if 29 Feb is mapped to non-leap year. /// Saturation can occur if 29 Feb is mapped to non-leap year.
inline time_t addYears(time_t t, Int64 delta) const inline Time addYears(Time t, Int64 delta) const
{ {
auto result_day = addYearsIndex(t, delta); auto result_day = addYearsIndex(t, delta);
const LUTIndex index = findIndex(t); const LUTIndex index = findIndex(t);
const Values & values = lut[index]; const Values & values = lut[index];
time_t time = t - values.date; Time time = t - values.date;
if (time >= values.time_at_offset_change()) if (time >= values.time_at_offset_change())
time += values.amount_of_offset_change(); time += values.amount_of_offset_change();
@ -1203,7 +1205,7 @@ public:
} }
inline std::string timeToString(time_t t) const inline std::string timeToString(Time t) const
{ {
DateTimeComponents components = toDateTimeComponents(t); DateTimeComponents components = toDateTimeComponents(t);
@ -1228,7 +1230,7 @@ public:
return s; return s;
} }
inline std::string dateToString(time_t t) const inline std::string dateToString(Time t) const
{ {
const Values & values = getValues(t); const Values & values = getValues(t);