// Copyright 2023 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include namespace { UInt8 getDayOfWeek(const cctz::civil_day & date) { cctz::weekday day_of_week = cctz::get_weekday(date); switch (day_of_week) { case cctz::weekday::monday: return 1; case cctz::weekday::tuesday: return 2; case cctz::weekday::wednesday: return 3; case cctz::weekday::thursday: return 4; case cctz::weekday::friday: return 5; case cctz::weekday::saturday: return 6; case cctz::weekday::sunday: return 7; } __builtin_unreachable(); } } // namespace DateLUTImpl::DateLUTImpl(const std::string & time_zone_) : time_zone(time_zone_) { cctz::time_zone cctz_time_zone; if (!cctz::load_time_zone(time_zone, &cctz_time_zone)) throw Poco::Exception("Cannot load time zone " + time_zone_); 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; /// 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); /// 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(); values.month = date.month(); values.day_of_month = date.day(); 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); values.days_in_month = cctz::civil_day(month + 1) - cctz::civil_day(month); } else values.days_in_month = i != 0 ? lut[i - 1].days_in_month : 31; values.time_at_offset_change_value = 0; values.amount_of_offset_change_value = 0; 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 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) { 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 (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[i - 1]; ++i; } /// Fill lookup table for years and months. 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]; if (values.day_of_month == 1) { if (values.month == 1) years_lut[values.year - DATE_LUT_MIN_YEAR] = 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; } } /// 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; } }