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