/src/muduo/muduo/base/TimeZone.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Use of this source code is governed by a BSD-style license |
2 | | // that can be found in the License file. |
3 | | // |
4 | | // Author: Shuo Chen (chenshuo at chenshuo dot com) |
5 | | |
6 | | #include "muduo/base/TimeZone.h" |
7 | | #include "muduo/base/noncopyable.h" |
8 | | #include "muduo/base/Date.h" |
9 | | |
10 | | #include <algorithm> |
11 | | #include <memory> |
12 | | #include <stdexcept> |
13 | | #include <string> |
14 | | #include <vector> |
15 | | |
16 | | #include <assert.h> |
17 | | //#define _BSD_SOURCE |
18 | | #include <endian.h> |
19 | | |
20 | | #include <stdint.h> |
21 | | #include <stdio.h> |
22 | | |
23 | | using namespace muduo; |
24 | | |
25 | | struct TimeZone::Data |
26 | | { |
27 | | struct Transition |
28 | | { |
29 | | int64_t utctime; |
30 | | int64_t localtime; // Shifted Epoch |
31 | | int localtimeIdx; |
32 | | |
33 | | Transition(int64_t t, int64_t l, int localIdx) |
34 | | : utctime(t), localtime(l), localtimeIdx(localIdx) |
35 | 0 | { } |
36 | | }; |
37 | | |
38 | | struct LocalTime |
39 | | { |
40 | | int32_t utcOffset; // East of UTC |
41 | | bool isDst; |
42 | | int desigIdx; |
43 | | |
44 | | LocalTime(int32_t offset, bool dst, int idx) |
45 | | : utcOffset(offset), isDst(dst), desigIdx(idx) |
46 | 0 | { } |
47 | | }; |
48 | | |
49 | | void addLocalTime(int32_t utcOffset, bool isDst, int desigIdx) |
50 | 0 | { |
51 | 0 | localtimes.push_back(LocalTime(utcOffset, isDst, desigIdx)); |
52 | 0 | } |
53 | | |
54 | | void addTransition(int64_t utcTime, int localtimeIdx) |
55 | 0 | { |
56 | 0 | LocalTime lt = localtimes.at(localtimeIdx); |
57 | 0 | transitions.push_back(Transition(utcTime, utcTime + lt.utcOffset, localtimeIdx)); |
58 | 0 | } |
59 | | |
60 | | const LocalTime* findLocalTime(int64_t utcTime) const; |
61 | | const LocalTime* findLocalTime(const struct DateTime& local, bool postTransition) const; |
62 | | |
63 | | struct CompareUtcTime |
64 | | { |
65 | | bool operator()(const Transition& lhs, const Transition& rhs) const |
66 | 0 | { |
67 | 0 | return lhs.utctime < rhs.utctime; |
68 | 0 | } |
69 | | }; |
70 | | |
71 | | struct CompareLocalTime |
72 | | { |
73 | | bool operator()(const Transition& lhs, const Transition& rhs) const |
74 | 0 | { |
75 | 0 | return lhs.localtime < rhs.localtime; |
76 | 0 | } |
77 | | }; |
78 | | |
79 | | std::vector<Transition> transitions; |
80 | | std::vector<LocalTime> localtimes; |
81 | | string abbreviation; |
82 | | string tzstring; |
83 | | }; |
84 | | |
85 | | namespace muduo |
86 | | { |
87 | | |
88 | | const int kSecondsPerDay = 24*60*60; |
89 | | |
90 | | namespace detail |
91 | | { |
92 | | |
93 | | class File : noncopyable |
94 | | { |
95 | | public: |
96 | | File(const char* file) |
97 | | : fp_(::fopen(file, "rb")) |
98 | 0 | { |
99 | 0 | } |
100 | | |
101 | | ~File() |
102 | 0 | { |
103 | 0 | if (fp_) |
104 | 0 | { |
105 | 0 | ::fclose(fp_); |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | 0 | bool valid() const { return fp_; } |
110 | | |
111 | | string readBytes(int n) |
112 | 0 | { |
113 | 0 | char buf[n]; |
114 | 0 | ssize_t nr = ::fread(buf, 1, n, fp_); |
115 | 0 | if (nr != n) |
116 | 0 | throw std::logic_error("no enough data"); |
117 | 0 | return string(buf, n); |
118 | 0 | } |
119 | | |
120 | | string readToEnd() |
121 | 0 | { |
122 | 0 | char buf[4096]; |
123 | 0 | string result; |
124 | 0 | ssize_t nr = 0; |
125 | 0 | while ( (nr = ::fread(buf, 1, sizeof buf, fp_)) > 0) |
126 | 0 | { |
127 | 0 | result.append(buf, nr); |
128 | 0 | } |
129 | 0 | return result; |
130 | 0 | } |
131 | | |
132 | | int64_t readInt64() |
133 | 0 | { |
134 | 0 | int64_t x = 0; |
135 | 0 | ssize_t nr = ::fread(&x, 1, sizeof(int64_t), fp_); |
136 | 0 | if (nr != sizeof(int64_t)) |
137 | 0 | throw std::logic_error("bad int64_t data"); |
138 | 0 | return be64toh(x); |
139 | 0 | } |
140 | | |
141 | | int32_t readInt32() |
142 | 0 | { |
143 | 0 | int32_t x = 0; |
144 | 0 | ssize_t nr = ::fread(&x, 1, sizeof(int32_t), fp_); |
145 | 0 | if (nr != sizeof(int32_t)) |
146 | 0 | throw std::logic_error("bad int32_t data"); |
147 | 0 | return be32toh(x); |
148 | 0 | } |
149 | | |
150 | | uint8_t readUInt8() |
151 | 0 | { |
152 | 0 | uint8_t x = 0; |
153 | 0 | ssize_t nr = ::fread(&x, 1, sizeof(uint8_t), fp_); |
154 | 0 | if (nr != sizeof(uint8_t)) |
155 | 0 | throw std::logic_error("bad uint8_t data"); |
156 | 0 | return x; |
157 | 0 | } |
158 | | |
159 | | off_t skip(ssize_t bytes) |
160 | 0 | { |
161 | 0 | return ::fseek(fp_, bytes, SEEK_CUR); |
162 | 0 | } |
163 | | |
164 | | private: |
165 | | FILE* fp_; |
166 | | }; |
167 | | |
168 | | // RFC 8536: https://www.rfc-editor.org/rfc/rfc8536.html |
169 | | bool readDataBlock(File& f, struct TimeZone::Data* data, bool v1) |
170 | 0 | { |
171 | 0 | const int time_size = v1 ? sizeof(int32_t) : sizeof(int64_t); |
172 | 0 | const int32_t isutccnt = f.readInt32(); |
173 | 0 | const int32_t isstdcnt = f.readInt32(); |
174 | 0 | const int32_t leapcnt = f.readInt32(); |
175 | 0 | const int32_t timecnt = f.readInt32(); |
176 | 0 | const int32_t typecnt = f.readInt32(); |
177 | 0 | const int32_t charcnt = f.readInt32(); |
178 | |
|
179 | 0 | if (leapcnt != 0) |
180 | 0 | return false; |
181 | 0 | if (isutccnt != 0 && isutccnt != typecnt) |
182 | 0 | return false; |
183 | 0 | if (isstdcnt != 0 && isstdcnt != typecnt) |
184 | 0 | return false; |
185 | | |
186 | 0 | std::vector<int64_t> trans; |
187 | 0 | trans.reserve(timecnt); |
188 | 0 | for (int i = 0; i < timecnt; ++i) |
189 | 0 | { |
190 | 0 | if (v1) |
191 | 0 | { |
192 | 0 | trans.push_back(f.readInt32()); |
193 | 0 | } |
194 | 0 | else |
195 | 0 | { |
196 | 0 | trans.push_back(f.readInt64()); |
197 | 0 | } |
198 | 0 | } |
199 | |
|
200 | 0 | std::vector<int> localtimes; |
201 | 0 | localtimes.reserve(timecnt); |
202 | 0 | for (int i = 0; i < timecnt; ++i) |
203 | 0 | { |
204 | 0 | uint8_t local = f.readUInt8(); |
205 | 0 | localtimes.push_back(local); |
206 | 0 | } |
207 | |
|
208 | 0 | data->localtimes.reserve(typecnt); |
209 | 0 | for (int i = 0; i < typecnt; ++i) |
210 | 0 | { |
211 | 0 | int32_t gmtoff = f.readInt32(); |
212 | 0 | uint8_t isdst = f.readUInt8(); |
213 | 0 | uint8_t abbrind = f.readUInt8(); |
214 | |
|
215 | 0 | data->addLocalTime(gmtoff, isdst, abbrind); |
216 | 0 | } |
217 | |
|
218 | 0 | for (int i = 0; i < timecnt; ++i) |
219 | 0 | { |
220 | 0 | int localIdx = localtimes[i]; |
221 | 0 | data->addTransition(trans[i], localIdx); |
222 | 0 | } |
223 | |
|
224 | 0 | data->abbreviation = f.readBytes(charcnt); |
225 | 0 | f.skip(leapcnt * (time_size + 4)); |
226 | 0 | f.skip(isstdcnt); |
227 | 0 | f.skip(isutccnt); |
228 | |
|
229 | 0 | if (!v1) |
230 | 0 | { |
231 | | // FIXME: read to next new-line. |
232 | 0 | data->tzstring = f.readToEnd(); |
233 | 0 | } |
234 | |
|
235 | 0 | return true; |
236 | 0 | } |
237 | | |
238 | | bool readTimeZoneFile(const char* zonefile, struct TimeZone::Data* data) |
239 | 0 | { |
240 | 0 | File f(zonefile); |
241 | 0 | if (f.valid()) |
242 | 0 | { |
243 | 0 | try |
244 | 0 | { |
245 | 0 | string head = f.readBytes(4); |
246 | 0 | if (head != "TZif") |
247 | 0 | throw std::logic_error("bad head"); |
248 | 0 | string version = f.readBytes(1); |
249 | 0 | f.readBytes(15); |
250 | |
|
251 | 0 | const int32_t isgmtcnt = f.readInt32(); |
252 | 0 | const int32_t isstdcnt = f.readInt32(); |
253 | 0 | const int32_t leapcnt = f.readInt32(); |
254 | 0 | const int32_t timecnt = f.readInt32(); |
255 | 0 | const int32_t typecnt = f.readInt32(); |
256 | 0 | const int32_t charcnt = f.readInt32(); |
257 | |
|
258 | 0 | if (version == "2") |
259 | 0 | { |
260 | 0 | size_t skip = sizeof(int32_t) * timecnt + timecnt + 6 * typecnt + |
261 | 0 | charcnt + 8 * leapcnt + isstdcnt + isgmtcnt; |
262 | 0 | f.skip(skip); |
263 | |
|
264 | 0 | head = f.readBytes(4); |
265 | 0 | if (head != "TZif") |
266 | 0 | throw std::logic_error("bad head"); |
267 | 0 | f.skip(16); |
268 | 0 | return readDataBlock(f, data, false); |
269 | 0 | } |
270 | 0 | else |
271 | 0 | { |
272 | | // TODO: Test with real v1 file. |
273 | 0 | f.skip(-4 * 6); // Rewind to counters |
274 | 0 | return readDataBlock(f, data, true); |
275 | 0 | } |
276 | 0 | } |
277 | 0 | catch (std::logic_error& e) |
278 | 0 | { |
279 | 0 | fprintf(stderr, "%s\n", e.what()); |
280 | 0 | } |
281 | 0 | } |
282 | 0 | return false; |
283 | 0 | } |
284 | | |
285 | | inline void fillHMS(unsigned seconds, struct DateTime* dt) |
286 | 0 | { |
287 | 0 | dt->second = seconds % 60; |
288 | 0 | unsigned minutes = seconds / 60; |
289 | 0 | dt->minute = minutes % 60; |
290 | 0 | dt->hour = minutes / 60; |
291 | 0 | } |
292 | | |
293 | | DateTime BreakTime(int64_t t) |
294 | 0 | { |
295 | 0 | struct DateTime dt; |
296 | 0 | int seconds = static_cast<int>(t % kSecondsPerDay); |
297 | 0 | int days = static_cast<int>(t / kSecondsPerDay); |
298 | | // C++11 rounds towards zero. |
299 | 0 | if (seconds < 0) |
300 | 0 | { |
301 | 0 | seconds += kSecondsPerDay; |
302 | 0 | --days; |
303 | 0 | } |
304 | 0 | detail::fillHMS(seconds, &dt); |
305 | 0 | Date date(days + Date::kJulianDayOf1970_01_01); |
306 | 0 | Date::YearMonthDay ymd = date.yearMonthDay(); |
307 | 0 | dt.year = ymd.year; |
308 | 0 | dt.month = ymd.month; |
309 | 0 | dt.day = ymd.day; |
310 | |
|
311 | 0 | return dt; |
312 | 0 | } |
313 | | |
314 | | } // namespace detail |
315 | | |
316 | | } // namespace muduo |
317 | | |
318 | | const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(int64_t utcTime) const |
319 | 0 | { |
320 | 0 | const LocalTime* local = NULL; |
321 | | |
322 | | // row UTC time isdst offset Local time (PRC) |
323 | | // 1 1989-09-16 17:00:00Z 0 8.0 1989-09-17 01:00:00 |
324 | | // 2 1990-04-14 18:00:00Z 1 9.0 1990-04-15 03:00:00 |
325 | | // 3 1990-09-15 17:00:00Z 0 8.0 1990-09-16 01:00:00 |
326 | | // 4 1991-04-13 18:00:00Z 1 9.0 1991-04-14 03:00:00 |
327 | | // 5 1991-09-14 17:00:00Z 0 8.0 1991-09-15 01:00:00 |
328 | | |
329 | | // input '1990-06-01 00:00:00Z', std::upper_bound returns row 3, |
330 | | // so the input is in range of row 2, offset is 9 hours, |
331 | | // local time is 1990-06-01 09:00:00 |
332 | 0 | if (transitions.empty() || utcTime < transitions.front().utctime) |
333 | 0 | { |
334 | | // FIXME: should be first non dst time zone |
335 | 0 | local = &localtimes.front(); |
336 | 0 | } |
337 | 0 | else |
338 | 0 | { |
339 | 0 | Transition sentry(utcTime, 0, 0); |
340 | 0 | std::vector<Transition>::const_iterator transI = |
341 | 0 | std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareUtcTime()); |
342 | 0 | assert(transI != transitions.begin()); |
343 | 0 | if (transI != transitions.end()) |
344 | 0 | { |
345 | 0 | --transI; |
346 | 0 | local = &localtimes[transI->localtimeIdx]; |
347 | 0 | } |
348 | 0 | else |
349 | 0 | { |
350 | | // FIXME: use TZ-env |
351 | 0 | local = &localtimes[transitions.back().localtimeIdx]; |
352 | 0 | } |
353 | 0 | } |
354 | |
|
355 | 0 | return local; |
356 | 0 | } |
357 | | |
358 | | const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime( |
359 | | const struct DateTime& lt, bool postTransition) const |
360 | 0 | { |
361 | 0 | const int64_t localtime = fromUtcTime(lt); |
362 | |
|
363 | 0 | if (transitions.empty() || localtime < transitions.front().localtime) |
364 | 0 | { |
365 | | // FIXME: should be first non dst time zone |
366 | 0 | return &localtimes.front(); |
367 | 0 | } |
368 | | |
369 | 0 | Transition sentry(0, localtime, 0); |
370 | 0 | std::vector<Transition>::const_iterator transI = |
371 | 0 | std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareLocalTime()); |
372 | 0 | assert(transI != transitions.begin()); |
373 | |
|
374 | 0 | if (transI == transitions.end()) |
375 | 0 | { |
376 | | // FIXME: use TZ-env |
377 | 0 | return &localtimes[transitions.back().localtimeIdx]; |
378 | 0 | } |
379 | | |
380 | 0 | Transition prior_trans = *(transI - 1); |
381 | 0 | int64_t prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset; |
382 | | |
383 | | // row UTC time isdst offset Local time (PRC) Prior second local time |
384 | | // 1 1989-09-16 17:00:00Z 0 8.0 1989-09-17 01:00:00 |
385 | | // 2 1990-04-14 18:00:00Z 1 9.0 1990-04-15 03:00:00 1990-04-15 01:59:59 |
386 | | // 3 1990-09-15 17:00:00Z 0 8.0 1990-09-16 01:00:00 1990-09-16 01:59:59 |
387 | | // 4 1991-04-13 18:00:00Z 1 9.0 1991-04-14 03:00:00 1991-04-14 01:59:59 |
388 | | // 5 1991-09-14 17:00:00Z 0 8.0 1991-09-15 01:00:00 |
389 | | |
390 | | // input 1991-04-14 02:30:00, found row 4, |
391 | | // 4 1991-04-13 18:00:00Z 1 9.0 1991-04-14 03:00:00 1991-04-14 01:59:59 |
392 | 0 | if (prior_second < localtime) |
393 | 0 | { |
394 | | // it's a skip |
395 | | // printf("SKIP: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime); |
396 | 0 | if (postTransition) |
397 | 0 | { |
398 | 0 | return &localtimes[transI->localtimeIdx]; |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | return &localtimes[prior_trans.localtimeIdx]; |
403 | 0 | } |
404 | 0 | } |
405 | | |
406 | | // input 1990-09-16 01:30:00, found row 4, looking at row 3 |
407 | | // 3 1990-09-15 17:00:00Z 0 8.0 1990-09-16 01:00:00 1990-09-16 01:59:59 |
408 | 0 | --transI; |
409 | 0 | if (transI != transitions.begin()) |
410 | 0 | { |
411 | 0 | prior_trans = *(transI - 1); |
412 | 0 | prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset; |
413 | 0 | } |
414 | 0 | if (localtime <= prior_second) |
415 | 0 | { |
416 | | // it's repeat |
417 | | // printf("REPEAT: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime); |
418 | 0 | if (postTransition) |
419 | 0 | { |
420 | 0 | return &localtimes[transI->localtimeIdx]; |
421 | 0 | } |
422 | 0 | else |
423 | 0 | { |
424 | 0 | return &localtimes[prior_trans.localtimeIdx]; |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | // otherwise, it's unique |
429 | 0 | return &localtimes[transI->localtimeIdx]; |
430 | 0 | } |
431 | | |
432 | | // static |
433 | | TimeZone TimeZone::UTC() |
434 | 0 | { |
435 | 0 | return TimeZone(0, "UTC"); |
436 | 0 | } |
437 | | |
438 | | // static |
439 | | TimeZone TimeZone::loadZoneFile(const char* zonefile) |
440 | 0 | { |
441 | 0 | std::unique_ptr<Data> data(new Data); |
442 | 0 | if (!detail::readTimeZoneFile(zonefile, data.get())) |
443 | 0 | { |
444 | 0 | data.reset(); |
445 | 0 | } |
446 | 0 | return TimeZone(std::move(data)); |
447 | 0 | } |
448 | | |
449 | | TimeZone::TimeZone(std::unique_ptr<Data> data) |
450 | | : data_(std::move(data)) |
451 | 0 | { |
452 | 0 | } |
453 | | |
454 | | TimeZone::TimeZone(int eastOfUtc, const char* name) |
455 | | : data_(new TimeZone::Data) |
456 | 0 | { |
457 | 0 | data_->addLocalTime(eastOfUtc, false, 0); |
458 | 0 | data_->abbreviation = name; |
459 | 0 | } |
460 | | |
461 | | struct DateTime TimeZone::toLocalTime(int64_t seconds, int* utcOffset) const |
462 | 0 | { |
463 | 0 | struct DateTime localTime; |
464 | 0 | assert(data_ != NULL); |
465 | |
|
466 | 0 | const Data::LocalTime* local = data_->findLocalTime(seconds); |
467 | |
|
468 | 0 | if (local) |
469 | 0 | { |
470 | 0 | localTime = detail::BreakTime(seconds + local->utcOffset); |
471 | 0 | if (utcOffset) |
472 | 0 | { |
473 | 0 | *utcOffset = local->utcOffset; |
474 | 0 | } |
475 | 0 | } |
476 | |
|
477 | 0 | return localTime; |
478 | 0 | } |
479 | | |
480 | | int64_t TimeZone::fromLocalTime(const struct DateTime& localtime, bool postTransition) const |
481 | 0 | { |
482 | 0 | assert(data_ != NULL); |
483 | 0 | const Data::LocalTime* local = data_->findLocalTime(localtime, postTransition); |
484 | 0 | const int64_t localSeconds = fromUtcTime(localtime); |
485 | 0 | if (local) |
486 | 0 | { |
487 | 0 | return localSeconds - local->utcOffset; |
488 | 0 | } |
489 | | // fallback as if it's UTC time. |
490 | 0 | return localSeconds; |
491 | 0 | } |
492 | | |
493 | | DateTime TimeZone::toUtcTime(int64_t secondsSinceEpoch) |
494 | 0 | { |
495 | 0 | return detail::BreakTime(secondsSinceEpoch); |
496 | 0 | } |
497 | | |
498 | | int64_t TimeZone::fromUtcTime(const DateTime& dt) |
499 | 0 | { |
500 | 0 | Date date(dt.year, dt.month, dt.day); |
501 | 0 | int secondsInDay = dt.hour * 3600 + dt.minute * 60 + dt.second; |
502 | 0 | int64_t days = date.julianDayNumber() - Date::kJulianDayOf1970_01_01; |
503 | 0 | return days * kSecondsPerDay + secondsInDay; |
504 | 0 | } |
505 | | |
506 | | |
507 | | DateTime::DateTime(const struct tm& t) |
508 | | : year(t.tm_year + 1900), month(t.tm_mon + 1), day(t.tm_mday), |
509 | | hour(t.tm_hour), minute(t.tm_min), second(t.tm_sec) |
510 | 0 | { |
511 | 0 | } |
512 | | |
513 | | string DateTime::toIsoString() const |
514 | 0 | { |
515 | 0 | char buf[64]; |
516 | 0 | snprintf(buf, sizeof buf, "%04d-%02d-%02d %02d:%02d:%02d", |
517 | 0 | year, month, day, hour, minute, second); |
518 | 0 | return buf; |
519 | 0 | } |