diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index 1436ad08342..0e7170cd21b 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -498,7 +498,7 @@ Field parseMyDateTime(const String & str, int8_t fsp) { // Since we only use DateLUTImpl as parameter placeholder of AddSecondsImpl::execute // and it's costly to construct a DateLUTImpl, a shared static instance is enough. - static const DateLUTImpl lut = DateLUT::instance("UTC"); + static const DateLUTImpl & lut = DateLUT::instance("UTC"); Int32 year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, delta_hour = 0, delta_minute = 0; @@ -806,7 +806,7 @@ void convertTimeZone(UInt64 from_time, UInt64 & to_time, const DateLUTImpl & tim } MyDateTime from_my_time(from_time); time_t epoch = getEpochSecond(from_my_time, time_zone_from); - if (unlikely(epoch + time_zone_to.getOffsetAtStartEpoch() + SECONDS_PER_DAY < 0)) + if (unlikely(epoch + time_zone_to.getOffsetAtStartOfEpoch() + SECONDS_PER_DAY < 0)) throw Exception("Unsupported timestamp value , TiFlash only support timestamp after 1970-01-01 00:00:00 UTC)"); MyDateTime to_my_time(time_zone_to.toYear(epoch), time_zone_to.toMonth(epoch), time_zone_to.toDayOfMonth(epoch), time_zone_to.toHour(epoch), time_zone_to.toMinute(epoch), time_zone_to.toSecond(epoch), from_my_time.micro_second); diff --git a/dbms/src/Common/MyTime.h b/dbms/src/Common/MyTime.h index 3e80ae58460..1b429048c94 100644 --- a/dbms/src/Common/MyTime.h +++ b/dbms/src/Common/MyTime.h @@ -175,7 +175,7 @@ inline time_t getEpochSecond(const MyDateTime & my_time, const DateLUTImpl & tim { /// - 3600 * 24 + my_time.hour * 3600 + my_time.minute * 60 + my_time.second is UTC based, need to adjust /// the epoch according to the input time_zone - return -3600 * 24 + my_time.hour * 3600 + my_time.minute * 60 + my_time.second - time_zone.getOffsetAtStartEpoch(); + return -3600 * 24 + my_time.hour * 3600 + my_time.minute * 60 + my_time.second - time_zone.getOffsetAtStartOfEpoch(); } else { diff --git a/dbms/src/DataTypes/DataTypeDate.cpp b/dbms/src/DataTypes/DataTypeDate.cpp index 27b31b0f3db..66f23503d14 100644 --- a/dbms/src/DataTypes/DataTypeDate.cpp +++ b/dbms/src/DataTypes/DataTypeDate.cpp @@ -11,12 +11,12 @@ namespace DB void DataTypeDate::serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr) const { - writeDateText(DayNum_t(static_cast(column).getData()[row_num]), ostr); + writeDateText(DayNum(static_cast(column).getData()[row_num]), ostr); } static void deserializeText(IColumn & column, ReadBuffer & istr) { - DayNum_t x; + DayNum x; readDateText(x, istr); static_cast(column).getData().push_back(x); } @@ -40,7 +40,7 @@ void DataTypeDate::serializeTextQuoted(const IColumn & column, size_t row_num, W void DataTypeDate::deserializeTextQuoted(IColumn & column, ReadBuffer & istr) const { - DayNum_t x; + DayNum x; assertChar('\'', istr); readDateText(x, istr); assertChar('\'', istr); @@ -56,7 +56,7 @@ void DataTypeDate::serializeTextJSON(const IColumn & column, size_t row_num, Wri void DataTypeDate::deserializeTextJSON(IColumn & column, ReadBuffer & istr) const { - DayNum_t x; + DayNum x; assertChar('"', istr); readDateText(x, istr); assertChar('"', istr); diff --git a/dbms/src/Dictionaries/ClickHouseDictionarySource.cpp b/dbms/src/Dictionaries/ClickHouseDictionarySource.cpp index 47377bb6519..4e4382e67eb 100644 --- a/dbms/src/Dictionaries/ClickHouseDictionarySource.cpp +++ b/dbms/src/Dictionaries/ClickHouseDictionarySource.cpp @@ -81,7 +81,7 @@ std::string ClickHouseDictionarySource::getUpdateFieldAndDate() auto tmp_time = update_time; update_time = std::chrono::system_clock::now(); time_t hr_time = std::chrono::system_clock::to_time_t(tmp_time) - 1; - std::string str_time = std::to_string(LocalDateTime(hr_time)); + std::string str_time = LocalDateTime(hr_time).toString(); return query_builder.composeUpdateQuery(update_field, str_time); } else diff --git a/dbms/src/Functions/FunctionsComparison.h b/dbms/src/Functions/FunctionsComparison.h index 8f7846ce324..e3cd6f416a6 100644 --- a/dbms/src/Functions/FunctionsComparison.h +++ b/dbms/src/Functions/FunctionsComparison.h @@ -130,20 +130,20 @@ inline int memcmp16(const void * a, const void * b) inline time_t dateToDateTime(UInt32 date_data) { - DayNum_t day_num(date_data); + DayNum day_num(date_data); LocalDate local_date(day_num); // todo use timezone info return DateLUT::instance().makeDateTime(local_date.year(), local_date.month(), local_date.day(), 0, 0, 0); } -inline std::tuple dateTimeToDate(time_t time_data) +inline std::tuple dateTimeToDate(time_t time_data) { // todo use timezone info auto & date_lut = DateLUT::instance(); auto truncated = date_lut.toHour(time_data) != 0 || date_lut.toMinute(time_data) != 0 || date_lut.toSecond(time_data) != 0; auto values = date_lut.getValues(time_data); auto day_num = date_lut.makeDayNum(values.year, values.month, values.day_of_month); - return std::make_tuple(day_num, truncated); + return std::make_tuple(static_cast(day_num), truncated); } @@ -191,7 +191,7 @@ struct DateDateTimeComparisonImpl // date vector with datetime constant // first check if datetime constant can be convert to date constant bool truncated; - DayNum_t date_num; + DayNum date_num; std::tie(date_num, truncated) = dateTimeToDate((time_t) b); if (!truncated) { @@ -230,7 +230,7 @@ struct DateDateTimeComparisonImpl { // datetime constant with date vector bool truncated; - DayNum_t date_num; + DayNum date_num; std::tie(date_num, truncated) = dateTimeToDate((time_t) a); if (!truncated) { @@ -1126,7 +1126,7 @@ class FunctionComparison : public IFunction if (is_date) { - DayNum_t date; + DayNum date; ReadBufferFromMemory in(string_value.data, string_value.size); readDateText(date, in); if (!in.eof()) diff --git a/dbms/src/Functions/FunctionsConversion.cpp b/dbms/src/Functions/FunctionsConversion.cpp index 91107ac158a..42517649e24 100644 --- a/dbms/src/Functions/FunctionsConversion.cpp +++ b/dbms/src/Functions/FunctionsConversion.cpp @@ -126,7 +126,7 @@ class FunctionTiDBUnixTimeStamp : public IFunction bool getUnixTimeStampHelper(UInt64 packed, UInt64 & ret) { - static const auto lut_utc = DateLUT::instance("UTC"); + static const auto & lut_utc = DateLUT::instance("UTC"); if (timezone_.is_name_based) convertTimeZone(packed, ret, *timezone_.timezone, lut_utc); diff --git a/dbms/src/Functions/FunctionsConversion.h b/dbms/src/Functions/FunctionsConversion.h index 69b78f770e3..e094f665b38 100644 --- a/dbms/src/Functions/FunctionsConversion.h +++ b/dbms/src/Functions/FunctionsConversion.h @@ -269,7 +269,7 @@ struct ToDateTimeImpl static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.fromDayNum(DayNum_t(d)); + return time_zone.fromDayNum(DayNum(d)); } }; @@ -408,7 +408,7 @@ struct FormatImpl { static void execute(const DataTypeDate::FieldType x, WriteBuffer & wb, const DataTypeDate *, const DateLUTImpl *) { - writeDateText(DayNum_t(x), wb); + writeDateText(DayNum(x), wb); } }; @@ -566,7 +566,7 @@ template void parseImpl(typename DataType::FieldType & x, Re template <> inline void parseImpl(DataTypeDate::FieldType & x, ReadBuffer & rb, const DateLUTImpl *) { - DayNum_t tmp(0); + DayNum tmp(0); readDateText(tmp, rb); x = tmp; } @@ -1408,7 +1408,7 @@ class FunctionFromUnixTime : public IFunction } else { - if (unlikely(integer_part + datelut->getOffsetAtStartEpoch() + SECONDS_PER_DAY < 0)) + if (unlikely(integer_part + datelut->getOffsetAtStartOfEpoch() + SECONDS_PER_DAY < 0)) throw Exception("Unsupported timestamp value , TiFlash only support timestamp after 1970-01-01 00:00:00 UTC)"); } MyDateTime result(datelut->toYear(integer_part), datelut->toMonth(integer_part), datelut->toDayOfMonth(integer_part), diff --git a/dbms/src/Functions/FunctionsDateTime.h b/dbms/src/Functions/FunctionsDateTime.h index f51fbca7b99..f4d1e5487cd 100644 --- a/dbms/src/Functions/FunctionsDateTime.h +++ b/dbms/src/Functions/FunctionsDateTime.h @@ -205,7 +205,7 @@ struct ToMondayImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toFirstDayNumOfWeek(DayNum_t(d)); + return time_zone.toFirstDayNumOfWeek(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toMonday", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -224,7 +224,7 @@ struct ToStartOfMonthImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toFirstDayNumOfMonth(DayNum_t(d)); + return time_zone.toFirstDayNumOfMonth(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toStartOfMonday", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -243,7 +243,7 @@ struct ToStartOfQuarterImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toFirstDayNumOfQuarter(DayNum_t(d)); + return time_zone.toFirstDayNumOfQuarter(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toStartOfQuarter", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -262,7 +262,7 @@ struct ToStartOfYearImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toFirstDayNumOfYear(DayNum_t(d)); + return time_zone.toFirstDayNumOfYear(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toStartOfYear", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -380,7 +380,7 @@ struct ToYearImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toYear(DayNum_t(d)); + return time_zone.toYear(DayNum(d)); } static inline UInt16 execute(UInt64 packed, const DateLUTImpl &) { return UInt16((packed >> 46) / 13); } @@ -397,7 +397,7 @@ struct ToQuarterImpl } static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toQuarter(DayNum_t(d)); + return time_zone.toQuarter(DayNum(d)); } static inline UInt8 execute(UInt64 packed, const DateLUTImpl &) { return ((/* Month */ (packed >> 46) % 13) + 2) / 3; } @@ -414,7 +414,7 @@ struct ToMonthImpl } static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toMonth(DayNum_t(d)); + return time_zone.toMonth(DayNum(d)); } // tidb date related type, ignore time_zone info static inline UInt8 execute(UInt64 t, const DateLUTImpl &) { return (UInt8)((t >> 46u) % 13); } @@ -432,7 +432,7 @@ struct ToDayOfMonthImpl } static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toDayOfMonth(DayNum_t(d)); + return time_zone.toDayOfMonth(DayNum(d)); } static inline UInt8 execute(UInt64 t, const DateLUTImpl & ) { return (UInt8)((t >> 41) & 31); @@ -451,7 +451,7 @@ struct ToDayOfWeekImpl } static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toDayOfWeek(DayNum_t(d)); + return time_zone.toDayOfWeek(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toDayOfWeek", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -528,7 +528,7 @@ struct ToRelativeYearNumImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toYear(DayNum_t(d)); + return time_zone.toYear(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeYearNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -547,7 +547,7 @@ struct ToRelativeQuarterNumImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toRelativeQuarterNum(DayNum_t(d)); + return time_zone.toRelativeQuarterNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeQuarterNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -566,7 +566,7 @@ struct ToRelativeMonthNumImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toRelativeMonthNum(DayNum_t(d)); + return time_zone.toRelativeMonthNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeMonthNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -585,7 +585,7 @@ struct ToRelativeWeekNumImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toRelativeWeekNum(DayNum_t(d)); + return time_zone.toRelativeWeekNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeWeekNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -604,7 +604,7 @@ struct ToRelativeDayNumImpl } static inline UInt16 execute(UInt16 d, const DateLUTImpl &) { - return static_cast(d); + return static_cast(d); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeDayNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -624,7 +624,7 @@ struct ToRelativeHourNumImpl } static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toRelativeHourNum(DayNum_t(d)); + return time_zone.toRelativeHourNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeHourNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -643,7 +643,7 @@ struct ToRelativeMinuteNumImpl } static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toRelativeMinuteNum(DayNum_t(d)); + return time_zone.toRelativeMinuteNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeMinuteNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -662,7 +662,7 @@ struct ToRelativeSecondNumImpl } static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.fromDayNum(DayNum_t(d)); + return time_zone.fromDayNum(DayNum(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toRelativeSecondNum", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -681,7 +681,7 @@ struct ToYYYYMMImpl } static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toNumYYYYMM(static_cast(d)); + return time_zone.toNumYYYYMM(static_cast(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toYYYYMM", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -700,7 +700,7 @@ struct ToYYYYMMDDImpl } static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toNumYYYYMMDD(static_cast(d)); + return time_zone.toNumYYYYMMDD(static_cast(d)); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toYYYYMMDD", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -719,7 +719,7 @@ struct ToYYYYMMDDhhmmssImpl } static inline UInt64 execute(UInt16 d, const DateLUTImpl & time_zone) { - return time_zone.toNumYYYYMMDDhhmmss(time_zone.toDate(static_cast(d))); + return time_zone.toNumYYYYMMDDhhmmss(time_zone.toDate(static_cast(d))); } static inline UInt8 execute(UInt64 , const DateLUTImpl & ) { throw Exception("Illegal type MyTime of argument for function toYYYYMMDDhhmmss", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); @@ -921,7 +921,7 @@ struct AddSecondsImpl static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) { - return time_zone.fromDayNum(DayNum_t(d)) + delta; + return time_zone.fromDayNum(DayNum(d)) + delta; } static inline UInt64 execute(UInt64 t, Int64 delta, const DateLUTImpl &) @@ -969,7 +969,7 @@ struct AddMinutesImpl static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) { - return time_zone.fromDayNum(DayNum_t(d)) + delta * 60; + return time_zone.fromDayNum(DayNum(d)) + delta * 60; } static inline UInt64 execute(UInt64 t, Int64 delta, const DateLUTImpl & time_zone) { @@ -988,7 +988,7 @@ struct AddHoursImpl static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) { - return time_zone.fromDayNum(DayNum_t(d)) + delta * 3600; + return time_zone.fromDayNum(DayNum(d)) + delta * 3600; } static inline UInt64 execute(UInt64 t, Int64 delta, const DateLUTImpl & time_zone) @@ -1054,7 +1054,7 @@ struct AddMonthsImpl static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) { - return time_zone.addMonths(DayNum_t(d), delta); + return time_zone.addMonths(DayNum(d), delta); } static inline UInt64 execute(UInt64 t, Int64 delta, const DateLUTImpl &) @@ -1080,7 +1080,7 @@ struct AddYearsImpl static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) { - return time_zone.addYears(DayNum_t(d), delta); + return time_zone.addYears(DayNum(d), delta); } static inline UInt64 execute(UInt64 t, Int64 delta, const DateLUTImpl & time_zone) diff --git a/dbms/src/Functions/FunctionsTiDBConversion.h b/dbms/src/Functions/FunctionsTiDBConversion.h index 32e973e0604..6bc88a1bc90 100644 --- a/dbms/src/Functions/FunctionsTiDBConversion.h +++ b/dbms/src/Functions/FunctionsTiDBConversion.h @@ -1281,7 +1281,7 @@ struct TiDBConvertToTime // Overflow if (micro_second >= std::pow(10, to_fsp)) { - static const auto lut = DateLUT::instance("UTC"); + static const auto & lut = DateLUT::instance("UTC"); datetime.micro_second = 0; packed_uint = datetime.toPackedUInt(); packed_uint = AddSecondsImpl::execute(packed_uint, 1, lut); diff --git a/dbms/src/IO/ReadHelpers.h b/dbms/src/IO/ReadHelpers.h index 2fb7e912350..231bb7f9504 100644 --- a/dbms/src/IO/ReadHelpers.h +++ b/dbms/src/IO/ReadHelpers.h @@ -631,7 +631,7 @@ inline void readDateText(LocalDate & date, ReadBuffer & buf) readDateTextFallback(date, buf); } -inline void readDateText(DayNum_t & date, ReadBuffer & buf) +inline void readDateText(DayNum & date, ReadBuffer & buf) { LocalDate local_date; readDateText(local_date, buf); diff --git a/dbms/src/IO/WriteHelpers.h b/dbms/src/IO/WriteHelpers.h index 8fd75aec7eb..6dff98f518a 100644 --- a/dbms/src/IO/WriteHelpers.h +++ b/dbms/src/IO/WriteHelpers.h @@ -540,7 +540,7 @@ inline void writeDateText(const LocalDate & date, WriteBuffer & buf) } template -inline void writeDateText(DayNum_t date, WriteBuffer & buf) +inline void writeDateText(DayNum date, WriteBuffer & buf) { if (unlikely(!date)) { diff --git a/dbms/src/Interpreters/Quota.cpp b/dbms/src/Interpreters/Quota.cpp index 48126eb0f77..b3eccb177e3 100644 --- a/dbms/src/Interpreters/Quota.cpp +++ b/dbms/src/Interpreters/Quota.cpp @@ -66,7 +66,7 @@ String QuotaForInterval::toString() const auto loaded_rounded_time = rounded_time.load(std::memory_order_relaxed); res << std::fixed << std::setprecision(3) - << "Interval: " << LocalDateTime(loaded_rounded_time) << " - " << LocalDateTime(loaded_rounded_time + duration) << ".\n" + << "Interval: " << LocalDateTime(loaded_rounded_time).toString() << " - " << LocalDateTime(loaded_rounded_time + duration).toString() << ".\n" << "Queries: " << used.queries << ".\n" << "Errors: " << used.errors << ".\n" << "Result rows: " << used.result_rows << ".\n" @@ -152,7 +152,7 @@ void QuotaForInterval::check( message << " has been exceeded. " << resource_name << ": " << used_amount << ", max: " << max_amount << ". " - << "Interval will end at " << LocalDateTime(rounded_time.load(std::memory_order_relaxed) + duration) << ". " + << "Interval will end at " << LocalDateTime(rounded_time.load(std::memory_order_relaxed) + duration).toString() << ". " << "Name of quota template: '" << quota_name << "'."; throw Exception(message.str(), ErrorCodes::QUOTA_EXPIRED); diff --git a/dbms/src/Interpreters/convertFieldToType.cpp b/dbms/src/Interpreters/convertFieldToType.cpp index 08ca468eb71..f9052d91c1e 100644 --- a/dbms/src/Interpreters/convertFieldToType.cpp +++ b/dbms/src/Interpreters/convertFieldToType.cpp @@ -156,10 +156,10 @@ static Field convertDecimalType(const Field & from, const To & type) ErrorCodes::TYPE_MISMATCH); } -DayNum_t stringToDate(const String & s) +DayNum stringToDate(const String & s) { ReadBufferFromString in(s); - DayNum_t date{}; + DayNum date{}; readDateText(date, in); if (!in.eof()) diff --git a/dbms/src/Storages/DeltaMerge/Index/ValueComparison.h b/dbms/src/Storages/DeltaMerge/Index/ValueComparison.h index 1214e06b4e3..e12a0a5405e 100644 --- a/dbms/src/Storages/DeltaMerge/Index/ValueComparison.h +++ b/dbms/src/Storages/DeltaMerge/Index/ValueComparison.h @@ -270,7 +270,7 @@ struct ValueComparision { if constexpr (std::is_same_v) { - DayNum_t date; + DayNum date; ReadBufferFromMemory in(left.data(), left.size()); readDateText(date, in); if (!in.eof()) @@ -330,4 +330,4 @@ struct ValueComparision }; } // namespace DM -} // namespace DB \ No newline at end of file +} // namespace DB diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index 5400f3aa664..5f12c65ebde 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -103,8 +103,8 @@ void MergeTreeDataMerger::FuturePart::assign(MergeTreeData::DataPartsVector part if (parts.front()->storage.format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING) { - DayNum_t min_date = DayNum_t(std::numeric_limits::max()); - DayNum_t max_date = DayNum_t(std::numeric_limits::min()); + DayNum min_date = DayNum(std::numeric_limits::max()); + DayNum max_date = DayNum(std::numeric_limits::min()); for (const auto & part : parts) { min_date = std::min(min_date, part->getMinDate()); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp index cf3c3f5264b..14cb3f6378a 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp @@ -259,8 +259,8 @@ String MergeTreeDataPart::getNewName(const MergeTreePartInfo & new_part_info) co /// the merged part name be determined only by source part names. /// It is simpler this way when the real min and max dates for the block range can change /// (e.g. after an ALTER DELETE command). - DayNum_t min_date; - DayNum_t max_date; + DayNum min_date; + DayNum max_date; MergeTreePartInfo::parseMinMaxDatesFromPartName(name, min_date, max_date); return new_part_info.getPartNameV0(min_date, max_date); } @@ -268,21 +268,21 @@ String MergeTreeDataPart::getNewName(const MergeTreePartInfo & new_part_info) co return new_part_info.getPartName(); } -DayNum_t MergeTreeDataPart::getMinDate() const +DayNum MergeTreeDataPart::getMinDate() const { if (storage.minmax_idx_date_column_pos != -1 && minmax_idx.initialized) - return DayNum_t(minmax_idx.min_values[storage.minmax_idx_date_column_pos].get()); + return DayNum(minmax_idx.min_values[storage.minmax_idx_date_column_pos].get()); else - return DayNum_t(); + return DayNum(); } -DayNum_t MergeTreeDataPart::getMaxDate() const +DayNum MergeTreeDataPart::getMaxDate() const { if (storage.minmax_idx_date_column_pos != -1 && minmax_idx.initialized) - return DayNum_t(minmax_idx.max_values[storage.minmax_idx_date_column_pos].get()); + return DayNum(minmax_idx.max_values[storage.minmax_idx_date_column_pos].get()); else - return DayNum_t(); + return DayNum(); } @@ -517,8 +517,8 @@ void MergeTreeDataPart::loadPartitionAndMinMaxIndex() } else if (storage.format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING) { - DayNum_t min_date; - DayNum_t max_date; + DayNum min_date; + DayNum max_date; MergeTreePartInfo::parseMinMaxDatesFromPartName(name, min_date, max_date); const auto & date_lut = DateLUT::instance(); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataPart.h b/dbms/src/Storages/MergeTree/MergeTreeDataPart.h index bfa37cb94e9..dbbf8fe523a 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataPart.h +++ b/dbms/src/Storages/MergeTree/MergeTreeDataPart.h @@ -65,8 +65,8 @@ struct MergeTreeDataPart bool contains(const MergeTreeDataPart & other) const { return info.contains(other.info); } /// If the partition key includes date column (a common case), these functions will return min and max values for this column. - DayNum_t getMinDate() const; - DayNum_t getMaxDate() const; + DayNum getMinDate() const; + DayNum getMaxDate() const; bool isEmpty() const { return rows_count == 0; } @@ -187,7 +187,7 @@ struct MergeTreeDataPart MinMaxIndex() = default; /// For month-based partitioning. - MinMaxIndex(DayNum_t min_date, DayNum_t max_date) + MinMaxIndex(DayNum min_date, DayNum max_date) : min_values(1, static_cast(min_date)) , max_values(1, static_cast(max_date)) , initialized(true) diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 8d254bb9bc5..3071dcc8fa8 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -176,13 +176,13 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithPa String part_name; if (data.format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING) { - DayNum_t min_date(minmax_idx.min_values[data.minmax_idx_date_column_pos].get()); - DayNum_t max_date(minmax_idx.max_values[data.minmax_idx_date_column_pos].get()); + DayNum min_date(minmax_idx.min_values[data.minmax_idx_date_column_pos].get()); + DayNum max_date(minmax_idx.max_values[data.minmax_idx_date_column_pos].get()); const auto & date_lut = DateLUT::instance(); - DayNum_t min_month = date_lut.toFirstDayNumOfMonth(DayNum_t(min_date)); - DayNum_t max_month = date_lut.toFirstDayNumOfMonth(DayNum_t(max_date)); + auto min_month = date_lut.toNumYYYYMM(min_date); + auto max_month = date_lut.toNumYYYYMM(max_date); if (min_month != max_month) throw Exception("Logical error: part spans more than one month."); diff --git a/dbms/src/Storages/MergeTree/MergeTreePartInfo.cpp b/dbms/src/Storages/MergeTree/MergeTreePartInfo.cpp index 338dcf2249d..c6c53d081f3 100644 --- a/dbms/src/Storages/MergeTree/MergeTreePartInfo.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreePartInfo.cpp @@ -78,7 +78,7 @@ bool MergeTreePartInfo::tryParsePartName(const String & dir_name, MergeTreePartI } -void MergeTreePartInfo::parseMinMaxDatesFromPartName(const String & dir_name, DayNum_t & min_date, DayNum_t & max_date) +void MergeTreePartInfo::parseMinMaxDatesFromPartName(const String & dir_name, DayNum & min_date, DayNum & max_date) { UInt32 min_yyyymmdd = 0; UInt32 max_yyyymmdd = 0; @@ -97,8 +97,8 @@ void MergeTreePartInfo::parseMinMaxDatesFromPartName(const String & dir_name, Da min_date = date_lut.YYYYMMDDToDayNum(min_yyyymmdd); max_date = date_lut.YYYYMMDDToDayNum(max_yyyymmdd); - DayNum_t min_month = date_lut.toFirstDayNumOfMonth(min_date); - DayNum_t max_month = date_lut.toFirstDayNumOfMonth(max_date); + auto min_month = date_lut.toNumYYYYMM(min_date); + auto max_month = date_lut.toNumYYYYMM(max_date); if (min_month != max_month) throw Exception("Part name " + dir_name + " contains different months", ErrorCodes::BAD_DATA_PART_NAME); @@ -129,7 +129,7 @@ String MergeTreePartInfo::getPartName() const } -String MergeTreePartInfo::getPartNameV0(DayNum_t left_date, DayNum_t right_date) const +String MergeTreePartInfo::getPartNameV0(DayNum left_date, DayNum right_date) const { const auto & date_lut = DateLUT::instance(); diff --git a/dbms/src/Storages/MergeTree/MergeTreePartInfo.h b/dbms/src/Storages/MergeTree/MergeTreePartInfo.h index e664f697a2d..b276f1586ee 100644 --- a/dbms/src/Storages/MergeTree/MergeTreePartInfo.h +++ b/dbms/src/Storages/MergeTree/MergeTreePartInfo.h @@ -52,7 +52,7 @@ struct MergeTreePartInfo } String getPartName() const; - String getPartNameV0(DayNum_t left_date, DayNum_t right_date) const; + String getPartNameV0(DayNum left_date, DayNum right_date) const; UInt64 getBlocksCount() const { return static_cast(max_block - min_block + 1); @@ -62,7 +62,7 @@ struct MergeTreePartInfo static bool tryParsePartName(const String & dir_name, MergeTreePartInfo * part_info, MergeTreeDataFormatVersion format_version); - static void parseMinMaxDatesFromPartName(const String & part_name, DayNum_t & min_date, DayNum_t & max_date); + static void parseMinMaxDatesFromPartName(const String & part_name, DayNum & min_date, DayNum & max_date); static bool contains(const String & outer_part_name, const String & inner_part_name, MergeTreeDataFormatVersion format_version); }; diff --git a/dbms/src/Storages/MergeTree/MergeTreePartition.cpp b/dbms/src/Storages/MergeTree/MergeTreePartition.cpp index 1726fb4d90c..80509ddce44 100644 --- a/dbms/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreePartition.cpp @@ -57,7 +57,7 @@ String MergeTreePartition::getID(const MergeTreeData & storage) const result += '-'; if (typeid_cast(storage.partition_key_sample.getByPosition(i).type.get())) - result += toString(DateLUT::instance().toNumYYYYMMDD(DayNum_t(value[i].safeGet()))); + result += toString(DateLUT::instance().toNumYYYYMMDD(DayNum(value[i].safeGet()))); else result += applyVisitor(to_string_visitor, value[i]); diff --git a/dbms/src/Storages/tests/part_name.cpp b/dbms/src/Storages/tests/part_name.cpp index 9fdc39c569a..79c5578a8ca 100644 --- a/dbms/src/Storages/tests/part_name.cpp +++ b/dbms/src/Storages/tests/part_name.cpp @@ -5,9 +5,9 @@ int main(int, char **) { - DayNum_t today = DateLUT::instance().toDayNum(time(nullptr)); + DayNum today = DateLUT::instance().toDayNum(time(nullptr)); - for (DayNum_t date = today; DayNum_t(date + 10) > today; --date) + for (DayNum date = today; DayNum(date + 10) > today; --date) { DB::MergeTreePartInfo part_info("partition", 0, 0, 0); std::string name = part_info.getPartNameV0(date, date); diff --git a/libs/libcommon/include/common/DateLUT.h b/libs/libcommon/include/common/DateLUT.h index 451bfa4d991..378b4360f3b 100644 --- a/libs/libcommon/include/common/DateLUT.h +++ b/libs/libcommon/include/common/DateLUT.h @@ -1,51 +1,40 @@ #pragma once #include "DateLUTImpl.h" -#include -#include + +#include "defines.h" + +#include + #include -#include #include - -// Also defined in Core/Defines.h -#if !defined(ALWAYS_INLINE) -#if defined(_MSC_VER) - #define ALWAYS_INLINE __forceinline -#else - #define ALWAYS_INLINE __attribute__((__always_inline__)) -#endif -#endif +#include +#include /// This class provides lazy initialization and lookup of singleton DateLUTImpl objects for a given timezone. -class DateLUT : public ext::singleton +class DateLUT : private boost::noncopyable { - friend class ext::singleton; - public: - DateLUT(const DateLUT &) = delete; - DateLUT & operator=(const DateLUT &) = delete; - /// Return singleton DateLUTImpl instance for the default time zone. static ALWAYS_INLINE const DateLUTImpl & instance() { - const auto & date_lut = ext::singleton::instance(); + const auto & date_lut = getInstance(); return *date_lut.default_impl.load(std::memory_order_acquire); } /// Return singleton DateLUTImpl instance for a given time zone. static ALWAYS_INLINE const DateLUTImpl & instance(const std::string & time_zone) { - const auto & date_lut = ext::singleton::instance(); + const auto & date_lut = getInstance(); if (time_zone.empty()) return *date_lut.default_impl.load(std::memory_order_acquire); return date_lut.getImplementation(time_zone); } - static void setDefaultTimezone(const std::string & time_zone) { - auto & date_lut = ext::singleton::instance(); + auto & date_lut = getInstance(); const auto & impl = date_lut.getImplementation(time_zone); date_lut.default_impl.store(&impl, std::memory_order_release); } @@ -54,6 +43,8 @@ class DateLUT : public ext::singleton DateLUT(); private: + static DateLUT & getInstance(); + const DateLUTImpl & getImplementation(const std::string & time_zone) const; using DateLUTImplPtr = std::unique_ptr; diff --git a/libs/libcommon/include/common/DateLUTImpl.h b/libs/libcommon/include/common/DateLUTImpl.h index 5ac136b2047..c6bc274554a 100644 --- a/libs/libcommon/include/common/DateLUTImpl.h +++ b/libs/libcommon/include/common/DateLUTImpl.h @@ -1,26 +1,38 @@ #pragma once -#include "Types.h" #include "DayNum.h" -#include "likely.h" +#include "defines.h" +#include "Types.h" + #include +#include #include +#include -#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 2105 /// Last supported year + +#define DATE_LUT_MIN_YEAR 1925 /// 1925 since wast majority of timezones changed to 15-minute aligned offsets somewhere in 1924 or earlier. +#define DATE_LUT_MAX_YEAR 2283 /// Last supported year (complete) #define DATE_LUT_YEARS (1 + DATE_LUT_MAX_YEAR - DATE_LUT_MIN_YEAR) /// Number of years in lookup table + +#define DATE_LUT_SIZE 0x20000 + +#define DATE_LUT_MAX (0xFFFFFFFFU - 86400) +#define DATE_LUT_MAX_DAY_NUM 0xFFFF + #define SECONDS_PER_DAY (3600 * 24) +/// A constant to add to time_t so every supported time point becomes non-negative and still has the same remainder of division by 3600. +/// If we treat "remainder of division" operation in the sense of modular arithmetic (not like in C++). +#define DATE_LUT_ADD ((1970 - DATE_LUT_MIN_YEAR) * 366 * 86400) + + #if defined(__PPC__) -#if !__clang__ +#if !defined(__clang__) #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif #endif + /// Flags for toYearWeek() function. enum class WeekModeFlag : UInt8 { @@ -29,24 +41,91 @@ enum class WeekModeFlag : UInt8 FIRST_WEEKDAY = 4, NEWYEAR_DAY = 8 }; -typedef std::pair YearWeek; +using YearWeek = std::pair; /** 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: - DateLUTImpl(const std::string & time_zone); +private: + friend class DateLUT; + 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; + + // Normalized and bound-checked index of element in lut, + // has to be a separate type to support overloading + // TODO: make sure that any arithmetic on LUTIndex actually results in valid LUTIndex. + STRONG_TYPEDEF(UInt32, LUTIndex) + + template + friend inline LUTIndex operator+(const LUTIndex & index, const T v) + { + return LUTIndex{(index.toUnderType() + UInt32(v)) & date_lut_mask}; + } + + template + friend inline LUTIndex operator+(const T v, const LUTIndex & index) + { + return LUTIndex{(v + index.toUnderType()) & date_lut_mask}; + } + + friend inline LUTIndex operator+(const LUTIndex & index, const LUTIndex & v) + { + return LUTIndex{(index.toUnderType() + v.toUnderType()) & date_lut_mask}; + } + + template + friend inline LUTIndex operator-(const LUTIndex & index, const T v) + { + return LUTIndex{(index.toUnderType() - UInt32(v)) & date_lut_mask}; + } + + template + friend inline LUTIndex operator-(const T v, const LUTIndex & index) + { + return LUTIndex{(v - index.toUnderType()) & date_lut_mask}; + } + + friend inline LUTIndex operator-(const LUTIndex & index, const LUTIndex & v) + { + return LUTIndex{(index.toUnderType() - v.toUnderType()) & date_lut_mask}; + } + + template + friend inline LUTIndex operator*(const LUTIndex & index, const T v) + { + return LUTIndex{(index.toUnderType() * UInt32(v)) & date_lut_mask}; + } + + template + friend inline LUTIndex operator*(const T v, const LUTIndex & index) + { + return LUTIndex{(v * index.toUnderType()) & date_lut_mask}; + } + + template + friend inline LUTIndex operator/(const LUTIndex & index, const T v) + { + return LUTIndex{(index.toUnderType() / UInt32(v)) & date_lut_mask}; + } + + template + friend inline LUTIndex operator/(const T v, const LUTIndex & index) + { + return LUTIndex{(UInt32(v) / index.toUnderType()) & date_lut_mask}; + } 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 is zero. - /// Change to time_t; change constants above; and recompile the sources if you need to support time after 2105 year. - UInt32 date; + /// time_t at beginning of the day. + Int64 date; /// Properties of the day. UInt16 year; @@ -59,117 +138,189 @@ class DateLUTImpl 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. + /// All in OffsetChangeFactor (15 minute) intervals. + Int8 amount_of_offset_change_value; /// Usually -4 or 4, but look at Lord Howe Island. Multiply by OffsetChangeFactor + UInt8 time_at_offset_change_value; /// In seconds from beginning of the day. Multiply by OffsetChangeFactor + + inline Int32 amount_of_offset_change() const + { + return static_cast(amount_of_offset_change_value) * OffsetChangeFactor; + } + + inline UInt32 time_at_offset_change() const + { + return static_cast(time_at_offset_change_value) * OffsetChangeFactor; + } + + /// Since most of the modern timezones have a DST change aligned to 15 minutes, to save as much space as possible inside Value, + /// we are dividing any offset change related value by this factor before setting it to Value, + /// hence it has to be explicitly multiplied back by this factor before being used. + static constexpr UInt16 OffsetChangeFactor = 900; }; static_assert(sizeof(Values) == 16); private: - /// Lookup table is indexed by DayNum_t. + + /// Mask is all-ones to allow efficient protection against overflow. + static constexpr UInt32 date_lut_mask = 0x1ffff; + static_assert(date_lut_mask == DATE_LUT_SIZE - 1); + + /// Offset to epoch in days (ExtendedDayNum) of the first day in LUT. + /// "epoch" is the Unix Epoch (starts at unix timestamp zero) + static constexpr UInt32 daynum_offset_epoch = 16436; + static_assert(daynum_offset_epoch == (1970 - DATE_LUT_MIN_YEAR) * 365 + (1970 - DATE_LUT_MIN_YEAR / 4 * 4) / 4); + + /// Lookup table is indexed by LUTIndex. /// 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]; + Values lut[DATE_LUT_SIZE + 1]; - /// Year number after DATE_LUT_MIN_YEAR -> day num for start of year. - DayNum_t years_lut[DATE_LUT_YEARS]; + /// Year number after DATE_LUT_MIN_YEAR -> LUTIndex in lut for start of year. + LUTIndex 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_t 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. time_t offset_at_start_of_epoch; - bool offset_is_whole_number_of_hours_everytime; - - /// DateLUT only support local time after 1970-01-01 00:00:00, but MySQL/TiDB support UTC time after - /// 1970-01-01 00:00:00. In some timezone(like America/Chicago), UTC time 1970-01-01 may fall into - /// 1969-12-31 in local time, so need to support day 1969-12-31 - Values day_1969_12_31; + /// UTC offset at the beginning of the first supported year. + time_t offset_at_start_of_lut; + bool offset_is_whole_number_of_hours_during_epoch; /// 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_t findIndex(time_t t) const + inline LUTIndex findIndex(time_t t) const { /// First guess. - DayNum_t guess(t / 86400); + Int64 guess = (t / 86400) + daynum_offset_epoch; + + /// For negative time_t the integer division was rounded up, so the guess is offset by one. + if (unlikely(t < 0)) + --guess; + + if (guess < 0) + return LUTIndex(0); + if (guess >= DATE_LUT_SIZE) + return LUTIndex(DATE_LUT_SIZE - 1); /// 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_t(guess + 1)].date) - return guess; + if (t >= lut[guess].date) + { + if (guess + 1 >= DATE_LUT_SIZE || t < lut[guess + 1].date) + return LUTIndex(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 (offset_at_start_of_epoch >= 0) - return DayNum_t(guess + 1); + return LUTIndex(guess + 1); + } - return DayNum_t(guess - 1); + return LUTIndex(guess ? guess - 1 : 0); } - inline const Values & find(time_t t) const + inline LUTIndex toLUTIndex(DayNum d) const { - if (unlikely(t + offset_at_start_of_epoch < 0)) - return day_1969_12_31; - return lut[findIndex(t)]; + return LUTIndex{(d + daynum_offset_epoch) & date_lut_mask}; + } + + inline LUTIndex toLUTIndex(ExtendedDayNum d) const + { + return LUTIndex{static_cast(d + daynum_offset_epoch) & date_lut_mask}; + } + + inline LUTIndex toLUTIndex(time_t t) const + { + return findIndex(t); + } + + inline LUTIndex toLUTIndex(LUTIndex i) const + { + return i; + } + + template + inline const Values & find(DateOrTime v) const + { + return lut[toLUTIndex(v)]; + } + + template + static inline T roundDown(T x, Divisor divisor) + { + static_assert(std::is_integral_v && std::is_integral_v); + assert(divisor > 0); + + 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; } public: const std::string & getTimeZone() const { return time_zone; } - time_t getOffsetAtStartEpoch() const { - return offset_at_start_of_epoch; - } + + // Methods only for unit-testing, it makes very little sense to use it from user code. + auto getOffsetAtStartOfEpoch() const { return offset_at_start_of_epoch; } + auto getTimeOffsetAtStartOfLUT() const { return offset_at_start_of_lut; } /// 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; } + inline ExtendedDayNum toDayNum(ExtendedDayNum d) const + { + return d; + } - /// Round down to start of monday. - inline time_t toFirstDayOfWeek(time_t t) const + template + inline ExtendedDayNum toDayNum(DateOrTime v) const { - DayNum_t index = findIndex(t); - return lut[DayNum_t(index - (lut[index].day_of_week - 1))].date; + return ExtendedDayNum{static_cast(toLUTIndex(v).toUnderType() - daynum_offset_epoch)}; } - inline DayNum_t toFirstDayNumOfWeek(DayNum_t d) const + /// Round down to start of monday. + template + inline time_t toFirstDayOfWeek(DateOrTime v) const { - return DayNum_t(d - (lut[d].day_of_week - 1)); + const LUTIndex i = toLUTIndex(v); + return lut[i - (lut[i].day_of_week - 1)].date; } - inline DayNum_t toFirstDayNumOfWeek(time_t t) const + template + inline ExtendedDayNum toFirstDayNumOfWeek(DateOrTime v) const { - return toFirstDayNumOfWeek(toDayNum(t)); + const LUTIndex i = toLUTIndex(v); + return toDayNum(i - (lut[i].day_of_week - 1)); } /// Round down to start of month. - inline time_t toFirstDayOfMonth(time_t t) const + template + inline time_t toFirstDayOfMonth(DateOrTime v) const { - DayNum_t index = findIndex(t); - return lut[index - (lut[index].day_of_month - 1)].date; + const LUTIndex i = toLUTIndex(v); + return lut[i - (lut[i].day_of_month - 1)].date; } - inline DayNum_t toFirstDayNumOfMonth(DayNum_t d) const + template + inline ExtendedDayNum toFirstDayNumOfMonth(DateOrTime v) const { - return DayNum_t(d - (lut[d].day_of_month - 1)); + const LUTIndex i = toLUTIndex(v); + return toDayNum(i - (lut[i].day_of_month - 1)); } - inline DayNum_t toFirstDayNumOfMonth(time_t t) const + /// Round down to start of quarter. + template + inline ExtendedDayNum toFirstDayNumOfQuarter(DateOrTime v) const { - return toFirstDayNumOfMonth(toDayNum(t)); + return toDayNum(toFirstDayOfQuarterIndex(v)); } - /// Round down to start of quarter. - inline DayNum_t toFirstDayNumOfQuarter(DayNum_t d) const + template + inline LUTIndex toFirstDayOfQuarterIndex(DateOrTime v) const { - DayNum_t index = d; + LUTIndex index = toLUTIndex(v); size_t month_inside_quarter = (lut[index].month - 1) % 3; index -= lut[index].day_of_month; @@ -179,17 +330,13 @@ class DateLUTImpl --month_inside_quarter; } - return DayNum_t(index + 1); - } - - inline DayNum_t toFirstDayNumOfQuarter(time_t t) const - { - return toFirstDayNumOfQuarter(toDayNum(t)); + return index + 1; } - inline time_t toFirstDayOfQuarter(time_t t) const + template + inline time_t toFirstDayOfQuarter(DateOrTime v) const { - return fromDayNum(toFirstDayNumOfQuarter(t)); + return toDate(toFirstDayOfQuarterIndex(v)); } /// Round down to start of year. @@ -198,44 +345,47 @@ class DateLUTImpl return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date; } - inline DayNum_t toFirstDayNumOfYear(DayNum_t d) const + template + inline LUTIndex toFirstDayNumOfYearIndex(DateOrTime v) const { - return years_lut[lut[d].year - DATE_LUT_MIN_YEAR]; + return years_lut[lut[toLUTIndex(v)].year - DATE_LUT_MIN_YEAR]; } - inline DayNum_t toFirstDayNumOfYear(time_t t) const + template + inline ExtendedDayNum toFirstDayNumOfYear(DateOrTime v) const { - return toFirstDayNumOfYear(toDayNum(t)); + return toDayNum(toFirstDayNumOfYearIndex(v)); } inline time_t toFirstDayOfNextMonth(time_t t) const { - DayNum_t index = findIndex(t); + LUTIndex 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_t index = findIndex(t); + LUTIndex index = findIndex(t); index -= lut[index].day_of_month; return lut[index - (lut[index].day_of_month - 1)].date; } - inline UInt8 daysInMonth(DayNum_t d) const + template + inline UInt8 daysInMonth(DateOrTime value) const { - return lut[d].days_in_month; + const LUTIndex i = toLUTIndex(value); + return lut[i].days_in_month; } - inline UInt8 daysInMonth(time_t t) const + inline UInt8 daysInMonth(Int16 year, UInt8 month) const { - return find(t).days_in_month; - } + UInt16 idx = year - DATE_LUT_MIN_YEAR; + if (unlikely(idx >= DATE_LUT_YEARS)) + return 31; /// Implementation specific behaviour on overflow. - inline UInt8 daysInMonth(UInt16 year, UInt8 month) const - { /// 32 makes arithmetic more simple. - DayNum_t any_day_of_month = DayNum_t(years_lut[year - DATE_LUT_MIN_YEAR] + 32 * (month - 1)); + const auto any_day_of_month = years_lut[year - DATE_LUT_MIN_YEAR] + 32 * (month - 1); return lut[any_day_of_month].days_in_month; } @@ -243,89 +393,110 @@ class DateLUTImpl */ inline time_t toDateAndShift(time_t t, Int32 days) const { - return lut[DayNum_t(findIndex(t) + days)].date; + return lut[findIndex(t) + days].date; } inline time_t toTime(time_t t) const { - DayNum_t index = findIndex(t); - - if (unlikely(index == 0)) - return t + offset_at_start_of_epoch; + const LUTIndex index = findIndex(t); time_t res = t - lut[index].date; - if (res >= lut[index].time_at_offset_change) - res += lut[index].amount_of_offset_change; + 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 { - if (unlikely(t + offset_at_start_of_epoch < 0)) - { - /// this requires all the timezone does not have DTS in 1969-12-31 - return (t + offset_at_start_of_epoch + SECONDS_PER_DAY) / 3600; - } - DayNum_t index = findIndex(t); + const LUTIndex index = findIndex(t); - /// If it is not 1970 year (findIndex found nothing appropriate), - /// than limit number of hours to avoid insane results like 1970-01-01 89:28:15 - if (unlikely(index == 0)) - return static_cast((t + offset_at_start_of_epoch) / 3600) % 24; + time_t time = t - lut[index].date; - time_t res = t - lut[index].date; + if (time >= lut[index].time_at_offset_change()) + time += lut[index].amount_of_offset_change(); - /// Data is cleaned to avoid possibility of underflow. - if (res >= lut[index].time_at_offset_change) - res += lut[index].amount_of_offset_change; + unsigned res = time / 3600; - return res / 3600; + /// In case time was changed backwards at the start of next day, we will repeat the hour 23. + return res <= 23 ? res : 23; } - /** 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. + /** 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 + { + const LUTIndex 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[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 + /// 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(); - inline unsigned toSecond(time_t t) const { - if (unlikely(t + offset_at_start_of_epoch < 0)) - return (t + offset_at_start_of_epoch + SECONDS_PER_DAY) % 60; - return t % 60; + return res + offset_at_start_of_epoch; + } + + + inline unsigned toSecond(time_t t) const + { + auto res = t % 60; + if (likely(res >= 0)) + return res; + return res + 60; } inline unsigned toMinute(time_t t) const { - if (unlikely(t + offset_at_start_of_epoch < 0)) - { - return (t + offset_at_start_of_epoch + SECONDS_PER_DAY) / 60 % 60; - } - if (offset_is_whole_number_of_hours_everytime) + if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) return (t / 60) % 60; - time_t date = find(t).date; - return (t - date) / 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. + + 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(); + + return time / 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; } + /// NOTE: Assuming timezone offset is a multiple of 15 minutes. + inline time_t toStartOfMinute(time_t t) const { return roundDown(t, 60); } + inline time_t toStartOfFiveMinute(time_t t) const { return roundDown(t, 300); } + inline time_t toStartOfFifteenMinutes(time_t t) const { return roundDown(t, 900); } + + inline time_t toStartOfTenMinutes(time_t 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. + Int64 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_t toStartOfHour(time_t t) const { - if (offset_is_whole_number_of_hours_everytime) + if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) return t / 3600 * 3600; - time_t date = find(t).date; - /// Still can return wrong values for time at 1970-01-01 if the UTC offset was non-whole number of hours. + Int64 date = find(t).date; return date + (t - date) / 3600 * 3600; } @@ -337,107 +508,116 @@ class DateLUTImpl * because the same calendar day starts/ends at different timestamps in different time zones) */ - inline DayNum_t toDayNum(time_t t) const { return findIndex(t); } - inline time_t fromDayNum(DayNum_t d) const { return lut[d].date; } + inline time_t fromDayNum(DayNum d) const { return lut[toLUTIndex(d)].date; } + inline time_t fromDayNum(ExtendedDayNum d) const { return lut[toLUTIndex(d)].date; } - inline time_t toDate(DayNum_t d) const { return lut[d].date; } - inline unsigned toMonth(DayNum_t d) const { return lut[d].month; } - inline unsigned toQuarter(DayNum_t d) const { return (lut[d].month - 1) / 3 + 1; } - inline unsigned toYear(DayNum_t d) const { return lut[d].year; } - inline unsigned toDayOfWeek(DayNum_t d) const { return lut[d].day_of_week; } - inline unsigned toDayOfMonth(DayNum_t d) const { return lut[d].day_of_month; } - inline unsigned toDayOfYear(DayNum_t d) const { return d + 1 - toFirstDayNumOfYear(d); } + template + inline time_t toDate(DateOrTime v) const { return lut[toLUTIndex(v)].date; } - inline unsigned toDayOfYear(time_t t) const { return toDayOfYear(toDayNum(t)); } + template + inline unsigned toMonth(DateOrTime v) const { return lut[toLUTIndex(v)].month; } - /// Number of week from some fixed moment in the past. Week begins at monday. - /// (round down to monday and divide DayNum_t 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_t d) const + template + inline unsigned toQuarter(DateOrTime v) const { return (lut[toLUTIndex(v)].month - 1) / 3 + 1; } + + template + inline Int16 toYear(DateOrTime v) const { return lut[toLUTIndex(v)].year; } + + template + inline unsigned toDayOfWeek(DateOrTime v) const { return lut[toLUTIndex(v)].day_of_week; } + + template + inline unsigned toDayOfMonth(DateOrTime v) const { return lut[toLUTIndex(v)].day_of_month; } + + template + inline unsigned toDayOfYear(DateOrTime v) const { - /// We add 8 to avoid underflow at beginning of unix epoch. - return (d + 8 - toDayOfWeek(d)) / 7; + // TODO: different overload for ExtendedDayNum + const LUTIndex i = toLUTIndex(v); + return i + 1 - toFirstDayNumOfYearIndex(i); } - inline unsigned toRelativeWeekNum(time_t t) const + /// 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) + template + inline unsigned toRelativeWeekNum(DateOrTime v) const { - return toRelativeWeekNum(toDayNum(t)); + const LUTIndex i = toLUTIndex(v); + /// We add 8 to avoid underflow at beginning of unix epoch. + return toDayNum(i + 8 - toDayOfWeek(i)) / 7; } /// Get year that contains most of the current week. Week begins at monday. - inline unsigned toISOYear(DayNum_t d) const + template + inline unsigned toISOYear(DateOrTime v) const { + const LUTIndex i = toLUTIndex(v); /// That's effectively the year of thursday of current week. - return toYear(DayNum_t(d + 4 - toDayOfWeek(d))); - } - - inline unsigned toISOYear(time_t t) const - { - return toISOYear(toDayNum(t)); + return toYear(toLUTIndex(i + 4 - toDayOfWeek(i))); } /// 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_t toFirstDayNumOfISOYear(DayNum_t d) const + template + inline LUTIndex toFirstDayNumOfISOYearIndex(DateOrTime v) const { - auto iso_year = toISOYear(d); + const LUTIndex i = toLUTIndex(v); + auto iso_year = toISOYear(i); - DayNum_t first_day_of_year = years_lut[iso_year - DATE_LUT_MIN_YEAR]; + const auto 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_t(first_day_of_week_of_year <= 4 + return LUTIndex{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); + : first_day_of_year + 8 - first_day_of_week_of_year}; } - inline DayNum_t toFirstDayNumOfISOYear(time_t t) const + template + inline ExtendedDayNum toFirstDayNumOfISOYear(DateOrTime v) const { - return toFirstDayNumOfISOYear(toDayNum(t)); + return toDayNum(toFirstDayNumOfISOYearIndex(v)); } inline time_t toFirstDayOfISOYear(time_t t) const { - return fromDayNum(toFirstDayNumOfISOYear(t)); + return lut[toFirstDayNumOfISOYearIndex(t)].date; } /// 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_t d) const - { - return 1 + DayNum_t(toFirstDayNumOfWeek(d) - toFirstDayNumOfISOYear(d)) / 7; - } - - inline unsigned toISOWeek(time_t t) const + template + inline unsigned toISOWeek(DateOrTime v) const { - return toISOWeek(toDayNum(t)); + return 1 + (toFirstDayNumOfWeek(v) - toFirstDayNumOfISOYear(v)) / 7; } /* 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 + 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 - releveant if WeekModeFlag::JANUARY is not set. + relevant if WeekModeFlag::JANUARY is not set. - If set Week is in range 1-53. + 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 + WeekModeFlag::FIRST_WEEKDAY (2) If not set Weeks are numbered according to ISO 8601:1988 - If set The week that contains the first + 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. + 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 @@ -446,30 +626,33 @@ class DateLUTImpl Otherwise it is the last week of the previous year, and the next week is week 1. */ - inline YearWeek toYearWeek(DayNum_t d, UInt8 week_mode) const + template + inline YearWeek toYearWeek(DateOrTime v, UInt8 week_mode) const { - bool newyear_day_mode = week_mode & static_cast(WeekModeFlag::NEWYEAR_DAY); + const bool newyear_day_mode = week_mode & static_cast(WeekModeFlag::NEWYEAR_DAY); week_mode = check_week_mode(week_mode); - bool monday_first_mode = week_mode & static_cast(WeekModeFlag::MONDAY_FIRST); + const bool monday_first_mode = week_mode & static_cast(WeekModeFlag::MONDAY_FIRST); bool week_year_mode = week_mode & static_cast(WeekModeFlag::YEAR); - bool first_weekday_mode = week_mode & static_cast(WeekModeFlag::FIRST_WEEKDAY); + const bool first_weekday_mode = week_mode & static_cast(WeekModeFlag::FIRST_WEEKDAY); + + const LUTIndex i = toLUTIndex(v); // Calculate week number of WeekModeFlag::NEWYEAR_DAY mode if (newyear_day_mode) { - return toYearWeekOfNewyearMode(d, monday_first_mode); + return toYearWeekOfNewyearMode(i, monday_first_mode); } - YearWeek yw(toYear(d), 0); + YearWeek yw(toYear(i), 0); UInt16 days = 0; - UInt16 daynr = makeDayNum(yw.first, toMonth(d), toDayOfMonth(d)); - UInt16 first_daynr = makeDayNum(yw.first, 1, 1); + const auto daynr = makeDayNum(yw.first, toMonth(i), toDayOfMonth(i)); + auto 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_t(first_daynr), !monday_first_mode); + UInt16 weekday = calc_weekday(first_daynr, !monday_first_mode); - if (toMonth(d) == 1 && toDayOfMonth(d) <= static_cast(7 - weekday)) + if (toMonth(i) == 1 && toDayOfMonth(i) <= static_cast(7 - weekday)) { if (!week_year_mode && ((first_weekday_mode && weekday != 0) || (!first_weekday_mode && weekday >= 4))) return yw; @@ -500,54 +683,55 @@ class DateLUTImpl /// 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_t d, bool monday_first_mode) const + template + inline YearWeek toYearWeekOfNewyearMode(DateOrTime v, bool monday_first_mode) const { YearWeek yw(0, 0); UInt16 offset_day = monday_first_mode ? 0U : 1U; + const LUTIndex i = LUTIndex(v); + // Checking the week across the year - yw.first = toYear(DayNum_t(d + 7 - toDayOfWeek(DayNum_t(d + offset_day)))); + yw.first = toYear(i + 7 - toDayOfWeek(i + offset_day)); - DayNum_t first_day = makeDayNum(yw.first, 1, 1); - DayNum_t this_day = d; + auto first_day = makeLUTIndex(yw.first, 1, 1); + auto this_day = i; + // TODO: do not perform calculations in terms of DayNum, since that would under/overflow for extended range. if (monday_first_mode) { // Rounds down a date to the nearest Monday. first_day = toFirstDayNumOfWeek(first_day); - this_day = toFirstDayNumOfWeek(d); + this_day = toFirstDayNumOfWeek(i); } else { // Rounds down a date to the nearest Sunday. if (toDayOfWeek(first_day) != 7) - first_day = DayNum_t(first_day - toDayOfWeek(first_day)); - if (toDayOfWeek(d) != 7) - this_day = DayNum_t(d - toDayOfWeek(d)); + first_day = ExtendedDayNum(first_day - toDayOfWeek(first_day)); + if (toDayOfWeek(i) != 7) + this_day = ExtendedDayNum(i - toDayOfWeek(i)); } yw.second = (this_day - first_day) / 7 + 1; return yw; } - /** - * get first day of week with week_mode, return Sunday or Monday - */ - inline DayNum_t toFirstDayNumOfWeek(DayNum_t d, UInt8 week_mode) const + /// Get first day of week with week_mode, return Sunday or Monday + template + inline ExtendedDayNum toFirstDayNumOfWeek(DateOrTime v, UInt8 week_mode) const { bool monday_first_mode = week_mode & static_cast(WeekModeFlag::MONDAY_FIRST); if (monday_first_mode) { - return toFirstDayNumOfWeek(d); + return toFirstDayNumOfWeek(v); } else { - return (toDayOfWeek(d) != 7) ? DayNum_t(d - toDayOfWeek(d)) : d; + return (toDayOfWeek(v) != 7) ? ExtendedDayNum(v - toDayOfWeek(v)) : toDayNum(v); } } - /* - * check and change mode to effective - */ + /// Check and change mode to effective. inline UInt8 check_week_mode(UInt8 mode) const { UInt8 week_format = (mode & 7); @@ -556,211 +740,321 @@ class DateLUTImpl return week_format; } - /* - * Calc weekday from d - * Returns 0 for monday, 1 for tuesday ... - */ - inline unsigned calc_weekday(DayNum_t d, bool sunday_first_day_of_week) const + /** Calculate weekday from d. + * Returns 0 for monday, 1 for tuesday... + */ + template + inline unsigned calc_weekday(DateOrTime v, bool sunday_first_day_of_week) const { + const LUTIndex i = toLUTIndex(v); if (!sunday_first_day_of_week) - return toDayOfWeek(d) - 1; + return toDayOfWeek(i) - 1; else - return toDayOfWeek(DayNum_t(d + 1)) - 1; + return toDayOfWeek(i + 1) - 1; } - /* Calc days in one year. */ - inline unsigned calc_days_in_year(UInt16 year) const + /// Calculate days in one year. + inline unsigned calc_days_in_year(Int32 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_t d) const - { - return lut[d].year * 12 + lut[d].month; - } - - inline unsigned toRelativeMonthNum(time_t t) const + template + inline unsigned toRelativeMonthNum(DateOrTime v) const { - return toRelativeMonthNum(toDayNum(t)); + const LUTIndex i = toLUTIndex(v); + return lut[i].year * 12 + lut[i].month; } - inline unsigned toRelativeQuarterNum(DayNum_t d) const + template + inline unsigned toRelativeQuarterNum(DateOrTime v) const { - return lut[d].year * 4 + (lut[d].month - 1) / 3; - } - - inline unsigned toRelativeQuarterNum(time_t t) const - { - return toRelativeQuarterNum(toDayNum(t)); + const LUTIndex i = toLUTIndex(v); + return lut[i].year * 4 + (lut[i].month - 1) / 3; } /// 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) + if (t >= 0 && offset_is_whole_number_of_hours_during_epoch) 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" time zone. - return (t + 86400 - offset_at_start_of_epoch) / 3600; + /// NOTE This assumption is false for "Pacific/Pitcairn" and "Pacific/Kiritimati" time zones. + return (t + DATE_LUT_ADD + 86400 - offset_at_start_of_epoch) / 3600 - (DATE_LUT_ADD / 3600); } - inline time_t toRelativeHourNum(DayNum_t d) const + template + inline time_t toRelativeHourNum(DateOrTime v) const { - return toRelativeHourNum(lut[d].date); + return toRelativeHourNum(lut[toLUTIndex(v)].date); } inline time_t toRelativeMinuteNum(time_t t) const { - return t / 60; + return (t + DATE_LUT_ADD) / 60 - (DATE_LUT_ADD / 60); } - inline time_t toRelativeMinuteNum(DayNum_t d) const + template + inline time_t toRelativeMinuteNum(DateOrTime v) const { - return toRelativeMinuteNum(lut[d].date); + return toRelativeMinuteNum(lut[toLUTIndex(v)].date); } - inline DayNum_t toStartOfYearInterval(DayNum_t d, UInt64 years) const + template + inline ExtendedDayNum toStartOfYearInterval(DateOrTime v, UInt64 years) const { if (years == 1) - return toFirstDayNumOfYear(d); - return years_lut[(lut[d].year - DATE_LUT_MIN_YEAR) / years * years]; + return toFirstDayNumOfYear(v); + + const LUTIndex i = toLUTIndex(v); + + UInt16 year = lut[i].year / years * years; + + /// For example, rounding down 1925 to 100 years will be 1900, but it's less than min supported year. + if (unlikely(year < DATE_LUT_MIN_YEAR)) + year = DATE_LUT_MIN_YEAR; + + return toDayNum(years_lut[year - DATE_LUT_MIN_YEAR]); } - inline DayNum_t toStartOfQuarterInterval(DayNum_t d, UInt64 quarters) const + inline ExtendedDayNum toStartOfQuarterInterval(ExtendedDayNum d, UInt64 quarters) const { if (quarters == 1) return toFirstDayNumOfQuarter(d); return toStartOfMonthInterval(d, quarters * 3); } - inline DayNum_t toStartOfMonthInterval(DayNum_t d, UInt64 months) const + inline ExtendedDayNum toStartOfMonthInterval(ExtendedDayNum 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]; + const Values & values = lut[toLUTIndex(d)]; + UInt32 month_total_index = (values.year - DATE_LUT_MIN_YEAR) * 12 + values.month - 1; + return toDayNum(years_months_lut[month_total_index / months * months]); } - inline DayNum_t toStartOfWeekInterval(DayNum_t d, UInt64 weeks) const + inline ExtendedDayNum toStartOfWeekInterval(ExtendedDayNum 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_t(4 + (d - 4) / days * days); + return ExtendedDayNum(4 + (d - 4) / days * days); } - inline time_t toStartOfDayInterval(DayNum_t d, UInt64 days) const + inline time_t toStartOfDayInterval(ExtendedDayNum d, UInt64 days) const { if (days == 1) return toDate(d); - return lut[d / days * days].date; + return lut[toLUTIndex(ExtendedDayNum(d / days * days))].date; } inline time_t toStartOfHourInterval(time_t t, UInt64 hours) const { 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 = t / seconds * seconds; - if (offset_is_whole_number_of_hours_everytime) - return t; - return toStartOfHour(t); + + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; + + 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; + + /// Should subtract 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 { 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 t / seconds * seconds; + return roundDown(t, seconds); } inline time_t toStartOfSecondInterval(time_t t, UInt64 seconds) const { if (seconds == 1) return t; - return t / seconds * seconds; + + return roundDown(t, seconds); + } + + inline LUTIndex makeLUTIndex(Int16 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 LUTIndex(0); + + return LUTIndex{years_months_lut[(year - DATE_LUT_MIN_YEAR) * 12 + month - 1] + day_of_month - 1}; } - /// Create DayNum_t from year, month, day of month. - inline DayNum_t makeDayNum(UInt16 year, UInt8 month, UInt8 day_of_month) const + /// Create DayNum from year, month, day of month. + inline ExtendedDayNum makeDayNum(Int16 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_t(0); + return ExtendedDayNum(0); - return DayNum_t(years_months_lut[(year - DATE_LUT_MIN_YEAR) * 12 + month - 1] + day_of_month - 1); + return toDayNum(makeLUTIndex(year, month, day_of_month)); } - inline time_t makeDate(UInt16 year, UInt8 month, UInt8 day_of_month) const + inline time_t makeDate(Int16 year, UInt8 month, UInt8 day_of_month) const { - return lut[makeDayNum(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. */ - inline time_t makeDateTime(UInt16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const + inline time_t makeDateTime(Int16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const { - size_t index = makeDayNum(year, month, day_of_month); - time_t time_offset = hour * 3600 + minute * 60 + second; + size_t index = makeLUTIndex(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; + if (time_offset >= lut[index].time_at_offset_change()) + time_offset -= lut[index].amount_of_offset_change(); return lut[index].date + time_offset; } - inline const Values & getValues(DayNum_t d) const { return lut[d]; } - inline const Values & getValues(time_t t) const { return lut[findIndex(t)]; } + template + inline const Values & getValues(DateOrTime v) const { return lut[toLUTIndex(v)]; } - inline UInt32 toNumYYYYMM(time_t t) const + template + inline UInt32 toNumYYYYMM(DateOrTime v) const { - const Values & values = find(t); + const Values & values = getValues(v); return values.year * 100 + values.month; } - inline UInt32 toNumYYYYMM(DayNum_t d) const + template + inline UInt32 toNumYYYYMMDD(DateOrTime v) const { - const Values & values = lut[d]; - return values.year * 100 + values.month; + const Values & values = getValues(v); + return values.year * 10000 + values.month * 100 + values.day_of_month; } - inline UInt32 toNumYYYYMMDD(time_t t) const + inline time_t YYYYMMDDToDate(UInt32 num) const { - const Values & values = find(t); - return values.year * 10000 + values.month * 100 + values.day_of_month; + return makeDate(num / 10000, num / 100 % 100, num % 100); } - inline UInt32 toNumYYYYMMDD(DayNum_t d) const + inline ExtendedDayNum YYYYMMDDToDayNum(UInt32 num) const { - const Values & values = lut[d]; - return values.year * 10000 + values.month * 100 + values.day_of_month; + return makeDayNum(num / 10000, num / 100 % 100, num % 100); } - inline time_t YYYYMMDDToDate(UInt32 num) const + + struct DateComponents { - return makeDate(num / 10000, num / 100 % 100, num % 100); + uint16_t year; + uint8_t month; + uint8_t day; + }; + + struct TimeComponents + { + uint8_t hour; + uint8_t minute; + uint8_t second; + }; + + struct DateTimeComponents + { + DateComponents date; + TimeComponents time; + }; + + inline DateComponents toDateComponents(time_t t) const + { + const Values & values = getValues(t); + return { values.year, values.month, values.day_of_month }; } - inline DayNum_t YYYYMMDDToDayNum(UInt32 num) const + inline DateTimeComponents toDateTimeComponents(time_t t) const { - return makeDayNum(num / 10000, num / 100 % 100, num % 100); + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; + + DateTimeComponents res; + + res.date.year = values.year; + res.date.month = values.month; + res.date.day = values.day_of_month; + + time_t time = t - values.date; + if (time >= values.time_at_offset_change()) + time += values.amount_of_offset_change(); + + if (unlikely(time < 0)) + { + res.time.second = 0; + res.time.minute = 0; + res.time.hour = 0; + } + else + { + res.time.second = time % 60; + res.time.minute = time / 60 % 60; + res.time.hour = time / 3600; + } + + /// In case time was changed backwards at the start of next day, we will repeat the hour 23. + if (unlikely(res.time.hour > 23)) + res.time.hour = 23; + + return res; } inline UInt64 toNumYYYYMMDDhhmmss(time_t t) const { - const Values & values = find(t); + DateTimeComponents components = toDateTimeComponents(t); + return - toSecond(t) - + toMinute(t) * 100 - + toHour(t) * 10000 - + UInt64(values.day_of_month) * 1000000 - + UInt64(values.month) * 100000000 - + UInt64(values.year) * 10000000000; + components.time.second + + components.time.minute * 100 + + components.time.hour * 10000 + + UInt64(components.date.day) * 1000000 + + UInt64(components.date.month) * 100000000 + + UInt64(components.date.year) * 10000000000; } inline time_t YYYYMMDDhhmmssToTime(UInt64 num) const @@ -777,25 +1071,29 @@ class DateLUTImpl /// Adding calendar intervals. /// Implementation specific behaviour when delta is too big. - inline time_t addDays(time_t t, Int64 delta) const + inline NO_SANITIZE_UNDEFINED time_t addDays(time_t t, Int64 delta) const { - DayNum_t index = findIndex(t); - time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t); + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; - index += delta; + time_t time = t - values.date; + if (time >= values.time_at_offset_change()) + time += values.amount_of_offset_change(); - if (time_offset >= lut[index].time_at_offset_change) - time_offset -= lut[index].amount_of_offset_change; + const LUTIndex new_index = index + delta; - return lut[index].date + time_offset; + if (time >= lut[new_index].time_at_offset_change()) + time -= lut[new_index].amount_of_offset_change(); + + return lut[new_index].date + time; } - inline time_t addWeeks(time_t t, Int64 delta) const + 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 + inline UInt8 saturateDayOfMonth(Int16 year, UInt8 month, UInt8 day_of_month) const { if (likely(day_of_month <= 28)) return day_of_month; @@ -808,25 +1106,12 @@ class DateLUTImpl 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_t 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 DayNum_t addMonths(DayNum_t d, Int64 delta) const + template + inline LUTIndex NO_SANITIZE_UNDEFINED addMonthsIndex(DateOrTime v, Int64 delta) const { - const Values & values = lut[d]; + const Values & values = lut[toLUTIndex(v)]; - Int64 month = static_cast(values.month) + delta; + Int64 month = values.month + delta; if (month > 0) { @@ -834,7 +1119,7 @@ class DateLUTImpl month = ((month - 1) % 12) + 1; auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month); - return makeDayNum(year, month, day_of_month); + return makeLUTIndex(year, month, day_of_month); } else { @@ -842,36 +1127,48 @@ class DateLUTImpl month = 12 - (-month % 12); auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month); - return makeDayNum(year, month, day_of_month); + return makeLUTIndex(year, month, day_of_month); } } - inline time_t addQuarters(time_t t, Int64 delta) const + /// If resulting month has less deys than source month, then saturation can happen. + /// Example: 31 Aug + 1 month = 30 Sep. + inline time_t NO_SANITIZE_UNDEFINED addMonths(time_t t, Int64 delta) const { - return addMonths(t, delta * 3); + const auto result_day = addMonthsIndex(t, delta); + + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; + + time_t time = t - values.date; + if (time >= values.time_at_offset_change()) + time += values.amount_of_offset_change(); + + if (time >= lut[result_day].time_at_offset_change()) + time -= lut[result_day].amount_of_offset_change(); + + return lut[result_day].date + time; } - inline DayNum_t addQuarters(DayNum_t d, Int64 delta) const + inline ExtendedDayNum NO_SANITIZE_UNDEFINED addMonths(ExtendedDayNum d, Int64 delta) const { - return addMonths(d, delta * 3); + return toDayNum(addMonthsIndex(d, delta)); } - /// Saturation can occur if 29 Feb is mapped to non-leap year. - inline time_t addYears(time_t t, Int64 delta) const + inline time_t NO_SANITIZE_UNDEFINED addQuarters(time_t t, Int64 delta) const { - DayNum_t 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 addMonths(t, delta * 3); + } - return lut[result_day].date + time_offset; + inline ExtendedDayNum addQuarters(ExtendedDayNum d, Int64 delta) const + { + return addMonths(d, delta * 3); } - inline DayNum_t addYears(DayNum_t d, Int64 delta) const + template + inline LUTIndex NO_SANITIZE_UNDEFINED addYearsIndex(DateOrTime v, Int64 delta) const { - const Values & values = lut[d]; + const Values & values = lut[toLUTIndex(v)]; auto year = values.year + delta; auto month = values.month; @@ -881,42 +1178,61 @@ class DateLUTImpl if (unlikely(day_of_month == 29 && month == 2)) day_of_month = saturateDayOfMonth(year, month, day_of_month); - return makeDayNum(year, month, day_of_month); + return makeLUTIndex(year, month, day_of_month); } + /// Saturation can occur if 29 Feb is mapped to non-leap year. + inline time_t addYears(time_t t, Int64 delta) const + { + auto result_day = addYearsIndex(t, delta); + + const LUTIndex index = findIndex(t); + const Values & values = lut[index]; - inline std::string timeToString(time_t t) const + time_t time = t - values.date; + if (time >= values.time_at_offset_change()) + time += values.amount_of_offset_change(); + + if (time >= lut[result_day].time_at_offset_change()) + time -= lut[result_day].amount_of_offset_change(); + + return lut[result_day].date + time; + } + + inline ExtendedDayNum addYears(ExtendedDayNum d, Int64 delta) const { - const Values & values = find(t); + return toDayNum(addYearsIndex(d, delta)); + } - 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; + inline std::string timeToString(time_t t) const + { + DateTimeComponents components = toDateTimeComponents(t); - auto hour = toHour(t); - auto minute = toMinute(t); - auto second = toSecond(t); + std::string s {"0000-00-00 00:00:00"}; - s[11] += hour / 10; - s[12] += hour % 10; - s[14] += minute / 10; - s[15] += minute % 10; - s[17] += second / 10; - s[18] += second % 10; + s[0] += components.date.year / 1000; + s[1] += (components.date.year / 100) % 10; + s[2] += (components.date.year / 10) % 10; + s[3] += components.date.year % 10; + s[5] += components.date.month / 10; + s[6] += components.date.month % 10; + s[8] += components.date.day / 10; + s[9] += components.date.day % 10; + + s[11] += components.time.hour / 10; + s[12] += components.time.hour % 10; + s[14] += components.time.minute / 10; + s[15] += components.time.minute % 10; + s[17] += components.time.second / 10; + s[18] += components.time.second % 10; return s; } inline std::string dateToString(time_t t) const { - const Values & values = find(t); + const Values & values = getValues(t); std::string s {"0000-00-00"}; @@ -932,9 +1248,9 @@ class DateLUTImpl return s; } - inline std::string dateToString(DayNum_t d) const + inline std::string dateToString(ExtendedDayNum d) const { - const Values & values = lut[d]; + const Values & values = getValues(d); std::string s {"0000-00-00"}; @@ -952,7 +1268,7 @@ class DateLUTImpl }; #if defined(__PPC__) -#if !__clang__ +#if !defined(__clang__) #pragma GCC diagnostic pop #endif #endif diff --git a/libs/libcommon/include/common/DayNum.h b/libs/libcommon/include/common/DayNum.h index 9957fdbe39d..d93718fe3ac 100644 --- a/libs/libcommon/include/common/DayNum.h +++ b/libs/libcommon/include/common/DayNum.h @@ -6,4 +6,9 @@ /** Represents number of days since 1970-01-01. * See DateLUTImpl for usage examples. */ -STRONG_TYPEDEF(UInt16, DayNum_t) +STRONG_TYPEDEF(UInt16, DayNum) + +/** Represent number of days since 1970-01-01 but in extended range, + * for dates before 1970-01-01 and after 2105 + */ +STRONG_TYPEDEF(Int32, ExtendedDayNum) diff --git a/libs/libcommon/include/common/LocalDate.h b/libs/libcommon/include/common/LocalDate.h index 8eddc6f9115..b1e6eeb907c 100644 --- a/libs/libcommon/include/common/LocalDate.h +++ b/libs/libcommon/include/common/LocalDate.h @@ -62,7 +62,7 @@ class LocalDate init(time); } - LocalDate(DayNum_t day_num) + LocalDate(DayNum day_num) { const auto & values = DateLUT::instance().getValues(day_num); m_year = values.year; @@ -92,23 +92,13 @@ class LocalDate LocalDate(const LocalDate &) noexcept = default; LocalDate & operator= (const LocalDate &) noexcept = default; - LocalDate & operator= (time_t time) + DayNum getDayNum() const { - init(time); - return *this; - } - - operator time_t() const - { - return DateLUT::instance().makeDate(m_year, m_month, m_day); - } - - DayNum_t getDayNum() const - { - return DateLUT::instance().makeDayNum(m_year, m_month, m_day); + const auto & lut = DateLUT::instance(); + return DayNum(lut.makeDayNum(m_year, m_month, m_day).toUnderType()); } - operator DayNum_t() const + operator DayNum() const { return getDayNum(); } @@ -165,18 +155,4 @@ class LocalDate } }; -inline std::ostream & operator<< (std::ostream & ostr, const LocalDate & date) -{ - return ostr << date.year() - << '-' << (date.month() / 10) << (date.month() % 10) - << '-' << (date.day() / 10) << (date.day() % 10); -} - - -namespace std -{ -inline string to_string(const LocalDate & date) -{ - return date.toString(); -} -} +static_assert(sizeof(LocalDate) == 4); diff --git a/libs/libcommon/include/common/LocalDateTime.h b/libs/libcommon/include/common/LocalDateTime.h index d0c208edadc..dde283e5ebb 100644 --- a/libs/libcommon/include/common/LocalDateTime.h +++ b/libs/libcommon/include/common/LocalDateTime.h @@ -14,8 +14,6 @@ * * When local time was shifted backwards (due to daylight saving time or whatever reason) * - then to resolve the ambiguity of transforming to time_t, lowest of two possible values is selected. - * - * packed - for memcmp to work naturally (but because m_year is 2 bytes, on little endian, comparison is correct only before year 2047) */ class LocalDateTime { @@ -27,29 +25,22 @@ class LocalDateTime unsigned char m_minute; unsigned char m_second; - void init(time_t time) + /// For struct to fill 8 bytes and for safe invocation of memcmp. + /// NOTE We may use attribute packed instead, but it is less portable. + unsigned char pad = 0; + + void init(time_t time, const DateLUTImpl & time_zone) { - if (unlikely(time > DATE_LUT_MAX || time == 0)) - { - m_year = 0; - m_month = 0; - m_day = 0; - m_hour = 0; - m_minute = 0; - m_second = 0; - - return; - } - - const auto & date_lut = DateLUT::instance(); - const auto & values = date_lut.getValues(time); - - m_year = values.year; - m_month = values.month; - m_day = values.day_of_month; - m_hour = date_lut.toHour(time); - m_minute = date_lut.toMinute(time); - m_second = date_lut.toSecond(time); + DateLUTImpl::DateTimeComponents components = time_zone.toDateTimeComponents(time); + + m_year = components.date.year; + m_month = components.date.month; + m_day = components.date.day; + m_hour = components.time.hour; + m_minute = components.time.minute; + m_second = components.time.second; + + (void)pad; /// Suppress unused private field warning. } void init(const char * s, size_t length) @@ -64,12 +55,14 @@ class LocalDateTime m_hour = (s[11] - '0') * 10 + (s[12] - '0'); m_minute = (s[14] - '0') * 10 + (s[15] - '0'); m_second = (s[17] - '0') * 10 + (s[18] - '0'); + + (void)pad; } public: - explicit LocalDateTime(time_t time) + explicit LocalDateTime(time_t time, const DateLUTImpl & time_zone = DateLUT::instance()) { - init(time); + init(time, time_zone); } LocalDateTime(unsigned short year_, unsigned char month_, unsigned char day_, @@ -98,19 +91,6 @@ class LocalDateTime LocalDateTime(const LocalDateTime &) noexcept = default; LocalDateTime & operator= (const LocalDateTime &) noexcept = default; - LocalDateTime & operator= (time_t time) - { - init(time); - return *this; - } - - operator time_t() const - { - return m_year == 0 - ? 0 - : DateLUT::instance().makeDateTime(m_year, m_month, m_day, m_hour, m_minute, m_second); - } - unsigned short year() const { return m_year; } unsigned char month() const { return m_month; } unsigned char day() const { return m_day; } @@ -126,8 +106,30 @@ class LocalDateTime void second(unsigned char x) { m_second = x; } LocalDate toDate() const { return LocalDate(m_year, m_month, m_day); } + LocalDateTime toStartOfDate() const { return LocalDateTime(m_year, m_month, m_day, 0, 0, 0); } - LocalDateTime toStartOfDate() { return LocalDateTime(m_year, m_month, m_day, 0, 0, 0); } + std::string toString() const + { + std::string s{"0000-00-00 00:00:00"}; + + s[0] += m_year / 1000; + s[1] += (m_year / 100) % 10; + s[2] += (m_year / 10) % 10; + s[3] += m_year % 10; + s[5] += m_month / 10; + s[6] += m_month % 10; + s[8] += m_day / 10; + s[9] += m_day % 10; + + s[11] += m_hour / 10; + s[12] += m_hour % 10; + s[14] += m_minute / 10; + s[15] += m_minute % 10; + s[17] += m_second / 10; + s[18] += m_second % 10; + + return s; + } bool operator< (const LocalDateTime & other) const { @@ -160,26 +162,4 @@ class LocalDateTime } }; -inline std::ostream & operator<< (std::ostream & ostr, const LocalDateTime & datetime) -{ - ostr << std::setfill('0') << std::setw(4) << datetime.year(); - - ostr << '-' << (datetime.month() / 10) << (datetime.month() % 10) - << '-' << (datetime.day() / 10) << (datetime.day() % 10) - << ' ' << (datetime.hour() / 10) << (datetime.hour() % 10) - << ':' << (datetime.minute() / 10) << (datetime.minute() % 10) - << ':' << (datetime.second() / 10) << (datetime.second() % 10); - - return ostr; -} - - -namespace std -{ -inline string to_string(const LocalDateTime & datetime) -{ - stringstream str; - str << datetime; - return str.str(); -} -} +static_assert(sizeof(LocalDateTime) == 8); diff --git a/libs/libcommon/src/DateLUT.cpp b/libs/libcommon/src/DateLUT.cpp index b6015b06b8e..e7eada1bca5 100644 --- a/libs/libcommon/src/DateLUT.cpp +++ b/libs/libcommon/src/DateLUT.cpp @@ -1,9 +1,10 @@ -#include +#include "common/DateLUT.h" #include #include #include #include + #include @@ -151,7 +152,13 @@ const DateLUTImpl & DateLUT::getImplementation(const std::string & time_zone) co auto it = impls.emplace(time_zone, nullptr).first; if (!it->second) - it->second = std::make_unique(time_zone); + it->second = std::unique_ptr(new DateLUTImpl(time_zone)); return *it->second; } + +DateLUT & DateLUT::getInstance() +{ + static DateLUT ret; + return ret; +} diff --git a/libs/libcommon/src/DateLUTImpl.cpp b/libs/libcommon/src/DateLUTImpl.cpp index 349a0af2641..c05a3d4bf8a 100644 --- a/libs/libcommon/src/DateLUTImpl.cpp +++ b/libs/libcommon/src/DateLUTImpl.cpp @@ -1,24 +1,16 @@ -#if __has_include() -#include // bundled, debian -#else -#include // freebsd -#endif +#include -#if __has_include() +#include #include -#else -#include -#endif - -#include +#include #include -#include +#include +#include #include #include #include - -#define DATE_LUT_MIN 0 +#include namespace @@ -36,35 +28,58 @@ UInt8 getDayOfWeek(const cctz::civil_day & date) case cctz::weekday::friday: return 5; case cctz::weekday::saturday: return 6; case cctz::weekday::sunday: return 7; - default: - throw Poco::Exception("Logical error: incorrect week day."); } + __builtin_unreachable(); } } +__attribute__((__weak__)) extern bool inside_main; + DateLUTImpl::DateLUTImpl(const std::string & time_zone_) : time_zone(time_zone_) { - size_t i = 0; - time_t start_of_day = DATE_LUT_MIN; + /// DateLUT should not be initialized in global constructors for the following reasons: + /// 1. It is too heavy. + if (&inside_main) + assert(inside_main); cctz::time_zone cctz_time_zone; - if (!cctz::load_time_zone(time_zone.data(), &cctz_time_zone)) + if (!cctz::load_time_zone(time_zone, &cctz_time_zone)) throw Poco::Exception("Cannot load time zone " + time_zone_); - cctz::time_zone::absolute_lookup start_of_epoch_lookup = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t(start_of_day)); - offset_at_start_of_epoch = start_of_epoch_lookup.offset; - offset_is_whole_number_of_hours_everytime = true; + constexpr cctz::civil_day epoch{1970, 1, 1}; + constexpr cctz::civil_day lut_start{DATE_LUT_MIN_YEAR, 1, 1}; + time_t start_of_day; - cctz::civil_day date{1970, 1, 1}; + /// Note: it's validated against all timezones in the system. + static_assert((epoch - lut_start) == daynum_offset_epoch); + offset_at_start_of_epoch = cctz_time_zone.lookup(cctz_time_zone.lookup(epoch).pre).offset; + offset_at_start_of_lut = cctz_time_zone.lookup(cctz_time_zone.lookup(lut_start).pre).offset; + offset_is_whole_number_of_hours_during_epoch = true; + + cctz::civil_day date = lut_start; + + UInt32 i = 0; do { cctz::time_zone::civil_lookup lookup = cctz_time_zone.lookup(date); - start_of_day = std::chrono::system_clock::to_time_t(lookup.pre); /// Ambiguity is possible. + /// Ambiguity is possible if time was changed backwards at the midnight + /// or after midnight time has been changed back to midnight, for example one hour backwards at 01:00 + /// or after midnight time has been changed to the previous day, for example two hours backwards at 01:00 + /// Then midnight appears twice. Usually time change happens exactly at 00:00 or 01:00. + + /// If transition did not involve previous day, we should use the first midnight as the start of the day, + /// otherwise it's better to use the second midnight. + + std::chrono::time_point start_of_day_time_point = lookup.trans < lookup.post + ? lookup.post /* Second midnight appears after transition, so there was a piece of previous day after transition */ + : lookup.pre; + + start_of_day = std::chrono::system_clock::to_time_t(start_of_day_time_point); Values & values = lut[i]; values.year = date.year(); @@ -73,6 +88,11 @@ DateLUTImpl::DateLUTImpl(const std::string & time_zone_) values.day_of_week = getDayOfWeek(date); values.date = start_of_day; + assert(values.year >= DATE_LUT_MIN_YEAR && values.year <= DATE_LUT_MAX_YEAR + 1); + assert(values.month >= 1 && values.month <= 12); + assert(values.day_of_month >= 1 && values.day_of_month <= 31); + assert(values.day_of_week >= 1 && values.day_of_week <= 7); + if (values.day_of_month == 1) { cctz::civil_month month(date); @@ -81,60 +101,55 @@ DateLUTImpl::DateLUTImpl(const std::string & time_zone_) else values.days_in_month = i != 0 ? lut[i - 1].days_in_month : 31; - values.time_at_offset_change = 0; - values.amount_of_offset_change = 0; + values.time_at_offset_change_value = 0; + values.amount_of_offset_change_value = 0; - if (start_of_day % 3600) - offset_is_whole_number_of_hours_everytime = false; + if (offset_is_whole_number_of_hours_during_epoch && start_of_day > 0 && start_of_day % 3600) + offset_is_whole_number_of_hours_during_epoch = false; - /// If UTC offset was changed in previous day. - if (i != 0) + /// If UTC offset was changed this day. + /// Change in time zone without transition is possible, e.g. Moscow 1991 Sun, 31 Mar, 02:00 MSK to EEST + cctz::time_zone::civil_transition transition{}; + if (cctz_time_zone.next_transition(start_of_day_time_point - std::chrono::seconds(1), &transition) + && (cctz::civil_day(transition.from) == date || cctz::civil_day(transition.to) == date) + && transition.from != transition.to) { - auto amount_of_offset_change_at_prev_day = 86400 - (lut[i].date - lut[i - 1].date); - if (amount_of_offset_change_at_prev_day) - { - lut[i - 1].amount_of_offset_change = amount_of_offset_change_at_prev_day; - - const auto utc_offset_at_beginning_of_day = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t(lut[i - 1].date)).offset; - - /// Find a time (timestamp offset from beginning of day), - /// when UTC offset was changed. Search is performed with 15-minute granularity, assuming it is enough. - - time_t time_at_offset_change = 900; - while (time_at_offset_change < 86400) - { - auto utc_offset_at_current_time = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t( - lut[i - 1].date + time_at_offset_change)).offset; - - if (utc_offset_at_current_time != utc_offset_at_beginning_of_day) - break; - - time_at_offset_change += 900; - } - - lut[i - 1].time_at_offset_change = time_at_offset_change; - - /// We doesn't support cases when time change results in switching to previous day. - if (static_cast(lut[i - 1].time_at_offset_change) + static_cast(lut[i - 1].amount_of_offset_change) < 0) - lut[i - 1].time_at_offset_change = -lut[i - 1].amount_of_offset_change; - } + values.time_at_offset_change_value = (transition.from - cctz::civil_second(date)) / Values::OffsetChangeFactor; + values.amount_of_offset_change_value = (transition.to - transition.from) / Values::OffsetChangeFactor; + +// std::cerr << time_zone << ", " << date << ": change from " << transition.from << " to " << transition.to << "\n"; +// std::cerr << time_zone << ", " << date << ": change at " << values.time_at_offset_change() << " with " << values.amount_of_offset_change() << "\n"; + + /// We don't support too large changes. + if (values.amount_of_offset_change_value > 24 * 4) + values.amount_of_offset_change_value = 24 * 4; + else if (values.amount_of_offset_change_value < -24 * 4) + values.amount_of_offset_change_value = -24 * 4; + + /// We don't support cases when time change results in switching to previous day. + /// Shift the point of time change later. + if (values.time_at_offset_change_value + values.amount_of_offset_change_value < 0) + values.time_at_offset_change_value = -values.amount_of_offset_change_value; } /// Going to next day. ++date; ++i; } - while (start_of_day <= DATE_LUT_MAX && i <= DATE_LUT_MAX_DAY_NUM); + while (i < DATE_LUT_SIZE && lut[i - 1].year <= DATE_LUT_MAX_YEAR); /// Fill excessive part of lookup table. This is needed only to simplify handling of overflow cases. while (i < DATE_LUT_SIZE) { - lut[i] = lut[DATE_LUT_MAX_DAY_NUM]; + lut[i] = lut[i - 1]; ++i; } /// Fill lookup table for years and months. - for (size_t day = 0; day < DATE_LUT_SIZE && lut[day].year <= DATE_LUT_MAX_YEAR; ++day) + size_t year_months_lut_index = 0; + size_t first_day_of_last_month = 0; + + for (size_t day = 0; day < DATE_LUT_SIZE; ++day) { const Values & values = lut[day]; @@ -142,15 +157,17 @@ DateLUTImpl::DateLUTImpl(const std::string & time_zone_) { if (values.month == 1) years_lut[values.year - DATE_LUT_MIN_YEAR] = day; - years_months_lut[(values.year - DATE_LUT_MIN_YEAR) * 12 + values.month - 1] = day; + + year_months_lut_index = (values.year - DATE_LUT_MIN_YEAR) * 12 + values.month - 1; + years_months_lut[year_months_lut_index] = day; + first_day_of_last_month = day; } } - day_1969_12_31.year = 1969; - day_1969_12_31.month = 12; - day_1969_12_31.day_of_month = 31; - day_1969_12_31.days_in_month = 31; - // day of week starts from 1 - day_1969_12_31.day_of_week = (lut[0].day_of_week - 1 - 1 + 7) % 7 + 1; - day_1969_12_31.time_at_offset_change = 0; - day_1969_12_31.amount_of_offset_change = 0; + + /// Fill the rest of lookup table with the same last month (2106-02-01). + for (; year_months_lut_index < DATE_LUT_YEARS * 12; ++year_months_lut_index) + { + years_months_lut[year_months_lut_index] = first_day_of_last_month; + } } +