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