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