| 1 | #if __has_include(<cctz/civil_time.h>) |
|---|---|
| 2 | #include <cctz/civil_time.h> // bundled, debian |
| 3 | #else |
| 4 | #include <civil_time.h> // freebsd |
| 5 | #endif |
| 6 | |
| 7 | #if __has_include(<cctz/time_zone.h>) |
| 8 | #include <cctz/time_zone.h> |
| 9 | #else |
| 10 | #include <time_zone.h> |
| 11 | #endif |
| 12 | |
| 13 | #include <common/DateLUTImpl.h> |
| 14 | #include <Poco/Exception.h> |
| 15 | |
| 16 | #include <memory> |
| 17 | #include <chrono> |
| 18 | #include <cstring> |
| 19 | #include <cassert> |
| 20 | #include <iostream> |
| 21 | |
| 22 | #define DATE_LUT_MIN 0 |
| 23 | |
| 24 | |
| 25 | namespace |
| 26 | { |
| 27 | |
| 28 | UInt8 getDayOfWeek(const cctz::civil_day & date) |
| 29 | { |
| 30 | cctz::weekday day_of_week = cctz::get_weekday(date); |
| 31 | switch (day_of_week) |
| 32 | { |
| 33 | case cctz::weekday::monday: return 1; |
| 34 | case cctz::weekday::tuesday: return 2; |
| 35 | case cctz::weekday::wednesday: return 3; |
| 36 | case cctz::weekday::thursday: return 4; |
| 37 | case cctz::weekday::friday: return 5; |
| 38 | case cctz::weekday::saturday: return 6; |
| 39 | case cctz::weekday::sunday: return 7; |
| 40 | default: |
| 41 | throw Poco::Exception("Logical error: incorrect week day."); |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | } |
| 46 | |
| 47 | |
| 48 | __attribute__((__weak__)) extern bool inside_main; |
| 49 | |
| 50 | DateLUTImpl::DateLUTImpl(const std::string & time_zone_) |
| 51 | : time_zone(time_zone_) |
| 52 | { |
| 53 | /// DateLUT should not be initialized in global constructors for the following reasons: |
| 54 | /// 1. It is too heavy. |
| 55 | if (&inside_main) |
| 56 | assert(inside_main); |
| 57 | |
| 58 | size_t i = 0; |
| 59 | time_t start_of_day = DATE_LUT_MIN; |
| 60 | |
| 61 | cctz::time_zone cctz_time_zone; |
| 62 | if (!cctz::load_time_zone(time_zone.data(), &cctz_time_zone)) |
| 63 | throw Poco::Exception("Cannot load time zone "+ time_zone_); |
| 64 | |
| 65 | cctz::time_zone::absolute_lookup start_of_epoch_lookup = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t(start_of_day)); |
| 66 | offset_at_start_of_epoch = start_of_epoch_lookup.offset; |
| 67 | offset_is_whole_number_of_hours_everytime = true; |
| 68 | |
| 69 | cctz::civil_day date{1970, 1, 1}; |
| 70 | |
| 71 | do |
| 72 | { |
| 73 | cctz::time_zone::civil_lookup lookup = cctz_time_zone.lookup(date); |
| 74 | |
| 75 | start_of_day = std::chrono::system_clock::to_time_t(lookup.pre); /// Ambiguity is possible. |
| 76 | |
| 77 | Values & values = lut[i]; |
| 78 | values.year = date.year(); |
| 79 | values.month = date.month(); |
| 80 | values.day_of_month = date.day(); |
| 81 | values.day_of_week = getDayOfWeek(date); |
| 82 | values.date = start_of_day; |
| 83 | |
| 84 | if (values.day_of_month == 1) |
| 85 | { |
| 86 | cctz::civil_month month(date); |
| 87 | values.days_in_month = cctz::civil_day(month + 1) - cctz::civil_day(month); |
| 88 | } |
| 89 | else |
| 90 | values.days_in_month = i != 0 ? lut[i - 1].days_in_month : 31; |
| 91 | |
| 92 | values.time_at_offset_change = 0; |
| 93 | values.amount_of_offset_change = 0; |
| 94 | |
| 95 | if (start_of_day % 3600) |
| 96 | offset_is_whole_number_of_hours_everytime = false; |
| 97 | |
| 98 | /// If UTC offset was changed in previous day. |
| 99 | if (i != 0) |
| 100 | { |
| 101 | auto amount_of_offset_change_at_prev_day = 86400 - (lut[i].date - lut[i - 1].date); |
| 102 | if (amount_of_offset_change_at_prev_day) |
| 103 | { |
| 104 | lut[i - 1].amount_of_offset_change = amount_of_offset_change_at_prev_day; |
| 105 | |
| 106 | const auto utc_offset_at_beginning_of_day = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t(lut[i - 1].date)).offset; |
| 107 | |
| 108 | /// Find a time (timestamp offset from beginning of day), |
| 109 | /// when UTC offset was changed. Search is performed with 15-minute granularity, assuming it is enough. |
| 110 | |
| 111 | time_t time_at_offset_change = 900; |
| 112 | while (time_at_offset_change < 86400) |
| 113 | { |
| 114 | auto utc_offset_at_current_time = cctz_time_zone.lookup(std::chrono::system_clock::from_time_t( |
| 115 | lut[i - 1].date + time_at_offset_change)).offset; |
| 116 | |
| 117 | if (utc_offset_at_current_time != utc_offset_at_beginning_of_day) |
| 118 | break; |
| 119 | |
| 120 | time_at_offset_change += 900; |
| 121 | } |
| 122 | |
| 123 | lut[i - 1].time_at_offset_change = time_at_offset_change; |
| 124 | |
| 125 | /// We doesn't support cases when time change results in switching to previous day. |
| 126 | if (static_cast<int>(lut[i - 1].time_at_offset_change) + static_cast<int>(lut[i - 1].amount_of_offset_change) < 0) |
| 127 | lut[i - 1].time_at_offset_change = -lut[i - 1].amount_of_offset_change; |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | /// Going to next day. |
| 132 | ++date; |
| 133 | ++i; |
| 134 | } |
| 135 | while (start_of_day <= DATE_LUT_MAX && i <= DATE_LUT_MAX_DAY_NUM); |
| 136 | |
| 137 | /// Fill excessive part of lookup table. This is needed only to simplify handling of overflow cases. |
| 138 | while (i < DATE_LUT_SIZE) |
| 139 | { |
| 140 | lut[i] = lut[DATE_LUT_MAX_DAY_NUM]; |
| 141 | ++i; |
| 142 | } |
| 143 | |
| 144 | /// Fill lookup table for years and months. |
| 145 | for (size_t day = 0; day < DATE_LUT_SIZE && lut[day].year <= DATE_LUT_MAX_YEAR; ++day) |
| 146 | { |
| 147 | const Values & values = lut[day]; |
| 148 | |
| 149 | if (values.day_of_month == 1) |
| 150 | { |
| 151 | if (values.month == 1) |
| 152 | years_lut[values.year - DATE_LUT_MIN_YEAR] = day; |
| 153 | years_months_lut[(values.year - DATE_LUT_MIN_YEAR) * 12 + values.month - 1] = day; |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 |