comparison common/date/tz.cpp @ 0:a4671277546c tip

created the repository for the thymian project
author ferencd
date Tue, 17 Aug 2021 11:19:54 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:a4671277546c
1 // The MIT License (MIT)
2 //
3 // Copyright (c) 2015, 2016, 2017 Howard Hinnant
4 // Copyright (c) 2015 Ville Voutilainen
5 // Copyright (c) 2016 Alexander Kormanovsky
6 // Copyright (c) 2016, 2017 Jiangang Zhuang
7 // Copyright (c) 2017 Nicolas Veloz Savino
8 // Copyright (c) 2017 Florian Dang
9 // Copyright (c) 2017 Aaron Bishop
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in all
19 // copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 // SOFTWARE.
28 //
29 // Our apologies. When the previous paragraph was written, lowercase had not yet
30 // been invented (that would involve another several millennia of evolution).
31 // We did not mean to shout.
32
33 #ifdef _WIN32
34 // windows.h will be included directly and indirectly (e.g. by curl).
35 // We need to define these macros to prevent windows.h bringing in
36 // more than we need and do it early so windows.h doesn't get included
37 // without these macros having been defined.
38 // min/max macros interfere with the C++ versions.
39 # ifndef NOMINMAX
40 # define NOMINMAX
41 # endif
42 // We don't need all that Windows has to offer.
43 # ifndef WIN32_LEAN_AND_MEAN
44 # define WIN32_LEAN_AND_MEAN
45 # endif
46
47 // for wcstombs
48 # ifndef _CRT_SECURE_NO_WARNINGS
49 # define _CRT_SECURE_NO_WARNINGS
50 # endif
51
52 // None of this happens with the MS SDK (at least VS14 which I tested), but:
53 // Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope."
54 // and error: 'SHGetKnownFolderPath' was not declared in this scope.".
55 // It seems when using mingw NTDDI_VERSION is undefined and that
56 // causes KNOWN_FOLDER_FLAG and the KF_ flags to not get defined.
57 // So we must define NTDDI_VERSION to get those flags on mingw.
58 // The docs say though here:
59 // https://msdn.microsoft.com/en-nz/library/windows/desktop/aa383745(v=vs.85).aspx
60 // that "If you define NTDDI_VERSION, you must also define _WIN32_WINNT."
61 // So we declare we require Vista or greater.
62 # ifdef __MINGW32__
63
64 # ifndef NTDDI_VERSION
65 # define NTDDI_VERSION 0x06000000
66 # define _WIN32_WINNT _WIN32_WINNT_VISTA
67 # elif NTDDI_VERSION < 0x06000000
68 # warning "If this fails to compile NTDDI_VERSION may be to low. See comments above."
69 # endif
70 // But once we define the values above we then get this linker error:
71 // "tz.cpp:(.rdata$.refptr.FOLDERID_Downloads[.refptr.FOLDERID_Downloads]+0x0): "
72 // "undefined reference to `FOLDERID_Downloads'"
73 // which #include <initguid.h> cures see:
74 // https://support.microsoft.com/en-us/kb/130869
75 # include <initguid.h>
76 // But with <initguid.h> included, the error moves on to:
77 // error: 'FOLDERID_Downloads' was not declared in this scope
78 // Which #include <knownfolders.h> cures.
79 # include <knownfolders.h>
80
81 # endif // __MINGW32__
82
83 # include <windows.h>
84 #endif // _WIN32
85
86 #include "date/tz_private.h"
87
88 #ifdef __APPLE__
89 # include "date/ios.h"
90 #else
91 # define TARGET_OS_IPHONE 0
92 #endif
93
94 #if USE_OS_TZDB
95 # include <dirent.h>
96 #endif
97 #include <algorithm>
98 #include <cctype>
99 #include <cstdlib>
100 #include <cstring>
101 #include <cwchar>
102 #include <exception>
103 #include <fstream>
104 #include <iostream>
105 #include <iterator>
106 #include <memory>
107 #if USE_OS_TZDB
108 # include <queue>
109 #endif
110 #include <sstream>
111 #include <string>
112 #include <tuple>
113 #include <vector>
114 #include <sys/stat.h>
115
116 // unistd.h is used on some platforms as part of the the means to get
117 // the current time zone. On Win32 windows.h provides a means to do it.
118 // gcc/mingw supports unistd.h on Win32 but MSVC does not.
119
120 #ifdef _WIN32
121 # ifdef WINAPI_FAMILY
122 # include <winapifamily.h>
123 # if WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP
124 # define WINRT
125 # define INSTALL .
126 # endif
127 # endif
128
129 # include <io.h> // _unlink etc.
130
131 # if defined(__clang__)
132 struct IUnknown; // fix for issue with static_cast<> in objbase.h
133 // (see https://github.com/philsquared/Catch/issues/690)
134 # endif
135
136 # include <shlobj.h> // CoTaskFree, ShGetKnownFolderPath etc.
137 # if HAS_REMOTE_API
138 # include <direct.h> // _mkdir
139 # include <shellapi.h> // ShFileOperation etc.
140 # endif // HAS_REMOTE_API
141 #else // !_WIN32
142 # include <unistd.h>
143 # if !USE_OS_TZDB
144 # include <wordexp.h>
145 # endif
146 # include <limits.h>
147 # include <string.h>
148 # if !USE_SHELL_API
149 # include <sys/stat.h>
150 # include <sys/fcntl.h>
151 # include <dirent.h>
152 # include <cstring>
153 # include <sys/wait.h>
154 # include <sys/types.h>
155 # endif //!USE_SHELL_API
156 #endif // !_WIN32
157
158
159 #if HAS_REMOTE_API
160 // Note curl includes windows.h so we must include curl AFTER definitions of things
161 // that affect windows.h such as NOMINMAX.
162 #if defined(_MSC_VER) && defined(SHORTENED_CURL_INCLUDE)
163 // For rmt_curl nuget package
164 # include <curl.h>
165 #else
166 # include <curl/curl.h>
167 #endif
168 #endif
169
170 #ifdef _WIN32
171 static CONSTDATA char folder_delimiter = '\\';
172 #else // !_WIN32
173 static CONSTDATA char folder_delimiter = '/';
174 #endif // !_WIN32
175
176 #if defined(__GNUC__) && __GNUC__ < 5
177 // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers
178 # pragma GCC diagnostic push
179 # pragma GCC diagnostic ignored "-Wmissing-field-initializers"
180 #endif // defined(__GNUC__) && __GNUC__ < 5
181
182 #if !USE_OS_TZDB
183
184 # ifdef _WIN32
185 # ifndef WINRT
186
187 namespace
188 {
189 struct task_mem_deleter
190 {
191 void operator()(wchar_t buf[])
192 {
193 if (buf != nullptr)
194 CoTaskMemFree(buf);
195 }
196 };
197 using co_task_mem_ptr = std::unique_ptr<wchar_t[], task_mem_deleter>;
198 }
199
200 // We might need to know certain locations even if not using the remote API,
201 // so keep these routines out of that block for now.
202 static
203 std::string
204 get_known_folder(const GUID& folderid)
205 {
206 std::string folder;
207 PWSTR pfolder = nullptr;
208 HRESULT hr = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, nullptr, &pfolder);
209 if (SUCCEEDED(hr))
210 {
211 co_task_mem_ptr folder_ptr(pfolder);
212 const wchar_t* fptr = folder_ptr.get();
213 auto state = std::mbstate_t();
214 const auto required = std::wcsrtombs(nullptr, &fptr, 0, &state);
215 if (required != 0 && required != std::size_t(-1))
216 {
217 folder.resize(required);
218 std::wcsrtombs(&folder[0], &fptr, folder.size(), &state);
219 }
220 }
221 return folder;
222 }
223
224 # ifndef INSTALL
225
226 // Usually something like "c:\Users\username\Downloads".
227 static
228 std::string
229 get_download_folder()
230 {
231 return get_known_folder(FOLDERID_Downloads);
232 }
233
234 # endif // !INSTALL
235
236 # endif // WINRT
237 # else // !_WIN32
238
239 # if !defined(INSTALL)
240
241 static
242 std::string
243 expand_path(std::string path)
244 {
245 # if TARGET_OS_IPHONE
246 return date::iOSUtils::get_tzdata_path();
247 # else // !TARGET_OS_IPHONE
248 ::wordexp_t w{};
249 std::unique_ptr<::wordexp_t, void(*)(::wordexp_t*)> hold{&w, ::wordfree};
250 ::wordexp(path.c_str(), &w, 0);
251 if (w.we_wordc != 1)
252 throw std::runtime_error("Cannot expand path: " + path);
253 path = w.we_wordv[0];
254 return path;
255 # endif // !TARGET_OS_IPHONE
256 }
257
258 static
259 std::string
260 get_download_folder()
261 {
262 return expand_path("~/Downloads");
263 }
264
265 # endif // !defined(INSTALL)
266
267 # endif // !_WIN32
268
269 #endif // !USE_OS_TZDB
270
271 namespace date
272 {
273 // +---------------------+
274 // | Begin Configuration |
275 // +---------------------+
276
277 using namespace detail;
278
279 #if !USE_OS_TZDB
280
281 static
282 std::string&
283 access_install()
284 {
285 static std::string install
286 #ifndef INSTALL
287
288 = get_download_folder() + folder_delimiter + "tzdata";
289
290 #else // !INSTALL
291
292 # define STRINGIZEIMP(x) #x
293 # define STRINGIZE(x) STRINGIZEIMP(x)
294
295 = STRINGIZE(INSTALL) + std::string(1, folder_delimiter) + "tzdata";
296
297 #undef STRINGIZEIMP
298 #undef STRINGIZE
299 #endif // !INSTALL
300
301 return install;
302 }
303
304 void
305 set_install(const std::string& s)
306 {
307 access_install() = s;
308 }
309
310 static
311 const std::string&
312 get_install()
313 {
314 static const std::string& ref = access_install();
315 return ref;
316 }
317
318 #if HAS_REMOTE_API
319 static
320 std::string
321 get_download_gz_file(const std::string& version)
322 {
323 auto file = get_install() + version + ".tar.gz";
324 return file;
325 }
326 #endif // HAS_REMOTE_API
327
328 #endif // !USE_OS_TZDB
329
330 // These can be used to reduce the range of the database to save memory
331 CONSTDATA auto min_year = date::year::min();
332 CONSTDATA auto max_year = date::year::max();
333
334 CONSTDATA auto min_day = date::January/1;
335 CONSTDATA auto max_day = date::December/31;
336
337 #if USE_OS_TZDB
338
339 CONSTCD14 const sys_seconds min_seconds = sys_days(min_year/min_day);
340
341 #endif // USE_OS_TZDB
342
343 #ifndef _WIN32
344
345 static
346 std::string
347 discover_tz_dir()
348 {
349 struct stat sb;
350 using namespace std;
351 # ifndef __APPLE__
352 CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo";
353 CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc";
354
355 // Check special path which is valid for buildroot with uclibc builds
356 if(stat(tz_dir_buildroot, &sb) == 0 && S_ISDIR(sb.st_mode))
357 return tz_dir_buildroot;
358 else if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode))
359 return tz_dir_default;
360 else
361 throw runtime_error("discover_tz_dir failed to find zoneinfo\n");
362 # else // __APPLE__
363 # if TARGET_OS_IPHONE
364 return "/var/db/timezone/zoneinfo";
365 # else
366 CONSTDATA auto timezone = "/etc/localtime";
367 if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0))
368 throw runtime_error("discover_tz_dir failed\n");
369 string result;
370 char rp[PATH_MAX+1] = {};
371 if (readlink(timezone, rp, sizeof(rp)-1) > 0)
372 result = string(rp);
373 else
374 throw system_error(errno, system_category(), "readlink() failed");
375 auto i = result.find("zoneinfo");
376 if (i == string::npos)
377 throw runtime_error("discover_tz_dir failed to find zoneinfo\n");
378 i = result.find('/', i);
379 if (i == string::npos)
380 throw runtime_error("discover_tz_dir failed to find '/'\n");
381 return result.substr(0, i);
382 # endif
383 # endif // __APPLE__
384 }
385
386 static
387 const std::string&
388 get_tz_dir()
389 {
390 static const std::string tz_dir = discover_tz_dir();
391 return tz_dir;
392 }
393
394 #endif
395
396 // +-------------------+
397 // | End Configuration |
398 // +-------------------+
399
400 #ifndef _MSC_VER
401 static_assert(min_year <= max_year, "Configuration error");
402 #endif
403
404 static std::unique_ptr<tzdb> init_tzdb();
405
406 tzdb_list::~tzdb_list()
407 {
408 const tzdb* ptr = head_;
409 head_ = nullptr;
410 while (ptr != nullptr)
411 {
412 auto next = ptr->next;
413 delete ptr;
414 ptr = next;
415 }
416 }
417
418 tzdb_list::tzdb_list(tzdb_list&& x) noexcept
419 : head_{x.head_.exchange(nullptr)}
420 {
421 }
422
423 void
424 tzdb_list::push_front(tzdb* tzdb) noexcept
425 {
426 tzdb->next = head_;
427 head_ = tzdb;
428 }
429
430 tzdb_list::const_iterator
431 tzdb_list::erase_after(const_iterator p) noexcept
432 {
433 auto t = p.p_->next;
434 p.p_->next = p.p_->next->next;
435 delete t;
436 return ++p;
437 }
438
439 struct tzdb_list::undocumented_helper
440 {
441 static void push_front(tzdb_list& db_list, tzdb* tzdb) noexcept
442 {
443 db_list.push_front(tzdb);
444 }
445 };
446
447 static
448 tzdb_list
449 create_tzdb()
450 {
451 tzdb_list tz_db;
452 tzdb_list::undocumented_helper::push_front(tz_db, init_tzdb().release());
453 return tz_db;
454 }
455
456 tzdb_list&
457 get_tzdb_list()
458 {
459 static tzdb_list tz_db = create_tzdb();
460 return tz_db;
461 }
462
463 #if !USE_OS_TZDB
464
465 #ifdef _WIN32
466
467 static
468 void
469 sort_zone_mappings(std::vector<date::detail::timezone_mapping>& mappings)
470 {
471 std::sort(mappings.begin(), mappings.end(),
472 [](const date::detail::timezone_mapping& lhs,
473 const date::detail::timezone_mapping& rhs)->bool
474 {
475 auto other_result = lhs.other.compare(rhs.other);
476 if (other_result < 0)
477 return true;
478 else if (other_result == 0)
479 {
480 auto territory_result = lhs.territory.compare(rhs.territory);
481 if (territory_result < 0)
482 return true;
483 else if (territory_result == 0)
484 {
485 if (lhs.type < rhs.type)
486 return true;
487 }
488 }
489 return false;
490 });
491 }
492
493 static
494 bool
495 native_to_standard_timezone_name(const std::string& native_tz_name,
496 std::string& standard_tz_name)
497 {
498 // TOOD! Need be a case insensitive compare?
499 if (native_tz_name == "UTC")
500 {
501 standard_tz_name = "Etc/UTC";
502 return true;
503 }
504 standard_tz_name.clear();
505 // TODO! we can improve on linear search.
506 const auto& mappings = date::get_tzdb().mappings;
507 for (const auto& tzm : mappings)
508 {
509 if (tzm.other == native_tz_name)
510 {
511 standard_tz_name = tzm.type;
512 return true;
513 }
514 }
515 return false;
516 }
517
518 // Parse this XML file:
519 // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml
520 // The parsing method is designed to be simple and quick. It is not overly
521 // forgiving of change but it should diagnose basic format issues.
522 // See timezone_mapping structure for more info.
523 static
524 std::vector<detail::timezone_mapping>
525 load_timezone_mappings_from_xml_file(const std::string& input_path)
526 {
527 std::size_t line_num = 0;
528 std::vector<detail::timezone_mapping> mappings;
529 std::string line;
530
531 std::ifstream is(input_path);
532 if (!is.is_open())
533 {
534 // We don't emit file exceptions because that's an implementation detail.
535 std::string msg = "Error opening time zone mapping file \"";
536 msg += input_path;
537 msg += "\".";
538 throw std::runtime_error(msg);
539 }
540
541 auto error = [&input_path, &line_num](const char* info)
542 {
543 std::string msg = "Error loading time zone mapping file \"";
544 msg += input_path;
545 msg += "\" at line ";
546 msg += std::to_string(line_num);
547 msg += ": ";
548 msg += info;
549 throw std::runtime_error(msg);
550 };
551 // [optional space]a="b"
552 auto read_attribute = [&line, &error]
553 (const char* name, std::string& value, std::size_t startPos)
554 ->std::size_t
555 {
556 value.clear();
557 // Skip leading space before attribute name.
558 std::size_t spos = line.find_first_not_of(' ', startPos);
559 if (spos == std::string::npos)
560 spos = startPos;
561 // Assume everything up to next = is the attribute name
562 // and that an = will always delimit that.
563 std::size_t epos = line.find('=', spos);
564 if (epos == std::string::npos)
565 error("Expected \'=\' right after attribute name.");
566 std::size_t name_len = epos - spos;
567 // Expect the name we find matches the name we expect.
568 if (line.compare(spos, name_len, name) != 0)
569 {
570 std::string msg;
571 msg = "Expected attribute name \'";
572 msg += name;
573 msg += "\' around position ";
574 msg += std::to_string(spos);
575 msg += " but found something else.";
576 error(msg.c_str());
577 }
578 ++epos; // Skip the '=' that is after the attribute name.
579 spos = epos;
580 if (spos < line.length() && line[spos] == '\"')
581 ++spos; // Skip the quote that is before the attribute value.
582 else
583 {
584 std::string msg = "Expected '\"' to begin value of attribute \'";
585 msg += name;
586 msg += "\'.";
587 error(msg.c_str());
588 }
589 epos = line.find('\"', spos);
590 if (epos == std::string::npos)
591 {
592 std::string msg = "Expected '\"' to end value of attribute \'";
593 msg += name;
594 msg += "\'.";
595 error(msg.c_str());
596 }
597 // Extract everything in between the quotes. Note no escaping is done.
598 std::size_t value_len = epos - spos;
599 value.assign(line, spos, value_len);
600 ++epos; // Skip the quote that is after the attribute value;
601 return epos;
602 };
603
604 // Quick but not overly forgiving XML mapping file processing.
605 bool mapTimezonesOpenTagFound = false;
606 bool mapTimezonesCloseTagFound = false;
607 std::size_t mapZonePos = std::string::npos;
608 std::size_t mapTimezonesPos = std::string::npos;
609 CONSTDATA char mapTimeZonesOpeningTag[] = { "<mapTimezones " };
610 CONSTDATA char mapZoneOpeningTag[] = { "<mapZone " };
611 CONSTDATA std::size_t mapZoneOpeningTagLen = sizeof(mapZoneOpeningTag) /
612 sizeof(mapZoneOpeningTag[0]) - 1;
613 while (!mapTimezonesOpenTagFound)
614 {
615 std::getline(is, line);
616 ++line_num;
617 if (is.eof())
618 {
619 // If there is no mapTimezones tag is it an error?
620 // Perhaps if there are no mapZone mappings it might be ok for
621 // its parent mapTimezones element to be missing?
622 // We treat this as an error though on the assumption that if there
623 // really are no mappings we should still get a mapTimezones parent
624 // element but no mapZone elements inside. Assuming we must
625 // find something will hopefully at least catch more drastic formatting
626 // changes or errors than if we don't do this and assume nothing found.
627 error("Expected a mapTimezones opening tag.");
628 }
629 mapTimezonesPos = line.find(mapTimeZonesOpeningTag);
630 mapTimezonesOpenTagFound = (mapTimezonesPos != std::string::npos);
631 }
632
633 // NOTE: We could extract the version info that follows the opening
634 // mapTimezones tag and compare that to the version of other data we have.
635 // I would have expected them to be kept in synch but testing has shown
636 // it typically does not match anyway. So what's the point?
637 while (!mapTimezonesCloseTagFound)
638 {
639 std::ws(is);
640 std::getline(is, line);
641 ++line_num;
642 if (is.eof())
643 error("Expected a mapTimezones closing tag.");
644 if (line.empty())
645 continue;
646 mapZonePos = line.find(mapZoneOpeningTag);
647 if (mapZonePos != std::string::npos)
648 {
649 mapZonePos += mapZoneOpeningTagLen;
650 detail::timezone_mapping zm{};
651 std::size_t pos = read_attribute("other", zm.other, mapZonePos);
652 pos = read_attribute("territory", zm.territory, pos);
653 read_attribute("type", zm.type, pos);
654 mappings.push_back(std::move(zm));
655
656 continue;
657 }
658 mapTimezonesPos = line.find("</mapTimezones>");
659 mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos);
660 if (!mapTimezonesCloseTagFound)
661 {
662 std::size_t commentPos = line.find("<!--");
663 if (commentPos == std::string::npos)
664 error("Unexpected mapping record found. A xml mapZone or comment "
665 "attribute or mapTimezones closing tag was expected.");
666 }
667 }
668
669 is.close();
670 return mappings;
671 }
672
673 #endif // _WIN32
674
675 // Parsing helpers
676
677 static
678 std::string
679 parse3(std::istream& in)
680 {
681 std::string r(3, ' ');
682 ws(in);
683 r[0] = static_cast<char>(in.get());
684 r[1] = static_cast<char>(in.get());
685 r[2] = static_cast<char>(in.get());
686 return r;
687 }
688
689 static
690 unsigned
691 parse_dow(std::istream& in)
692 {
693 CONSTDATA char*const dow_names[] =
694 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
695 auto s = parse3(in);
696 auto dow = std::find(std::begin(dow_names), std::end(dow_names), s) - dow_names;
697 if (dow >= std::end(dow_names) - std::begin(dow_names))
698 throw std::runtime_error("oops: bad dow name: " + s);
699 return static_cast<unsigned>(dow);
700 }
701
702 static
703 unsigned
704 parse_month(std::istream& in)
705 {
706 CONSTDATA char*const month_names[] =
707 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
708 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
709 auto s = parse3(in);
710 auto m = std::find(std::begin(month_names), std::end(month_names), s) - month_names;
711 if (m >= std::end(month_names) - std::begin(month_names))
712 throw std::runtime_error("oops: bad month name: " + s);
713 return static_cast<unsigned>(++m);
714 }
715
716 static
717 std::chrono::seconds
718 parse_unsigned_time(std::istream& in)
719 {
720 using namespace std::chrono;
721 int x;
722 in >> x;
723 auto r = seconds{hours{x}};
724 if (!in.eof() && in.peek() == ':')
725 {
726 in.get();
727 in >> x;
728 r += minutes{x};
729 if (!in.eof() && in.peek() == ':')
730 {
731 in.get();
732 in >> x;
733 r += seconds{x};
734 }
735 }
736 return r;
737 }
738
739 static
740 std::chrono::seconds
741 parse_signed_time(std::istream& in)
742 {
743 ws(in);
744 auto sign = 1;
745 if (in.peek() == '-')
746 {
747 sign = -1;
748 in.get();
749 }
750 else if (in.peek() == '+')
751 in.get();
752 return sign * parse_unsigned_time(in);
753 }
754
755 // MonthDayTime
756
757 detail::MonthDayTime::MonthDayTime(local_seconds tp, tz timezone)
758 : zone_(timezone)
759 {
760 using namespace date;
761 const auto dp = date::floor<days>(tp);
762 const auto hms = make_time(tp - dp);
763 const auto ymd = year_month_day(dp);
764 u = ymd.month() / ymd.day();
765 h_ = hms.hours();
766 m_ = hms.minutes();
767 s_ = hms.seconds();
768 }
769
770 detail::MonthDayTime::MonthDayTime(const date::month_day& md, tz timezone)
771 : zone_(timezone)
772 {
773 u = md;
774 }
775
776 date::day
777 detail::MonthDayTime::day() const
778 {
779 switch (type_)
780 {
781 case month_day:
782 return u.month_day_.day();
783 case month_last_dow:
784 return date::day{31};
785 case lteq:
786 case gteq:
787 break;
788 }
789 return u.month_day_weekday_.month_day_.day();
790 }
791
792 date::month
793 detail::MonthDayTime::month() const
794 {
795 switch (type_)
796 {
797 case month_day:
798 return u.month_day_.month();
799 case month_last_dow:
800 return u.month_weekday_last_.month();
801 case lteq:
802 case gteq:
803 break;
804 }
805 return u.month_day_weekday_.month_day_.month();
806 }
807
808 int
809 detail::MonthDayTime::compare(date::year y, const MonthDayTime& x, date::year yx,
810 std::chrono::seconds offset, std::chrono::minutes prev_save) const
811 {
812 if (zone_ != x.zone_)
813 {
814 auto dp0 = to_sys_days(y);
815 auto dp1 = x.to_sys_days(yx);
816 if (std::abs((dp0-dp1).count()) > 1)
817 return dp0 < dp1 ? -1 : 1;
818 if (zone_ == tz::local)
819 {
820 auto tp0 = to_time_point(y) - prev_save;
821 if (x.zone_ == tz::utc)
822 tp0 -= offset;
823 auto tp1 = x.to_time_point(yx);
824 return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
825 }
826 else if (zone_ == tz::standard)
827 {
828 auto tp0 = to_time_point(y);
829 auto tp1 = x.to_time_point(yx);
830 if (x.zone_ == tz::local)
831 tp1 -= prev_save;
832 else
833 tp0 -= offset;
834 return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
835 }
836 // zone_ == tz::utc
837 auto tp0 = to_time_point(y);
838 auto tp1 = x.to_time_point(yx);
839 if (x.zone_ == tz::local)
840 tp1 -= offset + prev_save;
841 else
842 tp1 -= offset;
843 return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1;
844 }
845 auto const t0 = to_time_point(y);
846 auto const t1 = x.to_time_point(yx);
847 return t0 < t1 ? -1 : t0 == t1 ? 0 : 1;
848 }
849
850 sys_seconds
851 detail::MonthDayTime::to_sys(date::year y, std::chrono::seconds offset,
852 std::chrono::seconds save) const
853 {
854 using namespace date;
855 using namespace std::chrono;
856 auto until_utc = to_time_point(y);
857 if (zone_ == tz::standard)
858 until_utc -= offset;
859 else if (zone_ == tz::local)
860 until_utc -= offset + save;
861 return until_utc;
862 }
863
864 detail::MonthDayTime::U&
865 detail::MonthDayTime::U::operator=(const date::month_day& x)
866 {
867 month_day_ = x;
868 return *this;
869 }
870
871 detail::MonthDayTime::U&
872 detail::MonthDayTime::U::operator=(const date::month_weekday_last& x)
873 {
874 month_weekday_last_ = x;
875 return *this;
876 }
877
878 detail::MonthDayTime::U&
879 detail::MonthDayTime::U::operator=(const pair& x)
880 {
881 month_day_weekday_ = x;
882 return *this;
883 }
884
885 date::sys_days
886 detail::MonthDayTime::to_sys_days(date::year y) const
887 {
888 using namespace std::chrono;
889 using namespace date;
890 switch (type_)
891 {
892 case month_day:
893 return sys_days(y/u.month_day_);
894 case month_last_dow:
895 return sys_days(y/u.month_weekday_last_);
896 case lteq:
897 {
898 auto const x = y/u.month_day_weekday_.month_day_;
899 auto const wd1 = weekday(static_cast<sys_days>(x));
900 auto const wd0 = u.month_day_weekday_.weekday_;
901 return sys_days(x) - (wd1-wd0);
902 }
903 case gteq:
904 break;
905 }
906 auto const x = y/u.month_day_weekday_.month_day_;
907 auto const wd1 = u.month_day_weekday_.weekday_;
908 auto const wd0 = weekday(static_cast<sys_days>(x));
909 return sys_days(x) + (wd1-wd0);
910 }
911
912 sys_seconds
913 detail::MonthDayTime::to_time_point(date::year y) const
914 {
915 // Add seconds first to promote to largest rep early to prevent overflow
916 return to_sys_days(y) + s_ + h_ + m_;
917 }
918
919 void
920 detail::MonthDayTime::canonicalize(date::year y)
921 {
922 using namespace std::chrono;
923 using namespace date;
924 switch (type_)
925 {
926 case month_day:
927 return;
928 case month_last_dow:
929 {
930 auto const ymd = year_month_day(sys_days(y/u.month_weekday_last_));
931 u.month_day_ = ymd.month()/ymd.day();
932 type_ = month_day;
933 return;
934 }
935 case lteq:
936 {
937 auto const x = y/u.month_day_weekday_.month_day_;
938 auto const wd1 = weekday(static_cast<sys_days>(x));
939 auto const wd0 = u.month_day_weekday_.weekday_;
940 auto const ymd = year_month_day(sys_days(x) - (wd1-wd0));
941 u.month_day_ = ymd.month()/ymd.day();
942 type_ = month_day;
943 return;
944 }
945 case gteq:
946 {
947 auto const x = y/u.month_day_weekday_.month_day_;
948 auto const wd1 = u.month_day_weekday_.weekday_;
949 auto const wd0 = weekday(static_cast<sys_days>(x));
950 auto const ymd = year_month_day(sys_days(x) + (wd1-wd0));
951 u.month_day_ = ymd.month()/ymd.day();
952 type_ = month_day;
953 return;
954 }
955 }
956 }
957
958 std::istream&
959 detail::operator>>(std::istream& is, MonthDayTime& x)
960 {
961 using namespace date;
962 using namespace std::chrono;
963 assert(((std::ios::failbit | std::ios::badbit) & is.exceptions()) ==
964 (std::ios::failbit | std::ios::badbit));
965 x = MonthDayTime{};
966 if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
967 {
968 auto m = parse_month(is);
969 if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
970 {
971 if (is.peek() == 'l')
972 {
973 for (int i = 0; i < 4; ++i)
974 is.get();
975 auto dow = parse_dow(is);
976 x.type_ = MonthDayTime::month_last_dow;
977 x.u = date::month(m)/weekday(dow)[last];
978 }
979 else if (std::isalpha(is.peek()))
980 {
981 auto dow = parse_dow(is);
982 char c{};
983 is >> c;
984 if (c == '<' || c == '>')
985 {
986 char c2{};
987 is >> c2;
988 if (c2 != '=')
989 throw std::runtime_error(std::string("bad operator: ") + c + c2);
990 int d;
991 is >> d;
992 if (d < 1 || d > 31)
993 throw std::runtime_error(std::string("bad operator: ") + c + c2
994 + std::to_string(d));
995 x.type_ = c == '<' ? MonthDayTime::lteq : MonthDayTime::gteq;
996 x.u = MonthDayTime::pair{ date::month(m) / d, date::weekday(dow) };
997 }
998 else
999 throw std::runtime_error(std::string("bad operator: ") + c);
1000 }
1001 else // if (std::isdigit(is.peek())
1002 {
1003 int d;
1004 is >> d;
1005 if (d < 1 || d > 31)
1006 throw std::runtime_error(std::string("day of month: ")
1007 + std::to_string(d));
1008 x.type_ = MonthDayTime::month_day;
1009 x.u = date::month(m)/d;
1010 }
1011 if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
1012 {
1013 int t;
1014 is >> t;
1015 x.h_ = hours{t};
1016 if (!is.eof() && is.peek() == ':')
1017 {
1018 is.get();
1019 is >> t;
1020 x.m_ = minutes{t};
1021 if (!is.eof() && is.peek() == ':')
1022 {
1023 is.get();
1024 is >> t;
1025 x.s_ = seconds{t};
1026 }
1027 }
1028 if (!is.eof() && std::isalpha(is.peek()))
1029 {
1030 char c;
1031 is >> c;
1032 switch (c)
1033 {
1034 case 's':
1035 x.zone_ = tz::standard;
1036 break;
1037 case 'u':
1038 x.zone_ = tz::utc;
1039 break;
1040 }
1041 }
1042 }
1043 }
1044 else
1045 {
1046 x.u = month{m}/1;
1047 }
1048 }
1049 return is;
1050 }
1051
1052 std::ostream&
1053 detail::operator<<(std::ostream& os, const MonthDayTime& x)
1054 {
1055 switch (x.type_)
1056 {
1057 case MonthDayTime::month_day:
1058 os << x.u.month_day_ << " ";
1059 break;
1060 case MonthDayTime::month_last_dow:
1061 os << x.u.month_weekday_last_ << " ";
1062 break;
1063 case MonthDayTime::lteq:
1064 os << x.u.month_day_weekday_.weekday_ << " on or before "
1065 << x.u.month_day_weekday_.month_day_ << " ";
1066 break;
1067 case MonthDayTime::gteq:
1068 if ((static_cast<unsigned>(x.day()) - 1) % 7 == 0)
1069 {
1070 os << (x.u.month_day_weekday_.month_day_.month() /
1071 x.u.month_day_weekday_.weekday_[
1072 (static_cast<unsigned>(x.day()) - 1)/7+1]) << " ";
1073 }
1074 else
1075 {
1076 os << x.u.month_day_weekday_.weekday_ << " on or after "
1077 << x.u.month_day_weekday_.month_day_ << " ";
1078 }
1079 break;
1080 }
1081 os << date::make_time(x.s_ + x.h_ + x.m_);
1082 if (x.zone_ == tz::utc)
1083 os << "UTC ";
1084 else if (x.zone_ == tz::standard)
1085 os << "STD ";
1086 else
1087 os << " ";
1088 return os;
1089 }
1090
1091 // Rule
1092
1093 detail::Rule::Rule(const std::string& s)
1094 {
1095 try
1096 {
1097 using namespace date;
1098 using namespace std::chrono;
1099 std::istringstream in(s);
1100 in.exceptions(std::ios::failbit | std::ios::badbit);
1101 std::string word;
1102 in >> word >> name_;
1103 int x;
1104 ws(in);
1105 if (std::isalpha(in.peek()))
1106 {
1107 in >> word;
1108 if (word == "min")
1109 {
1110 starting_year_ = year::min();
1111 }
1112 else
1113 throw std::runtime_error("Didn't find expected word: " + word);
1114 }
1115 else
1116 {
1117 in >> x;
1118 starting_year_ = year{x};
1119 }
1120 std::ws(in);
1121 if (std::isalpha(in.peek()))
1122 {
1123 in >> word;
1124 if (word == "only")
1125 {
1126 ending_year_ = starting_year_;
1127 }
1128 else if (word == "max")
1129 {
1130 ending_year_ = year::max();
1131 }
1132 else
1133 throw std::runtime_error("Didn't find expected word: " + word);
1134 }
1135 else
1136 {
1137 in >> x;
1138 ending_year_ = year{x};
1139 }
1140 in >> word; // TYPE (always "-")
1141 assert(word == "-");
1142 in >> starting_at_;
1143 save_ = duration_cast<minutes>(parse_signed_time(in));
1144 in >> abbrev_;
1145 if (abbrev_ == "-")
1146 abbrev_.clear();
1147 assert(hours{-1} <= save_ && save_ <= hours{2});
1148 }
1149 catch (...)
1150 {
1151 std::cerr << s << '\n';
1152 std::cerr << *this << '\n';
1153 throw;
1154 }
1155 }
1156
1157 detail::Rule::Rule(const Rule& r, date::year starting_year, date::year ending_year)
1158 : name_(r.name_)
1159 , starting_year_(starting_year)
1160 , ending_year_(ending_year)
1161 , starting_at_(r.starting_at_)
1162 , save_(r.save_)
1163 , abbrev_(r.abbrev_)
1164 {
1165 }
1166
1167 bool
1168 detail::operator==(const Rule& x, const Rule& y)
1169 {
1170 if (std::tie(x.name_, x.save_, x.starting_year_, x.ending_year_) ==
1171 std::tie(y.name_, y.save_, y.starting_year_, y.ending_year_))
1172 return x.month() == y.month() && x.day() == y.day();
1173 return false;
1174 }
1175
1176 bool
1177 detail::operator<(const Rule& x, const Rule& y)
1178 {
1179 using namespace std::chrono;
1180 auto const xm = x.month();
1181 auto const ym = y.month();
1182 if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) <
1183 std::tie(y.name_, y.starting_year_, ym, y.ending_year_))
1184 return true;
1185 if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) >
1186 std::tie(y.name_, y.starting_year_, ym, y.ending_year_))
1187 return false;
1188 return x.day() < y.day();
1189 }
1190
1191 bool
1192 detail::operator==(const Rule& x, const date::year& y)
1193 {
1194 return x.starting_year_ <= y && y <= x.ending_year_;
1195 }
1196
1197 bool
1198 detail::operator<(const Rule& x, const date::year& y)
1199 {
1200 return x.ending_year_ < y;
1201 }
1202
1203 bool
1204 detail::operator==(const date::year& x, const Rule& y)
1205 {
1206 return y.starting_year_ <= x && x <= y.ending_year_;
1207 }
1208
1209 bool
1210 detail::operator<(const date::year& x, const Rule& y)
1211 {
1212 return x < y.starting_year_;
1213 }
1214
1215 bool
1216 detail::operator==(const Rule& x, const std::string& y)
1217 {
1218 return x.name() == y;
1219 }
1220
1221 bool
1222 detail::operator<(const Rule& x, const std::string& y)
1223 {
1224 return x.name() < y;
1225 }
1226
1227 bool
1228 detail::operator==(const std::string& x, const Rule& y)
1229 {
1230 return y.name() == x;
1231 }
1232
1233 bool
1234 detail::operator<(const std::string& x, const Rule& y)
1235 {
1236 return x < y.name();
1237 }
1238
1239 std::ostream&
1240 detail::operator<<(std::ostream& os, const Rule& r)
1241 {
1242 using namespace date;
1243 using namespace std::chrono;
1244 detail::save_ostream<char> _(os);
1245 os.fill(' ');
1246 os.flags(std::ios::dec | std::ios::left);
1247 os.width(15);
1248 os << r.name_;
1249 os << r.starting_year_ << " " << r.ending_year_ << " ";
1250 os << r.starting_at_;
1251 if (r.save_ >= minutes{0})
1252 os << ' ';
1253 os << date::make_time(r.save_) << " ";
1254 os << r.abbrev_;
1255 return os;
1256 }
1257
1258 date::day
1259 detail::Rule::day() const
1260 {
1261 return starting_at_.day();
1262 }
1263
1264 date::month
1265 detail::Rule::month() const
1266 {
1267 return starting_at_.month();
1268 }
1269
1270 struct find_rule_by_name
1271 {
1272 bool operator()(const Rule& x, const std::string& nm) const
1273 {
1274 return x.name() < nm;
1275 }
1276
1277 bool operator()(const std::string& nm, const Rule& x) const
1278 {
1279 return nm < x.name();
1280 }
1281 };
1282
1283 bool
1284 detail::Rule::overlaps(const Rule& x, const Rule& y)
1285 {
1286 // assume x.starting_year_ <= y.starting_year_;
1287 if (!(x.starting_year_ <= y.starting_year_))
1288 {
1289 std::cerr << x << '\n';
1290 std::cerr << y << '\n';
1291 assert(x.starting_year_ <= y.starting_year_);
1292 }
1293 if (y.starting_year_ > x.ending_year_)
1294 return false;
1295 return !(x.starting_year_ == y.starting_year_ && x.ending_year_ == y.ending_year_);
1296 }
1297
1298 void
1299 detail::Rule::split(std::vector<Rule>& rules, std::size_t i, std::size_t k, std::size_t& e)
1300 {
1301 using namespace date;
1302 using difference_type = std::vector<Rule>::iterator::difference_type;
1303 // rules[i].starting_year_ <= rules[k].starting_year_ &&
1304 // rules[i].ending_year_ >= rules[k].starting_year_ &&
1305 // (rules[i].starting_year_ != rules[k].starting_year_ ||
1306 // rules[i].ending_year_ != rules[k].ending_year_)
1307 assert(rules[i].starting_year_ <= rules[k].starting_year_ &&
1308 rules[i].ending_year_ >= rules[k].starting_year_ &&
1309 (rules[i].starting_year_ != rules[k].starting_year_ ||
1310 rules[i].ending_year_ != rules[k].ending_year_));
1311 if (rules[i].starting_year_ == rules[k].starting_year_)
1312 {
1313 if (rules[k].ending_year_ < rules[i].ending_year_)
1314 {
1315 rules.insert(rules.begin() + static_cast<difference_type>(k+1),
1316 Rule(rules[i], rules[k].ending_year_ + years{1},
1317 std::move(rules[i].ending_year_)));
1318 ++e;
1319 rules[i].ending_year_ = rules[k].ending_year_;
1320 }
1321 else // rules[k].ending_year_ > rules[i].ending_year_
1322 {
1323 rules.insert(rules.begin() + static_cast<difference_type>(k+1),
1324 Rule(rules[k], rules[i].ending_year_ + years{1},
1325 std::move(rules[k].ending_year_)));
1326 ++e;
1327 rules[k].ending_year_ = rules[i].ending_year_;
1328 }
1329 }
1330 else // rules[i].starting_year_ < rules[k].starting_year_
1331 {
1332 if (rules[k].ending_year_ < rules[i].ending_year_)
1333 {
1334 rules.insert(rules.begin() + static_cast<difference_type>(k),
1335 Rule(rules[i], rules[k].starting_year_, rules[k].ending_year_));
1336 ++k;
1337 rules.insert(rules.begin() + static_cast<difference_type>(k+1),
1338 Rule(rules[i], rules[k].ending_year_ + years{1},
1339 std::move(rules[i].ending_year_)));
1340 rules[i].ending_year_ = rules[k].starting_year_ - years{1};
1341 e += 2;
1342 }
1343 else if (rules[k].ending_year_ > rules[i].ending_year_)
1344 {
1345 rules.insert(rules.begin() + static_cast<difference_type>(k),
1346 Rule(rules[i], rules[k].starting_year_, rules[i].ending_year_));
1347 ++k;
1348 rules.insert(rules.begin() + static_cast<difference_type>(k+1),
1349 Rule(rules[k], rules[i].ending_year_ + years{1},
1350 std::move(rules[k].ending_year_)));
1351 e += 2;
1352 rules[k].ending_year_ = std::move(rules[i].ending_year_);
1353 rules[i].ending_year_ = rules[k].starting_year_ - years{1};
1354 }
1355 else // rules[k].ending_year_ == rules[i].ending_year_
1356 {
1357 rules.insert(rules.begin() + static_cast<difference_type>(k),
1358 Rule(rules[i], rules[k].starting_year_,
1359 std::move(rules[i].ending_year_)));
1360 ++k;
1361 ++e;
1362 rules[i].ending_year_ = rules[k].starting_year_ - years{1};
1363 }
1364 }
1365 }
1366
1367 void
1368 detail::Rule::split_overlaps(std::vector<Rule>& rules, std::size_t i, std::size_t& e)
1369 {
1370 using difference_type = std::vector<Rule>::iterator::difference_type;
1371 auto j = i;
1372 for (; i + 1 < e; ++i)
1373 {
1374 for (auto k = i + 1; k < e; ++k)
1375 {
1376 if (overlaps(rules[i], rules[k]))
1377 {
1378 split(rules, i, k, e);
1379 std::sort(rules.begin() + static_cast<difference_type>(i),
1380 rules.begin() + static_cast<difference_type>(e));
1381 }
1382 }
1383 }
1384 for (; j < e; ++j)
1385 {
1386 if (rules[j].starting_year() == rules[j].ending_year())
1387 rules[j].starting_at_.canonicalize(rules[j].starting_year());
1388 }
1389 }
1390
1391 void
1392 detail::Rule::split_overlaps(std::vector<Rule>& rules)
1393 {
1394 using difference_type = std::vector<Rule>::iterator::difference_type;
1395 for (std::size_t i = 0; i < rules.size();)
1396 {
1397 auto e = static_cast<std::size_t>(std::upper_bound(
1398 rules.cbegin()+static_cast<difference_type>(i), rules.cend(), rules[i].name(),
1399 [](const std::string& nm, const Rule& x)
1400 {
1401 return nm < x.name();
1402 }) - rules.cbegin());
1403 split_overlaps(rules, i, e);
1404 auto first_rule = rules.begin() + static_cast<difference_type>(i);
1405 auto last_rule = rules.begin() + static_cast<difference_type>(e);
1406 auto t = std::lower_bound(first_rule, last_rule, min_year);
1407 if (t > first_rule+1)
1408 {
1409 if (t == last_rule || t->starting_year() >= min_year)
1410 --t;
1411 auto d = static_cast<std::size_t>(t - first_rule);
1412 rules.erase(first_rule, t);
1413 e -= d;
1414 }
1415 first_rule = rules.begin() + static_cast<difference_type>(i);
1416 last_rule = rules.begin() + static_cast<difference_type>(e);
1417 t = std::upper_bound(first_rule, last_rule, max_year);
1418 if (t != last_rule)
1419 {
1420 auto d = static_cast<std::size_t>(last_rule - t);
1421 rules.erase(t, last_rule);
1422 e -= d;
1423 }
1424 i = e;
1425 }
1426 rules.shrink_to_fit();
1427 }
1428
1429 // Find the rule that comes chronologically before Rule r. For multi-year rules,
1430 // y specifies which rules in r. For single year rules, y is assumed to be equal
1431 // to the year specified by r.
1432 // Returns a pointer to the chronologically previous rule, and the year within
1433 // that rule. If there is no previous rule, returns nullptr and year::min().
1434 // Preconditions:
1435 // r->starting_year() <= y && y <= r->ending_year()
1436 static
1437 std::pair<const Rule*, date::year>
1438 find_previous_rule(const Rule* r, date::year y)
1439 {
1440 using namespace date;
1441 auto const& rules = get_tzdb().rules;
1442 if (y == r->starting_year())
1443 {
1444 if (r == &rules.front() || r->name() != r[-1].name())
1445 std::terminate(); // never called with first rule
1446 --r;
1447 if (y == r->starting_year())
1448 return {r, y};
1449 return {r, r->ending_year()};
1450 }
1451 if (r == &rules.front() || r->name() != r[-1].name() ||
1452 r[-1].starting_year() < r->starting_year())
1453 {
1454 while (r < &rules.back() && r->name() == r[1].name() &&
1455 r->starting_year() == r[1].starting_year())
1456 ++r;
1457 return {r, --y};
1458 }
1459 --r;
1460 return {r, y};
1461 }
1462
1463 // Find the rule that comes chronologically after Rule r. For multi-year rules,
1464 // y specifies which rules in r. For single year rules, y is assumed to be equal
1465 // to the year specified by r.
1466 // Returns a pointer to the chronologically next rule, and the year within
1467 // that rule. If there is no next rule, return a pointer to a defaulted rule
1468 // and y+1.
1469 // Preconditions:
1470 // first <= r && r < last && r->starting_year() <= y && y <= r->ending_year()
1471 // [first, last) all have the same name
1472 static
1473 std::pair<const Rule*, date::year>
1474 find_next_rule(const Rule* first_rule, const Rule* last_rule, const Rule* r, date::year y)
1475 {
1476 using namespace date;
1477 if (y == r->ending_year())
1478 {
1479 if (r == last_rule-1)
1480 return {nullptr, year::max()};
1481 ++r;
1482 if (y == r->ending_year())
1483 return {r, y};
1484 return {r, r->starting_year()};
1485 }
1486 if (r == last_rule-1 || r->ending_year() < r[1].ending_year())
1487 {
1488 while (r > first_rule && r->starting_year() == r[-1].starting_year())
1489 --r;
1490 return {r, ++y};
1491 }
1492 ++r;
1493 return {r, y};
1494 }
1495
1496 // Find the rule that comes chronologically after Rule r. For multi-year rules,
1497 // y specifies which rules in r. For single year rules, y is assumed to be equal
1498 // to the year specified by r.
1499 // Returns a pointer to the chronologically next rule, and the year within
1500 // that rule. If there is no next rule, return nullptr and year::max().
1501 // Preconditions:
1502 // r->starting_year() <= y && y <= r->ending_year()
1503 static
1504 std::pair<const Rule*, date::year>
1505 find_next_rule(const Rule* r, date::year y)
1506 {
1507 using namespace date;
1508 auto const& rules = get_tzdb().rules;
1509 if (y == r->ending_year())
1510 {
1511 if (r == &rules.back() || r->name() != r[1].name())
1512 return {nullptr, year::max()};
1513 ++r;
1514 if (y == r->ending_year())
1515 return {r, y};
1516 return {r, r->starting_year()};
1517 }
1518 if (r == &rules.back() || r->name() != r[1].name() ||
1519 r->ending_year() < r[1].ending_year())
1520 {
1521 while (r > &rules.front() && r->name() == r[-1].name() &&
1522 r->starting_year() == r[-1].starting_year())
1523 --r;
1524 return {r, ++y};
1525 }
1526 ++r;
1527 return {r, y};
1528 }
1529
1530 static
1531 const Rule*
1532 find_first_std_rule(const std::pair<const Rule*, const Rule*>& eqr)
1533 {
1534 auto r = eqr.first;
1535 auto ry = r->starting_year();
1536 while (r->save() != std::chrono::minutes{0})
1537 {
1538 std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
1539 if (r == nullptr)
1540 throw std::runtime_error("Could not find standard offset in rule "
1541 + eqr.first->name());
1542 }
1543 return r;
1544 }
1545
1546 static
1547 std::pair<const Rule*, date::year>
1548 find_rule_for_zone(const std::pair<const Rule*, const Rule*>& eqr,
1549 const date::year& y, const std::chrono::seconds& offset,
1550 const MonthDayTime& mdt)
1551 {
1552 assert(eqr.first != nullptr);
1553 assert(eqr.second != nullptr);
1554
1555 using namespace std::chrono;
1556 using namespace date;
1557 auto r = eqr.first;
1558 auto ry = r->starting_year();
1559 auto prev_save = minutes{0};
1560 auto prev_year = year::min();
1561 const Rule* prev_rule = nullptr;
1562 while (r != nullptr)
1563 {
1564 if (mdt.compare(y, r->mdt(), ry, offset, prev_save) <= 0)
1565 break;
1566 prev_rule = r;
1567 prev_year = ry;
1568 prev_save = prev_rule->save();
1569 std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
1570 }
1571 return {prev_rule, prev_year};
1572 }
1573
1574 static
1575 std::pair<const Rule*, date::year>
1576 find_rule_for_zone(const std::pair<const Rule*, const Rule*>& eqr,
1577 const sys_seconds& tp_utc,
1578 const local_seconds& tp_std,
1579 const local_seconds& tp_loc)
1580 {
1581 using namespace std::chrono;
1582 using namespace date;
1583 auto r = eqr.first;
1584 auto ry = r->starting_year();
1585 auto prev_save = minutes{0};
1586 auto prev_year = year::min();
1587 const Rule* prev_rule = nullptr;
1588 while (r != nullptr)
1589 {
1590 bool found = false;
1591 switch (r->mdt().zone())
1592 {
1593 case tz::utc:
1594 found = tp_utc < r->mdt().to_time_point(ry);
1595 break;
1596 case tz::standard:
1597 found = sys_seconds{tp_std.time_since_epoch()} < r->mdt().to_time_point(ry);
1598 break;
1599 case tz::local:
1600 found = sys_seconds{tp_loc.time_since_epoch()} < r->mdt().to_time_point(ry);
1601 break;
1602 }
1603 if (found)
1604 break;
1605 prev_rule = r;
1606 prev_year = ry;
1607 prev_save = prev_rule->save();
1608 std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry);
1609 }
1610 return {prev_rule, prev_year};
1611 }
1612
1613 static
1614 sys_info
1615 find_rule(const std::pair<const Rule*, date::year>& first_rule,
1616 const std::pair<const Rule*, date::year>& last_rule,
1617 const date::year& y, const std::chrono::seconds& offset,
1618 const MonthDayTime& mdt, const std::chrono::minutes& initial_save,
1619 const std::string& initial_abbrev)
1620 {
1621 using namespace std::chrono;
1622 using namespace date;
1623 auto r = first_rule.first;
1624 auto ry = first_rule.second;
1625 sys_info x{sys_days(year::min()/min_day), sys_days(year::max()/max_day),
1626 seconds{0}, initial_save, initial_abbrev};
1627 while (r != nullptr)
1628 {
1629 auto tr = r->mdt().to_sys(ry, offset, x.save);
1630 auto tx = mdt.to_sys(y, offset, x.save);
1631 // Find last rule where tx >= tr
1632 if (tx <= tr || (r == last_rule.first && ry == last_rule.second))
1633 {
1634 if (tx < tr && r == first_rule.first && ry == first_rule.second)
1635 {
1636 x.end = r->mdt().to_sys(ry, offset, x.save);
1637 break;
1638 }
1639 if (tx < tr)
1640 {
1641 std::tie(r, ry) = find_previous_rule(r, ry); // can't return nullptr for r
1642 assert(r != nullptr);
1643 }
1644 // r != nullptr && tx >= tr (if tr were to be recomputed)
1645 auto prev_save = initial_save;
1646 if (!(r == first_rule.first && ry == first_rule.second))
1647 prev_save = find_previous_rule(r, ry).first->save();
1648 x.begin = r->mdt().to_sys(ry, offset, prev_save);
1649 x.save = r->save();
1650 x.abbrev = r->abbrev();
1651 if (!(r == last_rule.first && ry == last_rule.second))
1652 {
1653 std::tie(r, ry) = find_next_rule(r, ry); // can't return nullptr for r
1654 assert(r != nullptr);
1655 x.end = r->mdt().to_sys(ry, offset, x.save);
1656 }
1657 else
1658 x.end = sys_days(year::max()/max_day);
1659 break;
1660 }
1661 x.save = r->save();
1662 std::tie(r, ry) = find_next_rule(r, ry); // Can't return nullptr for r
1663 assert(r != nullptr);
1664 }
1665 return x;
1666 }
1667
1668 // zonelet
1669
1670 detail::zonelet::~zonelet()
1671 {
1672 #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
1673 using minutes = std::chrono::minutes;
1674 using string = std::string;
1675 if (tag_ == has_save)
1676 u.save_.~minutes();
1677 else
1678 u.rule_.~string();
1679 #endif
1680 }
1681
1682 detail::zonelet::zonelet()
1683 {
1684 #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
1685 ::new(&u.rule_) std::string();
1686 #endif
1687 }
1688
1689 detail::zonelet::zonelet(const zonelet& i)
1690 : gmtoff_(i.gmtoff_)
1691 , tag_(i.tag_)
1692 , format_(i.format_)
1693 , until_year_(i.until_year_)
1694 , until_date_(i.until_date_)
1695 , until_utc_(i.until_utc_)
1696 , until_std_(i.until_std_)
1697 , until_loc_(i.until_loc_)
1698 , initial_save_(i.initial_save_)
1699 , initial_abbrev_(i.initial_abbrev_)
1700 , first_rule_(i.first_rule_)
1701 , last_rule_(i.last_rule_)
1702 {
1703 #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
1704 if (tag_ == has_save)
1705 ::new(&u.save_) std::chrono::minutes(i.u.save_);
1706 else
1707 ::new(&u.rule_) std::string(i.u.rule_);
1708 #else
1709 if (tag_ == has_save)
1710 u.save_ = i.u.save_;
1711 else
1712 u.rule_ = i.u.rule_;
1713 #endif
1714 }
1715
1716 #endif // !USE_OS_TZDB
1717
1718 // time_zone
1719
1720 #if USE_OS_TZDB
1721
1722 time_zone::time_zone(const std::string& s, detail::undocumented)
1723 : name_(s)
1724 , adjusted_(new std::once_flag{})
1725 {
1726 }
1727
1728 enum class endian
1729 {
1730 native = __BYTE_ORDER__,
1731 little = __ORDER_LITTLE_ENDIAN__,
1732 big = __ORDER_BIG_ENDIAN__
1733 };
1734
1735 static
1736 inline
1737 std::uint32_t
1738 reverse_bytes(std::uint32_t i)
1739 {
1740 return
1741 (i & 0xff000000u) >> 24 |
1742 (i & 0x00ff0000u) >> 8 |
1743 (i & 0x0000ff00u) << 8 |
1744 (i & 0x000000ffu) << 24;
1745 }
1746
1747 static
1748 inline
1749 std::uint64_t
1750 reverse_bytes(std::uint64_t i)
1751 {
1752 return
1753 (i & 0xff00000000000000ull) >> 56 |
1754 (i & 0x00ff000000000000ull) >> 40 |
1755 (i & 0x0000ff0000000000ull) >> 24 |
1756 (i & 0x000000ff00000000ull) >> 8 |
1757 (i & 0x00000000ff000000ull) << 8 |
1758 (i & 0x0000000000ff0000ull) << 24 |
1759 (i & 0x000000000000ff00ull) << 40 |
1760 (i & 0x00000000000000ffull) << 56;
1761 }
1762
1763 template <class T>
1764 static
1765 inline
1766 void
1767 maybe_reverse_bytes(T&, std::false_type)
1768 {
1769 }
1770
1771 static
1772 inline
1773 void
1774 maybe_reverse_bytes(std::int32_t& t, std::true_type)
1775 {
1776 t = static_cast<std::int32_t>(reverse_bytes(static_cast<std::uint32_t>(t)));
1777 }
1778
1779 static
1780 inline
1781 void
1782 maybe_reverse_bytes(std::int64_t& t, std::true_type)
1783 {
1784 t = static_cast<std::int64_t>(reverse_bytes(static_cast<std::uint64_t>(t)));
1785 }
1786
1787 template <class T>
1788 static
1789 inline
1790 void
1791 maybe_reverse_bytes(T& t)
1792 {
1793 maybe_reverse_bytes(t, std::integral_constant<bool,
1794 endian::native == endian::little>{});
1795 }
1796
1797 static
1798 void
1799 load_header(std::istream& inf)
1800 {
1801 // Read TZif
1802 auto t = inf.get();
1803 auto z = inf.get();
1804 auto i = inf.get();
1805 auto f = inf.get();
1806 #ifndef NDEBUG
1807 assert(t == 'T');
1808 assert(z == 'Z');
1809 assert(i == 'i');
1810 assert(f == 'f');
1811 #else
1812 (void)t;
1813 (void)z;
1814 (void)i;
1815 (void)f;
1816 #endif
1817 }
1818
1819 static
1820 unsigned char
1821 load_version(std::istream& inf)
1822 {
1823 // Read version
1824 auto v = inf.get();
1825 assert(v != EOF);
1826 return static_cast<unsigned char>(v);
1827 }
1828
1829 static
1830 void
1831 skip_reserve(std::istream& inf)
1832 {
1833 inf.ignore(15);
1834 }
1835
1836 static
1837 void
1838 load_counts(std::istream& inf,
1839 std::int32_t& tzh_ttisgmtcnt, std::int32_t& tzh_ttisstdcnt,
1840 std::int32_t& tzh_leapcnt, std::int32_t& tzh_timecnt,
1841 std::int32_t& tzh_typecnt, std::int32_t& tzh_charcnt)
1842 {
1843 // Read counts;
1844 inf.read(reinterpret_cast<char*>(&tzh_ttisgmtcnt), 4);
1845 maybe_reverse_bytes(tzh_ttisgmtcnt);
1846 inf.read(reinterpret_cast<char*>(&tzh_ttisstdcnt), 4);
1847 maybe_reverse_bytes(tzh_ttisstdcnt);
1848 inf.read(reinterpret_cast<char*>(&tzh_leapcnt), 4);
1849 maybe_reverse_bytes(tzh_leapcnt);
1850 inf.read(reinterpret_cast<char*>(&tzh_timecnt), 4);
1851 maybe_reverse_bytes(tzh_timecnt);
1852 inf.read(reinterpret_cast<char*>(&tzh_typecnt), 4);
1853 maybe_reverse_bytes(tzh_typecnt);
1854 inf.read(reinterpret_cast<char*>(&tzh_charcnt), 4);
1855 maybe_reverse_bytes(tzh_charcnt);
1856 }
1857
1858 template <class TimeType>
1859 static
1860 std::vector<detail::transition>
1861 load_transitions(std::istream& inf, std::int32_t tzh_timecnt)
1862 {
1863 // Read transitions
1864 using namespace std::chrono;
1865 std::vector<detail::transition> transitions;
1866 transitions.reserve(static_cast<unsigned>(tzh_timecnt));
1867 for (std::int32_t i = 0; i < tzh_timecnt; ++i)
1868 {
1869 TimeType t;
1870 inf.read(reinterpret_cast<char*>(&t), sizeof(t));
1871 maybe_reverse_bytes(t);
1872 transitions.emplace_back(sys_seconds{seconds{t}});
1873 if (transitions.back().timepoint < min_seconds)
1874 transitions.back().timepoint = min_seconds;
1875 }
1876 return transitions;
1877 }
1878
1879 static
1880 std::vector<std::uint8_t>
1881 load_indices(std::istream& inf, std::int32_t tzh_timecnt)
1882 {
1883 // Read indices
1884 std::vector<std::uint8_t> indices;
1885 indices.reserve(static_cast<unsigned>(tzh_timecnt));
1886 for (std::int32_t i = 0; i < tzh_timecnt; ++i)
1887 {
1888 std::uint8_t t;
1889 inf.read(reinterpret_cast<char*>(&t), sizeof(t));
1890 indices.emplace_back(t);
1891 }
1892 return indices;
1893 }
1894
1895 static
1896 std::vector<ttinfo>
1897 load_ttinfo(std::istream& inf, std::int32_t tzh_typecnt)
1898 {
1899 // Read ttinfo
1900 std::vector<ttinfo> ttinfos;
1901 ttinfos.reserve(static_cast<unsigned>(tzh_typecnt));
1902 for (std::int32_t i = 0; i < tzh_typecnt; ++i)
1903 {
1904 ttinfo t;
1905 inf.read(reinterpret_cast<char*>(&t), 6);
1906 maybe_reverse_bytes(t.tt_gmtoff);
1907 ttinfos.emplace_back(t);
1908 }
1909 return ttinfos;
1910 }
1911
1912 static
1913 std::string
1914 load_abbreviations(std::istream& inf, std::int32_t tzh_charcnt)
1915 {
1916 // Read abbreviations
1917 std::string abbrev;
1918 abbrev.resize(static_cast<unsigned>(tzh_charcnt), '\0');
1919 inf.read(&abbrev[0], tzh_charcnt);
1920 return abbrev;
1921 }
1922
1923 #if !MISSING_LEAP_SECONDS
1924
1925 template <class TimeType>
1926 static
1927 std::vector<leap>
1928 load_leaps(std::istream& inf, std::int32_t tzh_leapcnt)
1929 {
1930 // Read tzh_leapcnt pairs
1931 using namespace std::chrono;
1932 std::vector<leap> leap_seconds;
1933 leap_seconds.reserve(static_cast<std::size_t>(tzh_leapcnt));
1934 for (std::int32_t i = 0; i < tzh_leapcnt; ++i)
1935 {
1936 TimeType t0;
1937 std::int32_t t1;
1938 inf.read(reinterpret_cast<char*>(&t0), sizeof(t0));
1939 inf.read(reinterpret_cast<char*>(&t1), sizeof(t1));
1940 maybe_reverse_bytes(t0);
1941 maybe_reverse_bytes(t1);
1942 leap_seconds.emplace_back(sys_seconds{seconds{t0 - (t1-1)}},
1943 detail::undocumented{});
1944 }
1945 return leap_seconds;
1946 }
1947
1948 template <class TimeType>
1949 static
1950 std::vector<leap>
1951 load_leap_data(std::istream& inf,
1952 std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt,
1953 std::int32_t tzh_typecnt, std::int32_t tzh_charcnt)
1954 {
1955 inf.ignore(tzh_timecnt*static_cast<std::int32_t>(sizeof(TimeType)) + tzh_timecnt +
1956 tzh_typecnt*6 + tzh_charcnt);
1957 return load_leaps<TimeType>(inf, tzh_leapcnt);
1958 }
1959
1960 static
1961 std::vector<leap>
1962 load_just_leaps(std::istream& inf)
1963 {
1964 // Read tzh_leapcnt pairs
1965 using namespace std::chrono;
1966 load_header(inf);
1967 auto v = load_version(inf);
1968 std::int32_t tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
1969 tzh_timecnt, tzh_typecnt, tzh_charcnt;
1970 skip_reserve(inf);
1971 load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
1972 tzh_timecnt, tzh_typecnt, tzh_charcnt);
1973 if (v == 0)
1974 return load_leap_data<int32_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt,
1975 tzh_charcnt);
1976 #if !defined(NDEBUG)
1977 inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
1978 tzh_ttisstdcnt + tzh_ttisgmtcnt);
1979 load_header(inf);
1980 auto v2 = load_version(inf);
1981 assert(v == v2);
1982 skip_reserve(inf);
1983 #else // defined(NDEBUG)
1984 inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
1985 tzh_ttisstdcnt + tzh_ttisgmtcnt + (4+1+15));
1986 #endif // defined(NDEBUG)
1987 load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
1988 tzh_timecnt, tzh_typecnt, tzh_charcnt);
1989 return load_leap_data<int64_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt,
1990 tzh_charcnt);
1991 }
1992
1993 #endif // !MISSING_LEAP_SECONDS
1994
1995 template <class TimeType>
1996 void
1997 time_zone::load_data(std::istream& inf,
1998 std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt,
1999 std::int32_t tzh_typecnt, std::int32_t tzh_charcnt)
2000 {
2001 using namespace std::chrono;
2002 transitions_ = load_transitions<TimeType>(inf, tzh_timecnt);
2003 auto indices = load_indices(inf, tzh_timecnt);
2004 auto infos = load_ttinfo(inf, tzh_typecnt);
2005 auto abbrev = load_abbreviations(inf, tzh_charcnt);
2006 #if !MISSING_LEAP_SECONDS
2007 auto& leap_seconds = get_tzdb_list().front().leaps;
2008 if (leap_seconds.empty() && tzh_leapcnt > 0)
2009 leap_seconds = load_leaps<TimeType>(inf, tzh_leapcnt);
2010 #endif
2011 ttinfos_.reserve(infos.size());
2012 for (auto& info : infos)
2013 {
2014 ttinfos_.push_back({seconds{info.tt_gmtoff},
2015 abbrev.c_str() + info.tt_abbrind,
2016 info.tt_isdst != 0});
2017 }
2018 auto i = 0u;
2019 if (transitions_.empty() || transitions_.front().timepoint != min_seconds)
2020 {
2021 transitions_.emplace(transitions_.begin(), min_seconds);
2022 auto tf = std::find_if(ttinfos_.begin(), ttinfos_.end(),
2023 [](const expanded_ttinfo& ti)
2024 {return ti.is_dst == 0;});
2025 if (tf == ttinfos_.end())
2026 tf = ttinfos_.begin();
2027 transitions_[i].info = &*tf;
2028 ++i;
2029 }
2030 for (auto j = 0u; i < transitions_.size(); ++i, ++j)
2031 transitions_[i].info = ttinfos_.data() + indices[j];
2032 }
2033
2034 void
2035 time_zone::init_impl()
2036 {
2037 using namespace std;
2038 using namespace std::chrono;
2039 auto name = get_tz_dir() + ('/' + name_);
2040 std::ifstream inf(name);
2041 if (!inf.is_open())
2042 throw std::runtime_error{"Unable to open " + name};
2043 inf.exceptions(std::ios::failbit | std::ios::badbit);
2044 load_header(inf);
2045 auto v = load_version(inf);
2046 std::int32_t tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
2047 tzh_timecnt, tzh_typecnt, tzh_charcnt;
2048 skip_reserve(inf);
2049 load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
2050 tzh_timecnt, tzh_typecnt, tzh_charcnt);
2051 if (v == 0)
2052 {
2053 load_data<int32_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
2054 }
2055 else
2056 {
2057 #if !defined(NDEBUG)
2058 inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
2059 tzh_ttisstdcnt + tzh_ttisgmtcnt);
2060 load_header(inf);
2061 auto v2 = load_version(inf);
2062 assert(v == v2);
2063 skip_reserve(inf);
2064 #else // defined(NDEBUG)
2065 inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
2066 tzh_ttisstdcnt + tzh_ttisgmtcnt + (4+1+15));
2067 #endif // defined(NDEBUG)
2068 load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
2069 tzh_timecnt, tzh_typecnt, tzh_charcnt);
2070 load_data<int64_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
2071 }
2072 #if !MISSING_LEAP_SECONDS
2073 if (tzh_leapcnt > 0)
2074 {
2075 auto& leap_seconds = get_tzdb_list().front().leaps;
2076 auto itr = leap_seconds.begin();
2077 auto l = itr->date();
2078 seconds leap_count{0};
2079 for (auto t = std::upper_bound(transitions_.begin(), transitions_.end(), l,
2080 [](const sys_seconds& x, const transition& ct)
2081 {
2082 return x < ct.timepoint;
2083 });
2084 t != transitions_.end(); ++t)
2085 {
2086 while (t->timepoint >= l)
2087 {
2088 ++leap_count;
2089 if (++itr == leap_seconds.end())
2090 l = sys_days(max_year/max_day);
2091 else
2092 l = itr->date() + leap_count;
2093 }
2094 t->timepoint -= leap_count;
2095 }
2096 }
2097 #endif // !MISSING_LEAP_SECONDS
2098 auto b = transitions_.begin();
2099 auto i = transitions_.end();
2100 if (i != b)
2101 {
2102 for (--i; i != b; --i)
2103 {
2104 if (i->info->offset == i[-1].info->offset &&
2105 i->info->abbrev == i[-1].info->abbrev &&
2106 i->info->is_dst == i[-1].info->is_dst)
2107 i = transitions_.erase(i);
2108 }
2109 }
2110 }
2111
2112 void
2113 time_zone::init() const
2114 {
2115 std::call_once(*adjusted_, [this]() {const_cast<time_zone*>(this)->init_impl();});
2116 }
2117
2118 sys_info
2119 time_zone::load_sys_info(std::vector<detail::transition>::const_iterator i) const
2120 {
2121 using namespace std::chrono;
2122 assert(!transitions_.empty());
2123 assert(i != transitions_.begin());
2124 sys_info r;
2125 r.begin = i[-1].timepoint;
2126 r.end = i != transitions_.end() ? i->timepoint :
2127 sys_seconds(sys_days(year::max()/max_day));
2128 r.offset = i[-1].info->offset;
2129 r.save = i[-1].info->is_dst ? minutes{1} : minutes{0};
2130 r.abbrev = i[-1].info->abbrev;
2131 return r;
2132 }
2133
2134 sys_info
2135 time_zone::get_info_impl(sys_seconds tp) const
2136 {
2137 using namespace std;
2138 init();
2139 return load_sys_info(upper_bound(transitions_.begin(), transitions_.end(), tp,
2140 [](const sys_seconds& x, const transition& t)
2141 {
2142 return x < t.timepoint;
2143 }));
2144 }
2145
2146 local_info
2147 time_zone::get_info_impl(local_seconds tp) const
2148 {
2149 using namespace std::chrono;
2150 init();
2151 local_info i;
2152 i.result = local_info::unique;
2153 auto tr = upper_bound(transitions_.begin(), transitions_.end(), tp,
2154 [](const local_seconds& x, const transition& t)
2155 {
2156 return sys_seconds{x.time_since_epoch()} -
2157 t.info->offset < t.timepoint;
2158 });
2159 i.first = load_sys_info(tr);
2160 auto tps = sys_seconds{(tp - i.first.offset).time_since_epoch()};
2161 if (tps < i.first.begin + days{1} && tr != transitions_.begin())
2162 {
2163 i.second = load_sys_info(--tr);
2164 tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
2165 if (tps < i.second.end)
2166 {
2167 i.result = local_info::ambiguous;
2168 std::swap(i.first, i.second);
2169 }
2170 else
2171 {
2172 i.second = {};
2173 }
2174 }
2175 else if (tps >= i.first.end && tr != transitions_.end())
2176 {
2177 i.second = load_sys_info(++tr);
2178 tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
2179 if (tps < i.second.begin)
2180 i.result = local_info::nonexistent;
2181 else
2182 i.second = {};
2183 }
2184 return i;
2185 }
2186
2187 std::ostream&
2188 operator<<(std::ostream& os, const time_zone& z)
2189 {
2190 using namespace std::chrono;
2191 z.init();
2192 os << z.name_ << '\n';
2193 os << "Initially: ";
2194 auto const& t = z.transitions_.front();
2195 if (t.info->offset >= seconds{0})
2196 os << '+';
2197 os << make_time(t.info->offset);
2198 if (t.info->is_dst > 0)
2199 os << " daylight ";
2200 else
2201 os << " standard ";
2202 os << t.info->abbrev << '\n';
2203 for (auto i = std::next(z.transitions_.cbegin()); i < z.transitions_.cend(); ++i)
2204 os << *i << '\n';
2205 return os;
2206 }
2207
2208 #if !MISSING_LEAP_SECONDS
2209
2210 leap::leap(const sys_seconds& s, detail::undocumented)
2211 : date_(s)
2212 {
2213 }
2214
2215 #endif // !MISSING_LEAP_SECONDS
2216
2217 #else // !USE_OS_TZDB
2218
2219 time_zone::time_zone(const std::string& s, detail::undocumented)
2220 : adjusted_(new std::once_flag{})
2221 {
2222 try
2223 {
2224 using namespace date;
2225 std::istringstream in(s);
2226 in.exceptions(std::ios::failbit | std::ios::badbit);
2227 std::string word;
2228 in >> word >> name_;
2229 parse_info(in);
2230 }
2231 catch (...)
2232 {
2233 std::cerr << s << '\n';
2234 std::cerr << *this << '\n';
2235 zonelets_.pop_back();
2236 throw;
2237 }
2238 }
2239
2240 sys_info
2241 time_zone::get_info_impl(sys_seconds tp) const
2242 {
2243 return get_info_impl(tp, static_cast<int>(tz::utc));
2244 }
2245
2246 local_info
2247 time_zone::get_info_impl(local_seconds tp) const
2248 {
2249 using namespace std::chrono;
2250 local_info i{};
2251 i.first = get_info_impl(sys_seconds{tp.time_since_epoch()}, static_cast<int>(tz::local));
2252 auto tps = sys_seconds{(tp - i.first.offset).time_since_epoch()};
2253 if (tps < i.first.begin)
2254 {
2255 i.second = std::move(i.first);
2256 i.first = get_info_impl(i.second.begin - seconds{1}, static_cast<int>(tz::utc));
2257 i.result = local_info::nonexistent;
2258 }
2259 else if (i.first.end - tps <= days{1})
2260 {
2261 i.second = get_info_impl(i.first.end, static_cast<int>(tz::utc));
2262 tps = sys_seconds{(tp - i.second.offset).time_since_epoch()};
2263 if (tps >= i.second.begin)
2264 i.result = local_info::ambiguous;
2265 else
2266 i.second = {};
2267 }
2268 return i;
2269 }
2270
2271 void
2272 time_zone::add(const std::string& s)
2273 {
2274 try
2275 {
2276 std::istringstream in(s);
2277 in.exceptions(std::ios::failbit | std::ios::badbit);
2278 ws(in);
2279 if (!in.eof() && in.peek() != '#')
2280 parse_info(in);
2281 }
2282 catch (...)
2283 {
2284 std::cerr << s << '\n';
2285 std::cerr << *this << '\n';
2286 zonelets_.pop_back();
2287 throw;
2288 }
2289 }
2290
2291 void
2292 time_zone::parse_info(std::istream& in)
2293 {
2294 using namespace date;
2295 using namespace std::chrono;
2296 zonelets_.emplace_back();
2297 auto& zonelet = zonelets_.back();
2298 zonelet.gmtoff_ = parse_signed_time(in);
2299 in >> zonelet.u.rule_;
2300 if (zonelet.u.rule_ == "-")
2301 zonelet.u.rule_.clear();
2302 in >> zonelet.format_;
2303 if (!in.eof())
2304 ws(in);
2305 if (in.eof() || in.peek() == '#')
2306 {
2307 zonelet.until_year_ = year::max();
2308 zonelet.until_date_ = MonthDayTime(max_day, tz::utc);
2309 }
2310 else
2311 {
2312 int y;
2313 in >> y;
2314 zonelet.until_year_ = year{y};
2315 in >> zonelet.until_date_;
2316 zonelet.until_date_.canonicalize(zonelet.until_year_);
2317 }
2318 if ((zonelet.until_year_ < min_year) ||
2319 (zonelets_.size() > 1 && zonelets_.end()[-2].until_year_ > max_year))
2320 zonelets_.pop_back();
2321 }
2322
2323 void
2324 time_zone::adjust_infos(const std::vector<Rule>& rules)
2325 {
2326 using namespace std::chrono;
2327 using namespace date;
2328 const zonelet* prev_zonelet = nullptr;
2329 for (auto& z : zonelets_)
2330 {
2331 std::pair<const Rule*, const Rule*> eqr{};
2332 std::istringstream in;
2333 in.exceptions(std::ios::failbit | std::ios::badbit);
2334 // Classify info as rule-based, has save, or neither
2335 if (!z.u.rule_.empty())
2336 {
2337 // Find out if this zonelet has a rule or a save
2338 eqr = std::equal_range(rules.data(), rules.data() + rules.size(), z.u.rule_);
2339 if (eqr.first == eqr.second)
2340 {
2341 // The rule doesn't exist. Assume this is a save
2342 try
2343 {
2344 using namespace std::chrono;
2345 using string = std::string;
2346 in.str(z.u.rule_);
2347 auto tmp = duration_cast<minutes>(parse_signed_time(in));
2348 #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
2349 z.u.rule_.~string();
2350 z.tag_ = zonelet::has_save;
2351 ::new(&z.u.save_) minutes(tmp);
2352 #else
2353 z.u.rule_.clear();
2354 z.tag_ = zonelet::has_save;
2355 z.u.save_ = tmp;
2356 #endif
2357 }
2358 catch (...)
2359 {
2360 std::cerr << name_ << " : " << z.u.rule_ << '\n';
2361 throw;
2362 }
2363 }
2364 }
2365 else
2366 {
2367 // This zone::zonelet has no rule and no save
2368 z.tag_ = zonelet::is_empty;
2369 }
2370
2371 minutes final_save{0};
2372 if (z.tag_ == zonelet::has_save)
2373 {
2374 final_save = z.u.save_;
2375 }
2376 else if (z.tag_ == zonelet::has_rule)
2377 {
2378 z.last_rule_ = find_rule_for_zone(eqr, z.until_year_, z.gmtoff_,
2379 z.until_date_);
2380 if (z.last_rule_.first != nullptr)
2381 final_save = z.last_rule_.first->save();
2382 }
2383 z.until_utc_ = z.until_date_.to_sys(z.until_year_, z.gmtoff_, final_save);
2384 z.until_std_ = local_seconds{z.until_utc_.time_since_epoch()} + z.gmtoff_;
2385 z.until_loc_ = z.until_std_ + final_save;
2386
2387 if (z.tag_ == zonelet::has_rule)
2388 {
2389 if (prev_zonelet != nullptr)
2390 {
2391 z.first_rule_ = find_rule_for_zone(eqr, prev_zonelet->until_utc_,
2392 prev_zonelet->until_std_,
2393 prev_zonelet->until_loc_);
2394 if (z.first_rule_.first != nullptr)
2395 {
2396 z.initial_save_ = z.first_rule_.first->save();
2397 z.initial_abbrev_ = z.first_rule_.first->abbrev();
2398 if (z.first_rule_ != z.last_rule_)
2399 {
2400 z.first_rule_ = find_next_rule(eqr.first, eqr.second,
2401 z.first_rule_.first,
2402 z.first_rule_.second);
2403 }
2404 else
2405 {
2406 z.first_rule_ = std::make_pair(nullptr, year::min());
2407 z.last_rule_ = std::make_pair(nullptr, year::max());
2408 }
2409 }
2410 }
2411 if (z.first_rule_.first == nullptr && z.last_rule_.first != nullptr)
2412 {
2413 z.first_rule_ = std::make_pair(eqr.first, eqr.first->starting_year());
2414 z.initial_abbrev_ = find_first_std_rule(eqr)->abbrev();
2415 }
2416 }
2417
2418 #ifndef NDEBUG
2419 if (z.first_rule_.first == nullptr)
2420 {
2421 assert(z.first_rule_.second == year::min());
2422 assert(z.last_rule_.first == nullptr);
2423 assert(z.last_rule_.second == year::max());
2424 }
2425 else
2426 {
2427 assert(z.last_rule_.first != nullptr);
2428 }
2429 #endif
2430 prev_zonelet = &z;
2431 }
2432 }
2433
2434 static
2435 std::string
2436 format_abbrev(std::string format, const std::string& variable, std::chrono::seconds off,
2437 std::chrono::minutes save)
2438 {
2439 using namespace std::chrono;
2440 auto k = format.find("%s");
2441 if (k != std::string::npos)
2442 {
2443 format.replace(k, 2, variable);
2444 }
2445 else
2446 {
2447 k = format.find('/');
2448 if (k != std::string::npos)
2449 {
2450 if (save == minutes{0})
2451 format.erase(k);
2452 else
2453 format.erase(0, k+1);
2454 }
2455 else
2456 {
2457 k = format.find("%z");
2458 if (k != std::string::npos)
2459 {
2460 std::string temp;
2461 if (off < seconds{0})
2462 {
2463 temp = '-';
2464 off = -off;
2465 }
2466 else
2467 temp = '+';
2468 auto h = date::floor<hours>(off);
2469 off -= h;
2470 if (h < hours{10})
2471 temp += '0';
2472 temp += std::to_string(h.count());
2473 if (off > seconds{0})
2474 {
2475 auto m = date::floor<minutes>(off);
2476 off -= m;
2477 if (m < minutes{10})
2478 temp += '0';
2479 temp += std::to_string(m.count());
2480 if (off > seconds{0})
2481 {
2482 if (off < seconds{10})
2483 temp += '0';
2484 temp += std::to_string(off.count());
2485 }
2486 }
2487 format.replace(k, 2, temp);
2488 }
2489 }
2490 }
2491 return format;
2492 }
2493
2494 sys_info
2495 time_zone::get_info_impl(sys_seconds tp, int tz_int) const
2496 {
2497 using namespace std::chrono;
2498 using namespace date;
2499 tz timezone = static_cast<tz>(tz_int);
2500 assert(timezone != tz::standard);
2501 auto y = year_month_day(floor<days>(tp)).year();
2502 if (y < min_year || y > max_year)
2503 throw std::runtime_error("The year " + std::to_string(static_cast<int>(y)) +
2504 " is out of range:[" + std::to_string(static_cast<int>(min_year)) + ", "
2505 + std::to_string(static_cast<int>(max_year)) + "]");
2506 std::call_once(*adjusted_,
2507 [this]()
2508 {
2509 const_cast<time_zone*>(this)->adjust_infos(get_tzdb().rules);
2510 });
2511 auto i = std::upper_bound(zonelets_.begin(), zonelets_.end(), tp,
2512 [timezone](sys_seconds t, const zonelet& zl)
2513 {
2514 return timezone == tz::utc ? t < zl.until_utc_ :
2515 t < sys_seconds{zl.until_loc_.time_since_epoch()};
2516 });
2517
2518 sys_info r{};
2519 if (i != zonelets_.end())
2520 {
2521 if (i->tag_ == zonelet::has_save)
2522 {
2523 if (i != zonelets_.begin())
2524 r.begin = i[-1].until_utc_;
2525 else
2526 r.begin = sys_days(year::min()/min_day);
2527 r.end = i->until_utc_;
2528 r.offset = i->gmtoff_ + i->u.save_;
2529 r.save = i->u.save_;
2530 }
2531 else if (i->u.rule_.empty())
2532 {
2533 if (i != zonelets_.begin())
2534 r.begin = i[-1].until_utc_;
2535 else
2536 r.begin = sys_days(year::min()/min_day);
2537 r.end = i->until_utc_;
2538 r.offset = i->gmtoff_;
2539 }
2540 else
2541 {
2542 r = find_rule(i->first_rule_, i->last_rule_, y, i->gmtoff_,
2543 MonthDayTime(local_seconds{tp.time_since_epoch()}, timezone),
2544 i->initial_save_, i->initial_abbrev_);
2545 r.offset = i->gmtoff_ + r.save;
2546 if (i != zonelets_.begin() && r.begin < i[-1].until_utc_)
2547 r.begin = i[-1].until_utc_;
2548 if (r.end > i->until_utc_)
2549 r.end = i->until_utc_;
2550 }
2551 r.abbrev = format_abbrev(i->format_, r.abbrev, r.offset, r.save);
2552 assert(r.begin < r.end);
2553 }
2554 return r;
2555 }
2556
2557 std::ostream&
2558 operator<<(std::ostream& os, const time_zone& z)
2559 {
2560 using namespace date;
2561 using namespace std::chrono;
2562 detail::save_ostream<char> _(os);
2563 os.fill(' ');
2564 os.flags(std::ios::dec | std::ios::left);
2565 std::call_once(*z.adjusted_,
2566 [&z]()
2567 {
2568 const_cast<time_zone&>(z).adjust_infos(get_tzdb().rules);
2569 });
2570 os.width(35);
2571 os << z.name_;
2572 std::string indent;
2573 for (auto const& s : z.zonelets_)
2574 {
2575 os << indent;
2576 if (s.gmtoff_ >= seconds{0})
2577 os << ' ';
2578 os << make_time(s.gmtoff_) << " ";
2579 os.width(15);
2580 if (s.tag_ != zonelet::has_save)
2581 os << s.u.rule_;
2582 else
2583 {
2584 std::ostringstream tmp;
2585 tmp << make_time(s.u.save_);
2586 os << tmp.str();
2587 }
2588 os.width(8);
2589 os << s.format_ << " ";
2590 os << s.until_year_ << ' ' << s.until_date_;
2591 os << " " << s.until_utc_ << " UTC";
2592 os << " " << s.until_std_ << " STD";
2593 os << " " << s.until_loc_;
2594 os << " " << make_time(s.initial_save_);
2595 os << " " << s.initial_abbrev_;
2596 if (s.first_rule_.first != nullptr)
2597 os << " {" << *s.first_rule_.first << ", " << s.first_rule_.second << '}';
2598 else
2599 os << " {" << "nullptr" << ", " << s.first_rule_.second << '}';
2600 if (s.last_rule_.first != nullptr)
2601 os << " {" << *s.last_rule_.first << ", " << s.last_rule_.second << '}';
2602 else
2603 os << " {" << "nullptr" << ", " << s.last_rule_.second << '}';
2604 os << '\n';
2605 if (indent.empty())
2606 indent = std::string(35, ' ');
2607 }
2608 return os;
2609 }
2610
2611 #endif // !USE_OS_TZDB
2612
2613 #if !MISSING_LEAP_SECONDS
2614
2615 std::ostream&
2616 operator<<(std::ostream& os, const leap& x)
2617 {
2618 using namespace date;
2619 return os << x.date_ << " +";
2620 }
2621
2622 #endif // !MISSING_LEAP_SECONDS
2623
2624 #if USE_OS_TZDB
2625
2626 # ifdef __APPLE__
2627 static
2628 std::string
2629 get_version()
2630 {
2631 using namespace std;
2632 auto path = get_tz_dir() + string("/+VERSION");
2633 ifstream in{path};
2634 string version;
2635 in >> version;
2636 if (in.fail())
2637 throw std::runtime_error("Unable to get Timezone database version from " + path);
2638 return version;
2639 }
2640 # endif
2641
2642 static
2643 std::unique_ptr<tzdb>
2644 init_tzdb()
2645 {
2646 std::unique_ptr<tzdb> db(new tzdb);
2647
2648 //Iterate through folders
2649 std::queue<std::string> subfolders;
2650 subfolders.emplace(get_tz_dir());
2651 struct dirent* d;
2652 struct stat s;
2653 while (!subfolders.empty())
2654 {
2655 auto dirname = std::move(subfolders.front());
2656 subfolders.pop();
2657 auto dir = opendir(dirname.c_str());
2658 if (!dir)
2659 continue;
2660 while ((d = readdir(dir)) != nullptr)
2661 {
2662 // Ignore these files:
2663 if (d->d_name[0] == '.' || // curdir, prevdir, hidden
2664 memcmp(d->d_name, "posix", 5) == 0 || // starts with posix
2665 strcmp(d->d_name, "Factory") == 0 ||
2666 strcmp(d->d_name, "iso3166.tab") == 0 ||
2667 strcmp(d->d_name, "right") == 0 ||
2668 strcmp(d->d_name, "+VERSION") == 0 ||
2669 strcmp(d->d_name, "zone.tab") == 0 ||
2670 strcmp(d->d_name, "zone1970.tab") == 0 ||
2671 strcmp(d->d_name, "tzdata.zi") == 0 ||
2672 strcmp(d->d_name, "leapseconds") == 0 ||
2673 strcmp(d->d_name, "leap-seconds.list") == 0 )
2674 continue;
2675 auto subname = dirname + folder_delimiter + d->d_name;
2676 if(stat(subname.c_str(), &s) == 0)
2677 {
2678 if(S_ISDIR(s.st_mode))
2679 {
2680 if(!S_ISLNK(s.st_mode))
2681 {
2682 subfolders.push(subname);
2683 }
2684 }
2685 else
2686 {
2687 db->zones.emplace_back(subname.substr(get_tz_dir().size()+1),
2688 detail::undocumented{});
2689 }
2690 }
2691 }
2692 closedir(dir);
2693 }
2694 db->zones.shrink_to_fit();
2695 std::sort(db->zones.begin(), db->zones.end());
2696 # if !MISSING_LEAP_SECONDS
2697 std::ifstream in(get_tz_dir() + std::string(1, folder_delimiter) + "right/UTC",
2698 std::ios_base::binary);
2699 if (in)
2700 {
2701 in.exceptions(std::ios::failbit | std::ios::badbit);
2702 db->leaps = load_just_leaps(in);
2703 }
2704 else
2705 {
2706 in.clear();
2707 in.open(get_tz_dir() + std::string(1, folder_delimiter) +
2708 "UTC", std::ios_base::binary);
2709 if (!in)
2710 throw std::runtime_error("Unable to extract leap second information");
2711 in.exceptions(std::ios::failbit | std::ios::badbit);
2712 db->leaps = load_just_leaps(in);
2713 }
2714 # endif // !MISSING_LEAP_SECONDS
2715 # ifdef __APPLE__
2716 db->version = get_version();
2717 # endif
2718 return db;
2719 }
2720
2721 #else // !USE_OS_TZDB
2722
2723 // link
2724
2725 link::link(const std::string& s)
2726 {
2727 using namespace date;
2728 std::istringstream in(s);
2729 in.exceptions(std::ios::failbit | std::ios::badbit);
2730 std::string word;
2731 in >> word >> target_ >> name_;
2732 }
2733
2734 std::ostream&
2735 operator<<(std::ostream& os, const link& x)
2736 {
2737 using namespace date;
2738 detail::save_ostream<char> _(os);
2739 os.fill(' ');
2740 os.flags(std::ios::dec | std::ios::left);
2741 os.width(35);
2742 return os << x.name_ << " --> " << x.target_;
2743 }
2744
2745 // leap
2746
2747 leap::leap(const std::string& s, detail::undocumented)
2748 {
2749 using namespace date;
2750 std::istringstream in(s);
2751 in.exceptions(std::ios::failbit | std::ios::badbit);
2752 std::string word;
2753 int y;
2754 MonthDayTime date;
2755 in >> word >> y >> date;
2756 date_ = date.to_time_point(year(y));
2757 }
2758
2759 static
2760 bool
2761 file_exists(const std::string& filename)
2762 {
2763 #ifdef _WIN32
2764 return ::_access(filename.c_str(), 0) == 0;
2765 #else
2766 return ::access(filename.c_str(), F_OK) == 0;
2767 #endif
2768 }
2769
2770 #if HAS_REMOTE_API
2771
2772 // CURL tools
2773
2774 static
2775 int
2776 curl_global()
2777 {
2778 if (::curl_global_init(CURL_GLOBAL_DEFAULT) != 0)
2779 throw std::runtime_error("CURL global initialization failed");
2780 return 0;
2781 }
2782
2783 namespace
2784 {
2785
2786 struct curl_deleter
2787 {
2788 void operator()(CURL* p) const
2789 {
2790 ::curl_easy_cleanup(p);
2791 }
2792 };
2793
2794 } // unnamed namespace
2795
2796 static
2797 std::unique_ptr<CURL, curl_deleter>
2798 curl_init()
2799 {
2800 static const auto curl_is_now_initiailized = curl_global();
2801 (void)curl_is_now_initiailized;
2802 return std::unique_ptr<CURL, curl_deleter>{::curl_easy_init()};
2803 }
2804
2805 static
2806 bool
2807 download_to_string(const std::string& url, std::string& str)
2808 {
2809 str.clear();
2810 auto curl = curl_init();
2811 if (!curl)
2812 return false;
2813 std::string version;
2814 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "curl");
2815 curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
2816 curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb,
2817 void* userp) -> std::size_t
2818 {
2819 auto& userstr = *static_cast<std::string*>(userp);
2820 auto realsize = size * nmemb;
2821 userstr.append(contents, realsize);
2822 return realsize;
2823 };
2824 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb);
2825 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str);
2826 curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false);
2827 auto res = curl_easy_perform(curl.get());
2828 return (res == CURLE_OK);
2829 }
2830
2831 namespace
2832 {
2833 enum class download_file_options { binary, text };
2834 }
2835
2836 static
2837 bool
2838 download_to_file(const std::string& url, const std::string& local_filename,
2839 download_file_options opts)
2840 {
2841 auto curl = curl_init();
2842 if (!curl)
2843 return false;
2844 curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
2845 curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false);
2846 curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb,
2847 void* userp) -> std::size_t
2848 {
2849 auto& of = *static_cast<std::ofstream*>(userp);
2850 auto realsize = size * nmemb;
2851 of.write(contents, static_cast<std::streamsize>(realsize));
2852 return realsize;
2853 };
2854 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb);
2855 decltype(curl_easy_perform(curl.get())) res;
2856 {
2857 std::ofstream of(local_filename,
2858 opts == download_file_options::binary ?
2859 std::ofstream::out | std::ofstream::binary :
2860 std::ofstream::out);
2861 of.exceptions(std::ios::badbit);
2862 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &of);
2863 res = curl_easy_perform(curl.get());
2864 }
2865 return res == CURLE_OK;
2866 }
2867
2868 std::string
2869 remote_version()
2870 {
2871 std::string version;
2872 std::string str;
2873 if (download_to_string("https://www.iana.org/time-zones", str))
2874 {
2875 CONSTDATA char db[] = "/time-zones/releases/tzdata";
2876 CONSTDATA auto db_size = sizeof(db) - 1;
2877 auto p = str.find(db, 0, db_size);
2878 const int ver_str_len = 5;
2879 if (p != std::string::npos && p + (db_size + ver_str_len) <= str.size())
2880 version = str.substr(p + db_size, ver_str_len);
2881 }
2882 return version;
2883 }
2884
2885
2886 // TODO! Using system() create a process and a console window.
2887 // This is useful to see what errors may occur but is slow and distracting.
2888 // Consider implementing this functionality more directly, such as
2889 // using _mkdir and CreateProcess etc.
2890 // But use the current means now as matches Unix implementations and while
2891 // in proof of concept / testing phase.
2892 // TODO! Use <filesystem> eventually.
2893 static
2894 bool
2895 remove_folder_and_subfolders(const std::string& folder)
2896 {
2897 # ifdef _WIN32
2898 # if USE_SHELL_API
2899 // Delete the folder contents by deleting the folder.
2900 std::string cmd = "rd /s /q \"";
2901 cmd += folder;
2902 cmd += '\"';
2903 return std::system(cmd.c_str()) == EXIT_SUCCESS;
2904 # else // !USE_SHELL_API
2905 // Create a buffer containing the path to delete. It must be terminated
2906 // by two nuls. Who designs these API's...
2907 std::vector<char> from;
2908 from.assign(folder.begin(), folder.end());
2909 from.push_back('\0');
2910 from.push_back('\0');
2911 SHFILEOPSTRUCT fo{}; // Zero initialize.
2912 fo.wFunc = FO_DELETE;
2913 fo.pFrom = from.data();
2914 fo.fFlags = FOF_NO_UI;
2915 int ret = SHFileOperation(&fo);
2916 if (ret == 0 && !fo.fAnyOperationsAborted)
2917 return true;
2918 return false;
2919 # endif // !USE_SHELL_API
2920 # else // !_WIN32
2921 # if USE_SHELL_API
2922 return std::system(("rm -R " + folder).c_str()) == EXIT_SUCCESS;
2923 # else // !USE_SHELL_API
2924 struct dir_deleter {
2925 dir_deleter() {}
2926 void operator()(DIR* d) const
2927 {
2928 if (d != nullptr)
2929 {
2930 int result = closedir(d);
2931 assert(result == 0);
2932 }
2933 }
2934 };
2935 using closedir_ptr = std::unique_ptr<DIR, dir_deleter>;
2936
2937 std::string filename;
2938 struct stat statbuf;
2939 std::size_t folder_len = folder.length();
2940 struct dirent* p = nullptr;
2941
2942 closedir_ptr d(opendir(folder.c_str()));
2943 bool r = d.get() != nullptr;
2944 while (r && (p=readdir(d.get())) != nullptr)
2945 {
2946 if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0)
2947 continue;
2948
2949 // + 2 for path delimiter and nul terminator.
2950 std::size_t buf_len = folder_len + strlen(p->d_name) + 2;
2951 filename.resize(buf_len);
2952 std::size_t path_len = static_cast<std::size_t>(
2953 snprintf(&filename[0], buf_len, "%s/%s", folder.c_str(), p->d_name));
2954 assert(path_len == buf_len - 1);
2955 filename.resize(path_len);
2956
2957 if (stat(filename.c_str(), &statbuf) == 0)
2958 r = S_ISDIR(statbuf.st_mode)
2959 ? remove_folder_and_subfolders(filename)
2960 : unlink(filename.c_str()) == 0;
2961 }
2962 d.reset();
2963
2964 if (r)
2965 r = rmdir(folder.c_str()) == 0;
2966
2967 return r;
2968 # endif // !USE_SHELL_API
2969 # endif // !_WIN32
2970 }
2971
2972 static
2973 bool
2974 make_directory(const std::string& folder)
2975 {
2976 # ifdef _WIN32
2977 # if USE_SHELL_API
2978 // Re-create the folder.
2979 std::string cmd = "mkdir \"";
2980 cmd += folder;
2981 cmd += '\"';
2982 return std::system(cmd.c_str()) == EXIT_SUCCESS;
2983 # else // !USE_SHELL_API
2984 return _mkdir(folder.c_str()) == 0;
2985 # endif // !USE_SHELL_API
2986 # else // !_WIN32
2987 # if USE_SHELL_API
2988 return std::system(("mkdir -p " + folder).c_str()) == EXIT_SUCCESS;
2989 # else // !USE_SHELL_API
2990 return mkdir(folder.c_str(), 0777) == 0;
2991 # endif // !USE_SHELL_API
2992 # endif // !_WIN32
2993 }
2994
2995 static
2996 bool
2997 delete_file(const std::string& file)
2998 {
2999 # ifdef _WIN32
3000 # if USE_SHELL_API
3001 std::string cmd = "del \"";
3002 cmd += file;
3003 cmd += '\"';
3004 return std::system(cmd.c_str()) == 0;
3005 # else // !USE_SHELL_API
3006 return _unlink(file.c_str()) == 0;
3007 # endif // !USE_SHELL_API
3008 # else // !_WIN32
3009 # if USE_SHELL_API
3010 return std::system(("rm " + file).c_str()) == EXIT_SUCCESS;
3011 # else // !USE_SHELL_API
3012 return unlink(file.c_str()) == 0;
3013 # endif // !USE_SHELL_API
3014 # endif // !_WIN32
3015 }
3016
3017 # ifdef _WIN32
3018
3019 static
3020 bool
3021 move_file(const std::string& from, const std::string& to)
3022 {
3023 # if USE_SHELL_API
3024 std::string cmd = "move \"";
3025 cmd += from;
3026 cmd += "\" \"";
3027 cmd += to;
3028 cmd += '\"';
3029 return std::system(cmd.c_str()) == EXIT_SUCCESS;
3030 # else // !USE_SHELL_API
3031 return !!::MoveFile(from.c_str(), to.c_str());
3032 # endif // !USE_SHELL_API
3033 }
3034
3035 // Usually something like "c:\Program Files".
3036 static
3037 std::string
3038 get_program_folder()
3039 {
3040 return get_known_folder(FOLDERID_ProgramFiles);
3041 }
3042
3043 // Note folder can and usually does contain spaces.
3044 static
3045 std::string
3046 get_unzip_program()
3047 {
3048 std::string path;
3049
3050 // 7-Zip appears to note its location in the registry.
3051 // If that doesn't work, fall through and take a guess, but it will likely be wrong.
3052 HKEY hKey = nullptr;
3053 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\7-Zip", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
3054 {
3055 char value_buffer[MAX_PATH + 1]; // fyi 260 at time of writing.
3056 // in/out parameter. Documentation say that size is a count of bytes not chars.
3057 DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]);
3058 DWORD tzi_type = REG_SZ;
3059 // Testing shows Path key value is "C:\Program Files\7-Zip\" i.e. always with trailing \.
3060 bool got_value = (RegQueryValueExA(hKey, "Path", nullptr, &tzi_type,
3061 reinterpret_cast<LPBYTE>(value_buffer), &size) == ERROR_SUCCESS);
3062 RegCloseKey(hKey); // Close now incase of throw later.
3063 if (got_value)
3064 {
3065 // Function does not guarantee to null terminate.
3066 value_buffer[size / sizeof(value_buffer[0])] = '\0';
3067 path = value_buffer;
3068 if (!path.empty())
3069 {
3070 path += "7z.exe";
3071 return path;
3072 }
3073 }
3074 }
3075 path += get_program_folder();
3076 path += folder_delimiter;
3077 path += "7-Zip\\7z.exe";
3078 return path;
3079 }
3080
3081 # if !USE_SHELL_API
3082 static
3083 int
3084 run_program(const std::string& command)
3085 {
3086 STARTUPINFO si{};
3087 si.cb = sizeof(si);
3088 PROCESS_INFORMATION pi{};
3089
3090 // Allegedly CreateProcess overwrites the command line. Ugh.
3091 std::string mutable_command(command);
3092 if (CreateProcess(nullptr, &mutable_command[0],
3093 nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
3094 {
3095 WaitForSingleObject(pi.hProcess, INFINITE);
3096 DWORD exit_code;
3097 bool got_exit_code = !!GetExitCodeProcess(pi.hProcess, &exit_code);
3098 CloseHandle(pi.hProcess);
3099 CloseHandle(pi.hThread);
3100 // Not 100% sure about this still active thing is correct,
3101 // but I'm going with it because I *think* WaitForSingleObject might
3102 // return in some cases without INFINITE-ly waiting.
3103 // But why/wouldn't GetExitCodeProcess return false in that case?
3104 if (got_exit_code && exit_code != STILL_ACTIVE)
3105 return static_cast<int>(exit_code);
3106 }
3107 return EXIT_FAILURE;
3108 }
3109 # endif // !USE_SHELL_API
3110
3111 static
3112 std::string
3113 get_download_tar_file(const std::string& version)
3114 {
3115 auto file = get_install();
3116 file += folder_delimiter;
3117 file += "tzdata";
3118 file += version;
3119 file += ".tar";
3120 return file;
3121 }
3122
3123 static
3124 bool
3125 extract_gz_file(const std::string& version, const std::string& gz_file,
3126 const std::string& dest_folder)
3127 {
3128 auto unzip_prog = get_unzip_program();
3129 bool unzip_result = false;
3130 // Use the unzip program to extract the tar file from the archive.
3131
3132 // Aim to create a string like:
3133 // "C:\Program Files\7-Zip\7z.exe" x "C:\Users\SomeUser\Downloads\tzdata2016d.tar.gz"
3134 // -o"C:\Users\SomeUser\Downloads\tzdata"
3135 std::string cmd;
3136 cmd = '\"';
3137 cmd += unzip_prog;
3138 cmd += "\" x \"";
3139 cmd += gz_file;
3140 cmd += "\" -o\"";
3141 cmd += dest_folder;
3142 cmd += '\"';
3143
3144 # if USE_SHELL_API
3145 // When using shelling out with std::system() extra quotes are required around the
3146 // whole command. It's weird but necessary it seems, see:
3147 // http://stackoverflow.com/q/27975969/576911
3148
3149 cmd = "\"" + cmd + "\"";
3150 if (std::system(cmd.c_str()) == EXIT_SUCCESS)
3151 unzip_result = true;
3152 # else // !USE_SHELL_API
3153 if (run_program(cmd) == EXIT_SUCCESS)
3154 unzip_result = true;
3155 # endif // !USE_SHELL_API
3156 if (unzip_result)
3157 delete_file(gz_file);
3158
3159 // Use the unzip program extract the data from the tar file that was
3160 // just extracted from the archive.
3161 auto tar_file = get_download_tar_file(version);
3162 cmd = '\"';
3163 cmd += unzip_prog;
3164 cmd += "\" x \"";
3165 cmd += tar_file;
3166 cmd += "\" -o\"";
3167 cmd += get_install();
3168 cmd += '\"';
3169 # if USE_SHELL_API
3170 cmd = "\"" + cmd + "\"";
3171 if (std::system(cmd.c_str()) == EXIT_SUCCESS)
3172 unzip_result = true;
3173 # else // !USE_SHELL_API
3174 if (run_program(cmd) == EXIT_SUCCESS)
3175 unzip_result = true;
3176 # endif // !USE_SHELL_API
3177
3178 if (unzip_result)
3179 delete_file(tar_file);
3180
3181 return unzip_result;
3182 }
3183
3184 static
3185 std::string
3186 get_download_mapping_file(const std::string& version)
3187 {
3188 auto file = get_install() + version + "windowsZones.xml";
3189 return file;
3190 }
3191
3192 # else // !_WIN32
3193
3194 # if !USE_SHELL_API
3195 static
3196 int
3197 run_program(const char* prog, const char*const args[])
3198 {
3199 pid_t pid = fork();
3200 if (pid == -1) // Child failed to start.
3201 return EXIT_FAILURE;
3202
3203 if (pid != 0)
3204 {
3205 // We are in the parent. Child started. Wait for it.
3206 pid_t ret;
3207 int status;
3208 while ((ret = waitpid(pid, &status, 0)) == -1)
3209 {
3210 if (errno != EINTR)
3211 break;
3212 }
3213 if (ret != -1)
3214 {
3215 if (WIFEXITED(status))
3216 return WEXITSTATUS(status);
3217 }
3218 printf("Child issues!\n");
3219
3220 return EXIT_FAILURE; // Not sure what status of child is.
3221 }
3222 else // We are in the child process. Start the program the parent wants to run.
3223 {
3224
3225 if (execv(prog, const_cast<char**>(args)) == -1) // Does not return.
3226 {
3227 perror("unreachable 0\n");
3228 _Exit(127);
3229 }
3230 printf("unreachable 2\n");
3231 }
3232 printf("unreachable 2\n");
3233 // Unreachable.
3234 assert(false);
3235 exit(EXIT_FAILURE);
3236 return EXIT_FAILURE;
3237 }
3238 # endif // !USE_SHELL_API
3239
3240 static
3241 bool
3242 extract_gz_file(const std::string&, const std::string& gz_file, const std::string&)
3243 {
3244 # if USE_SHELL_API
3245 bool unzipped = std::system(("tar -xzf " + gz_file + " -C " + get_install()).c_str()) == EXIT_SUCCESS;
3246 # else // !USE_SHELL_API
3247 const char prog[] = {"/usr/bin/tar"};
3248 const char*const args[] =
3249 {
3250 prog, "-xzf", gz_file.c_str(), "-C", get_install().c_str(), nullptr
3251 };
3252 bool unzipped = (run_program(prog, args) == EXIT_SUCCESS);
3253 # endif // !USE_SHELL_API
3254 if (unzipped)
3255 {
3256 delete_file(gz_file);
3257 return true;
3258 }
3259 return false;
3260 }
3261
3262 # endif // !_WIN32
3263
3264 bool
3265 remote_download(const std::string& version)
3266 {
3267 assert(!version.empty());
3268
3269 # ifdef _WIN32
3270 // Download folder should be always available for Windows
3271 # else // !_WIN32
3272 // Create download folder if it does not exist on UNIX system
3273 auto download_folder = get_install();
3274 if (!file_exists(download_folder))
3275 {
3276 if (!make_directory(download_folder))
3277 return false;
3278 }
3279 # endif // _WIN32
3280
3281 auto url = "https://data.iana.org/time-zones/releases/tzdata" + version +
3282 ".tar.gz";
3283 bool result = download_to_file(url, get_download_gz_file(version),
3284 download_file_options::binary);
3285 # ifdef _WIN32
3286 if (result)
3287 {
3288 auto mapping_file = get_download_mapping_file(version);
3289 result = download_to_file(
3290 "https://raw.githubusercontent.com/unicode-org/cldr/master/"
3291 "common/supplemental/windowsZones.xml",
3292 mapping_file, download_file_options::text);
3293 }
3294 # endif // _WIN32
3295 return result;
3296 }
3297
3298 bool
3299 remote_install(const std::string& version)
3300 {
3301 auto success = false;
3302 assert(!version.empty());
3303
3304 std::string install = get_install();
3305 auto gz_file = get_download_gz_file(version);
3306 if (file_exists(gz_file))
3307 {
3308 if (file_exists(install))
3309 remove_folder_and_subfolders(install);
3310 if (make_directory(install))
3311 {
3312 if (extract_gz_file(version, gz_file, install))
3313 success = true;
3314 # ifdef _WIN32
3315 auto mapping_file_source = get_download_mapping_file(version);
3316 auto mapping_file_dest = get_install();
3317 mapping_file_dest += folder_delimiter;
3318 mapping_file_dest += "windowsZones.xml";
3319 if (!move_file(mapping_file_source, mapping_file_dest))
3320 success = false;
3321 # endif // _WIN32
3322 }
3323 }
3324 return success;
3325 }
3326
3327 #endif // HAS_REMOTE_API
3328
3329 static
3330 std::string
3331 get_version(const std::string& path)
3332 {
3333 std::string version;
3334 std::ifstream infile(path + "version");
3335 if (infile.is_open())
3336 {
3337 infile >> version;
3338 if (!infile.fail())
3339 return version;
3340 }
3341 else
3342 {
3343 infile.open(path + "NEWS");
3344 while (infile)
3345 {
3346 infile >> version;
3347 if (version == "Release")
3348 {
3349 infile >> version;
3350 return version;
3351 }
3352 }
3353 }
3354 throw std::runtime_error("Unable to get Timezone database version from " + path);
3355 }
3356
3357 static
3358 std::unique_ptr<tzdb>
3359 init_tzdb()
3360 {
3361 using namespace date;
3362 const std::string install = get_install();
3363 const std::string path = install + folder_delimiter;
3364 std::string line;
3365 bool continue_zone = false;
3366 std::unique_ptr<tzdb> db(new tzdb);
3367
3368 #if AUTO_DOWNLOAD
3369 if (!file_exists(install))
3370 {
3371 auto rv = remote_version();
3372 if (!rv.empty() && remote_download(rv))
3373 {
3374 if (!remote_install(rv))
3375 {
3376 std::string msg = "Timezone database version \"";
3377 msg += rv;
3378 msg += "\" did not install correctly to \"";
3379 msg += install;
3380 msg += "\"";
3381 throw std::runtime_error(msg);
3382 }
3383 }
3384 if (!file_exists(install))
3385 {
3386 std::string msg = "Timezone database not found at \"";
3387 msg += install;
3388 msg += "\"";
3389 throw std::runtime_error(msg);
3390 }
3391 db->version = get_version(path);
3392 }
3393 else
3394 {
3395 db->version = get_version(path);
3396 auto rv = remote_version();
3397 if (!rv.empty() && db->version != rv)
3398 {
3399 if (remote_download(rv))
3400 {
3401 remote_install(rv);
3402 db->version = get_version(path);
3403 }
3404 }
3405 }
3406 #else // !AUTO_DOWNLOAD
3407 if (!file_exists(install))
3408 {
3409 std::string msg = "Timezone database not found at \"";
3410 msg += install;
3411 msg += "\"";
3412 throw std::runtime_error(msg);
3413 }
3414 db->version = get_version(path);
3415 #endif // !AUTO_DOWNLOAD
3416
3417 CONSTDATA char*const files[] =
3418 {
3419 "africa", "antarctica", "asia", "australasia", "backward", "etcetera", "europe",
3420 "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds"
3421 };
3422
3423 for (const auto& filename : files)
3424 {
3425 std::ifstream infile(path + filename);
3426 while (infile)
3427 {
3428 std::getline(infile, line);
3429 if (!line.empty() && line[0] != '#')
3430 {
3431 std::istringstream in(line);
3432 std::string word;
3433 in >> word;
3434 if (word == "Rule")
3435 {
3436 db->rules.push_back(Rule(line));
3437 continue_zone = false;
3438 }
3439 else if (word == "Link")
3440 {
3441 db->links.push_back(link(line));
3442 continue_zone = false;
3443 }
3444 else if (word == "Leap")
3445 {
3446 db->leaps.push_back(leap(line, detail::undocumented{}));
3447 continue_zone = false;
3448 }
3449 else if (word == "Zone")
3450 {
3451 db->zones.push_back(time_zone(line, detail::undocumented{}));
3452 continue_zone = true;
3453 }
3454 else if (line[0] == '\t' && continue_zone)
3455 {
3456 db->zones.back().add(line);
3457 }
3458 else
3459 {
3460 std::cerr << line << '\n';
3461 }
3462 }
3463 }
3464 }
3465 std::sort(db->rules.begin(), db->rules.end());
3466 Rule::split_overlaps(db->rules);
3467 std::sort(db->zones.begin(), db->zones.end());
3468 db->zones.shrink_to_fit();
3469 std::sort(db->links.begin(), db->links.end());
3470 db->links.shrink_to_fit();
3471 std::sort(db->leaps.begin(), db->leaps.end());
3472 db->leaps.shrink_to_fit();
3473
3474 #ifdef _WIN32
3475 std::string mapping_file = get_install() + folder_delimiter + "windowsZones.xml";
3476 db->mappings = load_timezone_mappings_from_xml_file(mapping_file);
3477 sort_zone_mappings(db->mappings);
3478 #endif // _WIN32
3479
3480 return db;
3481 }
3482
3483 const tzdb&
3484 reload_tzdb()
3485 {
3486 #if AUTO_DOWNLOAD
3487 auto const& v = get_tzdb_list().front().version;
3488 if (!v.empty() && v == remote_version())
3489 return get_tzdb_list().front();
3490 #endif // AUTO_DOWNLOAD
3491 tzdb_list::undocumented_helper::push_front(get_tzdb_list(), init_tzdb().release());
3492 return get_tzdb_list().front();
3493 }
3494
3495 #endif // !USE_OS_TZDB
3496
3497 const tzdb&
3498 get_tzdb()
3499 {
3500 return get_tzdb_list().front();
3501 }
3502
3503 const time_zone*
3504 #if HAS_STRING_VIEW
3505 tzdb::locate_zone(std::string_view tz_name) const
3506 #else
3507 tzdb::locate_zone(const std::string& tz_name) const
3508 #endif
3509 {
3510 auto zi = std::lower_bound(zones.begin(), zones.end(), tz_name,
3511 #if HAS_STRING_VIEW
3512 [](const time_zone& z, const std::string_view& nm)
3513 #else
3514 [](const time_zone& z, const std::string& nm)
3515 #endif
3516 {
3517 return z.name() < nm;
3518 });
3519 if (zi == zones.end() || zi->name() != tz_name)
3520 {
3521 #if !USE_OS_TZDB
3522 auto li = std::lower_bound(links.begin(), links.end(), tz_name,
3523 #if HAS_STRING_VIEW
3524 [](const link& z, const std::string_view& nm)
3525 #else
3526 [](const link& z, const std::string& nm)
3527 #endif
3528 {
3529 return z.name() < nm;
3530 });
3531 if (li != links.end() && li->name() == tz_name)
3532 {
3533 zi = std::lower_bound(zones.begin(), zones.end(), li->target(),
3534 [](const time_zone& z, const std::string& nm)
3535 {
3536 return z.name() < nm;
3537 });
3538 if (zi != zones.end() && zi->name() == li->target())
3539 return &*zi;
3540 }
3541 #endif // !USE_OS_TZDB
3542 throw std::runtime_error(std::string(tz_name) + " not found in timezone database");
3543 }
3544 return &*zi;
3545 }
3546
3547 const time_zone*
3548 #if HAS_STRING_VIEW
3549 locate_zone(std::string_view tz_name)
3550 #else
3551 locate_zone(const std::string& tz_name)
3552 #endif
3553 {
3554 return get_tzdb().locate_zone(tz_name);
3555 }
3556
3557 #if USE_OS_TZDB
3558
3559 std::ostream&
3560 operator<<(std::ostream& os, const tzdb& db)
3561 {
3562 os << "Version: " << db.version << "\n\n";
3563 for (const auto& x : db.zones)
3564 os << x << '\n';
3565 #if !MISSING_LEAP_SECONDS
3566 os << '\n';
3567 for (const auto& x : db.leaps)
3568 os << x << '\n';
3569 #endif // !MISSING_LEAP_SECONDS
3570 return os;
3571 }
3572
3573 #else // !USE_OS_TZDB
3574
3575 std::ostream&
3576 operator<<(std::ostream& os, const tzdb& db)
3577 {
3578 os << "Version: " << db.version << '\n';
3579 std::string title("--------------------------------------------"
3580 "--------------------------------------------\n"
3581 "Name ""Start Y ""End Y "
3582 "Beginning ""Offset "
3583 "Designator\n"
3584 "--------------------------------------------"
3585 "--------------------------------------------\n");
3586 int count = 0;
3587 for (const auto& x : db.rules)
3588 {
3589 if (count++ % 50 == 0)
3590 os << title;
3591 os << x << '\n';
3592 }
3593 os << '\n';
3594 title = std::string("---------------------------------------------------------"
3595 "--------------------------------------------------------\n"
3596 "Name ""Offset "
3597 "Rule ""Abrev ""Until\n"
3598 "---------------------------------------------------------"
3599 "--------------------------------------------------------\n");
3600 count = 0;
3601 for (const auto& x : db.zones)
3602 {
3603 if (count++ % 10 == 0)
3604 os << title;
3605 os << x << '\n';
3606 }
3607 os << '\n';
3608 title = std::string("---------------------------------------------------------"
3609 "--------------------------------------------------------\n"
3610 "Alias ""To\n"
3611 "---------------------------------------------------------"
3612 "--------------------------------------------------------\n");
3613 count = 0;
3614 for (const auto& x : db.links)
3615 {
3616 if (count++ % 45 == 0)
3617 os << title;
3618 os << x << '\n';
3619 }
3620 os << '\n';
3621 title = std::string("---------------------------------------------------------"
3622 "--------------------------------------------------------\n"
3623 "Leap second on\n"
3624 "---------------------------------------------------------"
3625 "--------------------------------------------------------\n");
3626 os << title;
3627 for (const auto& x : db.leaps)
3628 os << x << '\n';
3629 return os;
3630 }
3631
3632 #endif // !USE_OS_TZDB
3633
3634 // -----------------------
3635
3636 #ifdef _WIN32
3637
3638 static
3639 std::string
3640 getTimeZoneKeyName()
3641 {
3642 DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
3643 auto result = GetDynamicTimeZoneInformation(&dtzi);
3644 if (result == TIME_ZONE_ID_INVALID)
3645 throw std::runtime_error("current_zone(): GetDynamicTimeZoneInformation()"
3646 " reported TIME_ZONE_ID_INVALID.");
3647 auto wlen = wcslen(dtzi.TimeZoneKeyName);
3648 char buf[128] = {};
3649 assert(sizeof(buf) >= wlen+1);
3650 wcstombs(buf, dtzi.TimeZoneKeyName, wlen);
3651 if (strcmp(buf, "Coordinated Universal Time") == 0)
3652 return "UTC";
3653 return buf;
3654 }
3655
3656 const time_zone*
3657 tzdb::current_zone() const
3658 {
3659 std::string win_tzid = getTimeZoneKeyName();
3660 std::string standard_tzid;
3661 if (!native_to_standard_timezone_name(win_tzid, standard_tzid))
3662 {
3663 std::string msg;
3664 msg = "current_zone() failed: A mapping from the Windows Time Zone id \"";
3665 msg += win_tzid;
3666 msg += "\" was not found in the time zone mapping database.";
3667 throw std::runtime_error(msg);
3668 }
3669 return locate_zone(standard_tzid);
3670 }
3671
3672 #else // !_WIN32
3673
3674 const time_zone*
3675 tzdb::current_zone() const
3676 {
3677 // On some OS's a file called /etc/localtime may
3678 // exist and it may be either a real file
3679 // containing time zone details or a symlink to such a file.
3680 // On MacOS and BSD Unix if this file is a symlink it
3681 // might resolve to a path like this:
3682 // "/usr/share/zoneinfo/America/Los_Angeles"
3683 // If it does, we try to determine the current
3684 // timezone from the remainder of the path by removing the prefix
3685 // and hoping the rest resolves to a valid timezone.
3686 // It may not always work though. If it doesn't then an
3687 // exception will be thrown by local_timezone.
3688 // The path may also take a relative form:
3689 // "../usr/share/zoneinfo/America/Los_Angeles".
3690 {
3691 struct stat sb;
3692 CONSTDATA auto timezone = "/etc/localtime";
3693 if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) {
3694 using namespace std;
3695 char rp[PATH_MAX+1] = {};
3696 if (realpath(timezone, rp) == nullptr)
3697 throw system_error(errno, system_category(), "realpath() failed");
3698 #if HAS_STRING_VIEW
3699 string_view result = rp;
3700 CONSTDATA string_view zoneinfo = "/zoneinfo/";
3701 const size_t pos = result.rfind(zoneinfo);
3702 if (pos == result.npos)
3703 throw runtime_error(
3704 "current_zone() failed to find \"/zoneinfo/\" in " + string(result));
3705 result.remove_prefix(pos + zoneinfo.size());
3706 #else
3707 string result = rp;
3708 CONSTDATA char zoneinfo[] = "/zoneinfo/";
3709 const size_t pos = result.rfind(zoneinfo);
3710 if (pos == result.npos)
3711 throw runtime_error(
3712 "current_zone() failed to find \"/zoneinfo/\" in " + result);
3713 result.erase(0, pos + sizeof(zoneinfo) - 1);
3714 #endif
3715 return locate_zone(result);
3716 }
3717 }
3718 // On embedded systems e.g. buildroot with uclibc the timezone is linked
3719 // into /etc/TZ which is a symlink to path like this:
3720 // "/usr/share/zoneinfo/uclibc/America/Los_Angeles"
3721 // If it does, we try to determine the current
3722 // timezone from the remainder of the path by removing the prefix
3723 // and hoping the rest resolves to valid timezone.
3724 // It may not always work though. If it doesn't then an
3725 // exception will be thrown by local_timezone.
3726 // The path may also take a relative form:
3727 // "../usr/share/zoneinfo/uclibc/America/Los_Angeles".
3728 {
3729 struct stat sb;
3730 CONSTDATA auto timezone = "/etc/TZ";
3731 if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) {
3732 using namespace std;
3733 string result;
3734 char rp[PATH_MAX+1] = {};
3735 if (readlink(timezone, rp, sizeof(rp)-1) > 0)
3736 result = string(rp);
3737 else
3738 throw system_error(errno, system_category(), "readlink() failed");
3739
3740 const size_t pos = result.find(get_tz_dir());
3741 if (pos != result.npos)
3742 result.erase(0, get_tz_dir().size() + 1 + pos);
3743 return locate_zone(result);
3744 }
3745 }
3746 {
3747 // On some versions of some linux distro's (e.g. Ubuntu),
3748 // the current timezone might be in the first line of
3749 // the /etc/timezone file.
3750 std::ifstream timezone_file("/etc/timezone");
3751 if (timezone_file.is_open())
3752 {
3753 std::string result;
3754 std::getline(timezone_file, result);
3755 if (!result.empty())
3756 return locate_zone(result);
3757 }
3758 // Fall through to try other means.
3759 }
3760 {
3761 // On some versions of some bsd distro's (e.g. FreeBSD),
3762 // the current timezone might be in the first line of
3763 // the /var/db/zoneinfo file.
3764 std::ifstream timezone_file("/var/db/zoneinfo");
3765 if (timezone_file.is_open())
3766 {
3767 std::string result;
3768 std::getline(timezone_file, result);
3769 if (!result.empty())
3770 return locate_zone(result);
3771 }
3772 // Fall through to try other means.
3773 }
3774 {
3775 // On some versions of some bsd distro's (e.g. iOS),
3776 // it is not possible to use file based approach,
3777 // we switch to system API, calling functions in
3778 // CoreFoundation framework.
3779 #if TARGET_OS_IPHONE
3780 std::string result = date::iOSUtils::get_current_timezone();
3781 if (!result.empty())
3782 return locate_zone(result);
3783 #endif
3784 // Fall through to try other means.
3785 }
3786 {
3787 // On some versions of some linux distro's (e.g. Red Hat),
3788 // the current timezone might be in the first line of
3789 // the /etc/sysconfig/clock file as:
3790 // ZONE="US/Eastern"
3791 std::ifstream timezone_file("/etc/sysconfig/clock");
3792 std::string result;
3793 while (timezone_file)
3794 {
3795 std::getline(timezone_file, result);
3796 auto p = result.find("ZONE=\"");
3797 if (p != std::string::npos)
3798 {
3799 result.erase(p, p+6);
3800 result.erase(result.rfind('"'));
3801 return locate_zone(result);
3802 }
3803 }
3804 // Fall through to try other means.
3805 }
3806 throw std::runtime_error("Could not get current timezone");
3807 }
3808
3809 #endif // !_WIN32
3810
3811 const time_zone*
3812 current_zone()
3813 {
3814 return get_tzdb().current_zone();
3815 }
3816
3817 } // namespace date
3818
3819 #if defined(__GNUC__) && __GNUC__ < 5
3820 # pragma GCC diagnostic pop
3821 #endif