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 |