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
25namespace
26{
27
28UInt8 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
50DateLUTImpl::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