From 6c87eac52f393fcd5df15a6396ec6bf58d986fbb Mon Sep 17 00:00:00 2001 From: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Date: Mon, 8 Mar 2021 17:53:27 -0800 Subject: [PATCH 01/22] : C++20 clocks, clock_cast, tzdb::leap_seconds (#1671) * [time.clocks] C++20 clocks * concepts workarounds for /BE * test failures and review feedback * buildfix * fix _HAS_CXX20/namespace nesting * _File_time_clock to filesystem namespace * [time.clock.file] tests now pass, consolidate testing code * revert whitespace change; more review feedback * utc_clock::to_sys with floating point durations * Unicode Windows API functions * YOU get constexpr, YOU get constexpr, EVERYBODY gets constexpr * Comment cleanup * SHOUTY comments: in for a penny, in for a pound * formatting * static_assert wording * improved exception handling * switch negated test * less macro-y alias * explicit return types to match Standard * renames and noexcept * misc review feedback * clang-format * addressing review comments on test coverage Co-authored-by: Miya Natsuhara --- stl/CMakeLists.txt | 4 +- stl/inc/chrono | 773 +++++++++++++++++- stl/inc/filesystem | 19 +- stl/inc/header-units.json | 1 + stl/inc/xfilesystem_abi.h | 5 +- stl/inc/xtzdb.h | 79 ++ stl/inc/xutility | 1 + .../stl_atomic_wait.files.settings.targets | 1 + .../stl_base/stl.files.settings.targets | 1 + stl/src/msvcp_atomic_wait.src | 4 + stl/src/syncstream.cpp | 31 +- stl/src/tzdb.cpp | 71 ++ tests/libcxx/expected_results.txt | 4 - tests/libcxx/skipped_tests.txt | 4 - .../test.compile.pass.cpp | 86 -- .../test.cpp | 463 +++++++++++ 16 files changed, 1403 insertions(+), 144 deletions(-) create mode 100644 stl/inc/xtzdb.h create mode 100644 stl/src/tzdb.cpp delete mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.compile.pass.cpp create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index ba137d5fef..64d282508c 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -233,6 +233,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/xtimec.h ${CMAKE_CURRENT_LIST_DIR}/inc/xtr1common ${CMAKE_CURRENT_LIST_DIR}/inc/xtree + ${CMAKE_CURRENT_LIST_DIR}/inc/xtzdb.h ${CMAKE_CURRENT_LIST_DIR}/inc/xutility ${CMAKE_CURRENT_LIST_DIR}/inc/ymath.h ${CMAKE_CURRENT_LIST_DIR}/inc/yvals.h @@ -399,6 +400,7 @@ set(SOURCES_SATELLITE_ATOMIC_WAIT ${CMAKE_CURRENT_LIST_DIR}/src/atomic_wait.cpp ${CMAKE_CURRENT_LIST_DIR}/src/parallel_algorithms.cpp ${CMAKE_CURRENT_LIST_DIR}/src/syncstream.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/tzdb.cpp ) set(SOURCES_SATELLITE_CODECVT_IDS @@ -498,7 +500,7 @@ function(add_stl_dlls D_SUFFIX THIS_CONFIG_DEFINITIONS THIS_CONFIG_COMPILE_OPTIO file(WRITE "${_ATOMIC_WAIT_DEF_NAME}" "${_ATOMIC_WAIT_DEF_CONTENTS}") add_library(msvcp${D_SUFFIX}_atomic_wait SHARED "${_ATOMIC_WAIT_DEF_NAME}") - target_link_libraries(msvcp${D_SUFFIX}_atomic_wait PRIVATE msvcp${D_SUFFIX}_atomic_wait_objects msvcp${D_SUFFIX}_satellite_objects msvcp${D_SUFFIX}_implib_objects "msvcp${D_SUFFIX}" "${TOOLSET_LIB}/vcruntime${D_SUFFIX}.lib" "${TOOLSET_LIB}/msvcrt${D_SUFFIX}.lib" "ucrt${D_SUFFIX}.lib") + target_link_libraries(msvcp${D_SUFFIX}_atomic_wait PRIVATE msvcp${D_SUFFIX}_atomic_wait_objects msvcp${D_SUFFIX}_satellite_objects msvcp${D_SUFFIX}_implib_objects "msvcp${D_SUFFIX}" "${TOOLSET_LIB}/vcruntime${D_SUFFIX}.lib" "${TOOLSET_LIB}/msvcrt${D_SUFFIX}.lib" "ucrt${D_SUFFIX}.lib" "advapi32.lib") set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES ARCHIVE_OUTPUT_NAME "msvcp140_atomic_wait${D_SUFFIX}${VCLIBS_SUFFIX}") set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") set_target_properties(msvcp${D_SUFFIX}_atomic_wait PROPERTIES OUTPUT_NAME "${_ATOMIC_WAIT_OUTPUT_NAME}") diff --git a/stl/inc/chrono b/stl/inc/chrono index 69d0f848fc..de7457d263 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -16,10 +16,15 @@ #include #if _HAS_CXX20 +#include +#include +#include #include -#ifdef __cpp_lib_concepts -#include -#endif // defined(__cpp_lib_concepts) +#include +#include +#include +#include +#include #endif // _HAS_CXX20 #pragma pack(push, _CRT_PACKING) @@ -2209,6 +2214,768 @@ namespace chrono { return hours{_Ret}; } + + // [time.zone.leap] + + // CLASS leap_second + class leap_second { + public: + leap_second(const leap_second&) = default; + leap_second& operator=(const leap_second&) = default; + + constexpr leap_second( + const sys_seconds& _Date_, const bool _Is_positive_, const seconds& _Prev_elapsed) noexcept + : _Date{_Date_}, _Is_positive{_Is_positive_} { + _Elapsed_offset = _Prev_elapsed + value(); + } + + _NODISCARD constexpr sys_seconds date() const noexcept { + return _Date; + } + + _NODISCARD constexpr seconds value() const noexcept { + return _Is_positive ? seconds{1} : seconds{-1}; + } + + _NODISCARD constexpr bool _Positive() const noexcept { + return _Is_positive; + } + + _NODISCARD constexpr seconds _Elapsed() const noexcept { + return _Elapsed_offset; + } + + private: + sys_seconds _Date; + bool _Is_positive; + seconds _Elapsed_offset; + }; + + _NODISCARD constexpr bool operator==(const leap_second& _Left, const leap_second& _Right) noexcept { + return _Left.date() == _Right.date(); + } + template + _NODISCARD constexpr bool operator==(const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + return _Left.date() == _Right; + } + + template + _NODISCARD constexpr bool operator<(const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + return _Left.date() < _Right; + } + template + _NODISCARD constexpr bool operator<(const sys_time<_Duration>& _Left, const leap_second& _Right) noexcept { + return _Left < _Right.date(); + } + + template + _NODISCARD constexpr bool operator>(const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + return _Right < _Left.date(); + } + template + _NODISCARD constexpr bool operator>(const sys_time<_Duration>& _Left, const leap_second& _Right) noexcept { + return _Right.date() < _Left; + } + + template + _NODISCARD constexpr bool operator<=(const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + return !(_Right < _Left.date()); + } + template + _NODISCARD constexpr bool operator<=(const sys_time<_Duration>& _Left, const leap_second& _Right) noexcept { + return !(_Right.date() < _Left); + } + + template + _NODISCARD constexpr bool operator>=(const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + return !(_Left.date() < _Right); + } + template + _NODISCARD constexpr bool operator>=(const sys_time<_Duration>& _Left, const leap_second& _Right) noexcept { + return !(_Left < _Right.date()); + } + +#ifdef __cpp_lib_concepts + // clang-format off + template + requires three_way_comparable_with> + _NODISCARD constexpr auto operator<=>( + const leap_second& _Left, const sys_time<_Duration>& _Right) noexcept { + // clang-format on + return _Left.date() <=> _Right; + } + _NODISCARD constexpr strong_ordering operator<=>(const leap_second& _Left, const leap_second& _Right) noexcept { + return _Left.date() <=> _Right.date(); + } +#endif // __cpp_lib_concepts + + // [time.zone.db] + + // TRANSITION: work in progress + // STRUCT tzdb + struct tzdb { + vector> leap_seconds; + bool _All_ls_positive; + }; + + _NODISCARD inline pair _Xtzdb_generate_leap_seconds( + const size_t _Current_size) { + // Returns empty vector if no new leap seconds are found. + static constexpr leap_second _Known_leap_seconds[]{ + {sys_seconds{seconds{78796800}}, true, seconds{0}}, + {sys_seconds{seconds{94694400}}, true, seconds{1}}, + {sys_seconds{seconds{126230400}}, true, seconds{2}}, + {sys_seconds{seconds{157766400}}, true, seconds{3}}, + {sys_seconds{seconds{189302400}}, true, seconds{4}}, + {sys_seconds{seconds{220924800}}, true, seconds{5}}, + {sys_seconds{seconds{252460800}}, true, seconds{6}}, + {sys_seconds{seconds{283996800}}, true, seconds{7}}, + {sys_seconds{seconds{315532800}}, true, seconds{8}}, + {sys_seconds{seconds{362793600}}, true, seconds{9}}, + {sys_seconds{seconds{394329600}}, true, seconds{10}}, + {sys_seconds{seconds{425865600}}, true, seconds{11}}, + {sys_seconds{seconds{489024000}}, true, seconds{12}}, + {sys_seconds{seconds{567993600}}, true, seconds{13}}, + {sys_seconds{seconds{631152000}}, true, seconds{14}}, + {sys_seconds{seconds{662688000}}, true, seconds{15}}, + {sys_seconds{seconds{709948800}}, true, seconds{16}}, + {sys_seconds{seconds{741484800}}, true, seconds{17}}, + {sys_seconds{seconds{773020800}}, true, seconds{18}}, + {sys_seconds{seconds{820454400}}, true, seconds{19}}, + {sys_seconds{seconds{867715200}}, true, seconds{20}}, + {sys_seconds{seconds{915148800}}, true, seconds{21}}, + {sys_seconds{seconds{1136073600}}, true, seconds{22}}, + {sys_seconds{seconds{1230768000}}, true, seconds{23}}, + {sys_seconds{seconds{1341100800}}, true, seconds{24}}, + {sys_seconds{seconds{1435708800}}, true, seconds{25}}, + {sys_seconds{seconds{1483228800}}, true, seconds{26}}, + }; + + // __std_tzdb_get_reg_leap_seconds gets leap second (LS) data from the registry, but only if it contains more + // LSs than we already know about. The registry only contains LSs after 2018, so we need to tell it how many of + // *those* we already know about. The *total* number of LSs known at this point is a combination of what the + // caller knows (_Current_size, 0 on first call) and the _Known_leap_seconds entries. + constexpr size_t _Pre_2018_count = 27; + const size_t _Known_post_2018_ls_size = + (_STD max)(_Current_size, _STD size(_Known_leap_seconds)) - _Pre_2018_count; + + size_t _Reg_post_2018_ls_size; // number of post-2018 LSs found in the registry + unique_ptr<__std_tzdb_registry_leap_info[], decltype(&__std_tzdb_delete_reg_leap_seconds)> _Reg_ls_data{ + __std_tzdb_get_reg_leap_seconds(_Known_post_2018_ls_size, &_Reg_post_2018_ls_size), + &__std_tzdb_delete_reg_leap_seconds}; + + if (_Reg_post_2018_ls_size > _Known_post_2018_ls_size && !_Reg_ls_data) { + _Xbad_alloc(); // registry has new data, but failed to allocate storage + } else if (_Reg_post_2018_ls_size == 0 && _Reg_ls_data) { + _XGetLastError(); // allocated storage for registry data, but failed to read + } + + const size_t _New_size = _Pre_2018_count + _Reg_post_2018_ls_size; // total size with registry data + decltype(tzdb::leap_seconds) _Leap_sec_info; + bool _All_ls_positive = true; + + if (_New_size > _Current_size) { + _Leap_sec_info.reserve(_New_size); + _Leap_sec_info.insert( + _Leap_sec_info.begin(), _STD cbegin(_Known_leap_seconds), _STD cend(_Known_leap_seconds)); + + for (size_t _Idx = 0; _Idx < _Reg_post_2018_ls_size; ++_Idx) { + // Leap seconds occur at _Ls._Hour:59:59. We store the next second after, so we need to add an entire + // hour. + const auto& _Ls = _Reg_ls_data[_Idx]; + const auto _Date = + static_cast(year_month_day{year{_Ls._Year}, month{_Ls._Month}, day{_Ls._Day}}) + + hours{_Ls._Hour + 1}; + _Leap_sec_info.emplace_back(_Date, !_Ls._Negative, _Leap_sec_info.back()._Elapsed()); + _All_ls_positive = _All_ls_positive && !_Ls._Negative; + } + } + + return {_Leap_sec_info, _All_ls_positive}; + } + + // TRANSITION: work in progress + // CLASS tzdb_list + class tzdb_list { + private: + using _ListType = forward_list>; + + public: + using const_iterator = _ListType::const_iterator; + + tzdb_list(const tzdb_list&) = delete; + tzdb_list& operator=(const tzdb_list&) = delete; + + tzdb_list() { + auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(0); + _Tzdb_list.emplace_front(tzdb{_STD move(_Leap_sec), _All_ls_positive}); + } + + _NODISCARD const tzdb& front() const noexcept { + _Shared_lock _Lk(_Tzdb_mutex); + return _Tzdb_list.front(); + } + + template + void _Emplace_front(_ArgsTy&&... _Args) { + _Unique_lock _Lk(_Tzdb_mutex); + _Tzdb_list.emplace_front(_STD forward<_ArgsTy>(_Args)...); + } + + const tzdb& _Reload() { + _Unique_lock _Lk(_Tzdb_mutex); + auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(_Tzdb_list.front().leap_seconds.size()); + if (!_Leap_sec.empty()) { + _Tzdb_list.emplace_front(tzdb{_STD move(_Leap_sec), _All_ls_positive}); + } + return _Tzdb_list.front(); + } + + private: + _ListType _Tzdb_list; + mutable _Smtx_t _Tzdb_mutex = {}; + + struct _NODISCARD _Shared_lock { + explicit _Shared_lock(_Smtx_t& _Mtx_) : _Mtx{&_Mtx_} { + _Smtx_lock_shared(_Mtx); + } + + _Shared_lock(const _Shared_lock&) = delete; + _Shared_lock& operator=(const _Shared_lock&) = delete; + + void _Unlock() { + if (_Owns) { + _Smtx_unlock_shared(_Mtx); + } + _Owns = false; + } + + ~_Shared_lock() { + if (_Owns) { + _Smtx_unlock_shared(_Mtx); + } + } + + _Smtx_t* _Mtx; + bool _Owns = true; + }; + + struct _NODISCARD _Unique_lock { + explicit _Unique_lock(_Smtx_t& _Mtx_) : _Mtx{&_Mtx_} { + _Smtx_lock_exclusive(_Mtx); + } + + _Unique_lock(const _Unique_lock&) = delete; + _Unique_lock& operator=(const _Unique_lock&) = delete; + + ~_Unique_lock() { + _Smtx_unlock_exclusive(_Mtx); + } + + _Smtx_t* _Mtx; + }; + }; + + inline atomic _Global_tzdb_list; + + // FUNCTION get_tzdb_list + _NODISCARD inline tzdb_list& get_tzdb_list() { + auto* _Tzdb_ptr = _Global_tzdb_list.load(); + if (_Tzdb_ptr == nullptr) { + auto _My_tzdb = static_cast(__std_calloc_crt(1, sizeof(tzdb_list))); + if (_My_tzdb == nullptr) { + _Xruntime_error("bad allocation"); + } + + try { + _STD construct_at(_My_tzdb); + } catch (const exception& _Except) { + _Xruntime_error(_Except.what()); + } + + if (_Global_tzdb_list.compare_exchange_strong(_Tzdb_ptr, _My_tzdb)) { + _Tzdb_ptr = _My_tzdb; + } else { + _STD destroy_at(_My_tzdb); + __std_free_crt(_My_tzdb); + } + } + + return *_Tzdb_ptr; + } + + // FUNCTION get_tzdb + _NODISCARD inline const tzdb& get_tzdb() { + return _CHRONO get_tzdb_list().front(); + } + + // FUNCTION reload_tzdb + inline const tzdb& reload_tzdb() { + try { + return _CHRONO get_tzdb_list()._Reload(); + } catch (const exception& _Except) { + _Xruntime_error(_Except.what()); + } + } + + // [time.clock.utc] + + class utc_clock; + template + using utc_time = time_point; + using utc_seconds = utc_time; + + // STRUCT leap_second_info + struct leap_second_info { + bool is_leap_second; + seconds elapsed; + }; + + // FUNCTION TEMPLATE get_leap_second_info + template + _NODISCARD leap_second_info get_leap_second_info(const utc_time<_Duration>& _Time) { + const utc_seconds _Time_floor = _CHRONO floor(_Time); + const auto& _Tzdb = _CHRONO get_tzdb(); + const auto& _Ls_vector = _Tzdb.leap_seconds; + + // Find first leap second after _Time. + vector::const_iterator _It; + if (_Tzdb._All_ls_positive) { + // Where "target_ls" is the next leap second at or after _Time, _It either points to: + // (1) The 2nd leap second after _Time if _Time_floor is in the range [target_ls - _Elapsed() - 1, + // target_ls), or + // (2) The leap second just after _Time otherwise. + // Note that we can always use prev(_It) to determine whether _Time is *during* a leap second insertion, + // since that falls under case (2) above. However, when we fall under case (1), we need to execute an + // additional decrement to get the correct elapsed offset. For example, if leap seconds are inserted at + // seconds {100, 200, 300, 400}, we have: + // + // UTC sys *_It + // 99 99 100 + // 100 X 200 + // 101 100 200 + // 102 101 200 + // ... + // 199 198 200 + // 200 199 300^ + // 201 X 300 + // 202 200 300 + // ... + // 299 297 300 + // 300 298 400^ + // 301 299 400^ + // 302 X 400 + // 303 300 400 + // + // ^_It points to 2nd leap second + + _It = _STD upper_bound(_Ls_vector.begin(), _Ls_vector.end(), sys_seconds{_Time_floor.time_since_epoch()}); + } else { + seconds _Prev_elapsed{0}; + for (_It = _Ls_vector.begin(); _It != _Ls_vector.end(); ++_It) { + // UTC time when leap second insertion begins. In all cases, _It->date() + _It->_Elapsed() is the *end* + // of the insertion. For a negative leap that's also the beginning, but for a positive one, insertion + // begins 1 second earlier. + const utc_seconds _This_ls_begin{ + _It->date().time_since_epoch() + (_It->_Positive() ? _Prev_elapsed : _It->_Elapsed())}; + if (_This_ls_begin > _Time_floor) { + break; + } + _Prev_elapsed = _It->_Elapsed(); + } + } + + if (_It == _Ls_vector.begin()) { + return {false, seconds{0}}; + } else { + // Convert to the last leap second before or equal to _Time. + const auto& _Last_leap = *--_It; + const utc_seconds _Utc_leap_second{_Last_leap.date().time_since_epoch() + _It->_Elapsed() - seconds{1}}; +#ifdef __cpp_lib_concepts + const auto _Leap_cmp = _Utc_leap_second <=> _Time_floor; +#else // ^^^ __cpp_lib_concepts / TRANSITION, GH-395 workaround vvv + const auto _Leap_cmp = _Utc_leap_second > _Time_floor ? strong_ordering::greater + : _Utc_leap_second == _Time_floor ? strong_ordering::equal + : strong_ordering::less; +#endif // ^^^ workaround + if (_Tzdb._All_ls_positive && _STD is_gt(_Leap_cmp)) { // Case (1) + --_It; + } + return {_Last_leap._Positive() && _STD is_eq(_Leap_cmp), _It->_Elapsed()}; + } + } + + // CLASS utc_clock + class utc_clock { + public: + using rep = system_clock::rep; + using period = system_clock::period; + using duration = duration; + using time_point = time_point; + static constexpr bool is_steady = system_clock::is_steady; + + _NODISCARD static time_point now() { + return from_sys(system_clock::now()); + } + + template + _NODISCARD static sys_time> to_sys(const utc_time<_Duration>& _Utc_time) { + using _CommonType = common_type_t<_Duration, seconds>; + const auto _Lsi{get_leap_second_info(_Utc_time)}; + _CommonType _Ticks; + if (_Lsi.is_leap_second) { + const auto _Leap_sec_minus_one = _CHRONO floor(_Utc_time.time_since_epoch()) - _Lsi.elapsed; + if constexpr (is_integral_v) { + constexpr auto _Delta{seconds{1} - _CommonType{1}}; + _Ticks = _Leap_sec_minus_one + _Delta; + } else { + const auto _Leap_sec_begin = _CHRONO ceil<_CommonType>(_Leap_sec_minus_one + seconds{1}); + _Ticks = _CommonType{_STD nextafter(_Leap_sec_begin.count(), typename _CommonType::rep{0})}; + } + } else { + _Ticks = _Utc_time.time_since_epoch() - _Lsi.elapsed; + } + return sys_time<_CommonType>{_Ticks}; + } + + template + _NODISCARD static utc_time> from_sys(const sys_time<_Duration>& _Sys_time) { + const auto& _Tzdb = _CHRONO get_tzdb(); + const auto& _Ls_vector = _Tzdb.leap_seconds; + auto _It = _STD upper_bound(_Ls_vector.begin(), _Ls_vector.end(), _CHRONO floor(_Sys_time)); + const auto _Offset = _It == _Ls_vector.begin() ? seconds{0} : (--_It)->_Elapsed(); + return utc_time>{_Sys_time.time_since_epoch() + _Offset}; + } + }; + + // [time.clock.tai] + + class tai_clock; + + template + using tai_time = time_point; + using tai_seconds = tai_time; + + // CLASS tai_clock + class tai_clock { + public: + using rep = system_clock::rep; + using period = system_clock::period; + using duration = duration; + using time_point = time_point; + static constexpr bool is_steady = system_clock::is_steady; + + static constexpr seconds _Tai_epoch_adjust{378691210}; + + _NODISCARD static time_point now() noexcept(noexcept(utc_clock::now())) { + return from_utc(utc_clock::now()); + } + + template + _NODISCARD static utc_time> to_utc( + const tai_time<_Duration>& _Time) noexcept { + return utc_time>{_Time.time_since_epoch()} - _Tai_epoch_adjust; + } + + template + _NODISCARD static tai_time> from_utc( + const utc_time<_Duration>& _Time) noexcept { + return tai_time>{_Time.time_since_epoch()} + _Tai_epoch_adjust; + } + }; + + // [time.clock.gps] + + class gps_clock; + + template + using gps_time = time_point; + using gps_seconds = gps_time; + + // CLASS gps_clock + class gps_clock { + public: + using rep = system_clock::rep; + using period = system_clock::period; + using duration = chrono::duration; + using time_point = chrono::time_point; + static constexpr bool is_steady = system_clock::is_steady; + + static constexpr seconds _Gps_epoch_adjust{-315964809}; + + _NODISCARD static time_point now() noexcept(noexcept(utc_clock::now())) { + return from_utc(utc_clock::now()); + } + + template + _NODISCARD static utc_time> to_utc( + const gps_time<_Duration>& _Time) noexcept { + return utc_time>{_Time.time_since_epoch()} - _Gps_epoch_adjust; + } + + template + _NODISCARD static gps_time> from_utc( + const utc_time<_Duration>& _Time) noexcept { + return gps_time>{_Time.time_since_epoch()} + _Gps_epoch_adjust; + } + }; +#endif // ^^^ _HAS_CXX20 +} // namespace chrono + +// [time.clock.file] + +#if _HAS_CXX20 +namespace filesystem { + struct _File_time_clock; +} // namespace filesystem + +namespace chrono { + // ALIAS file_clock + using file_clock = filesystem::_File_time_clock; + + template + using file_time = time_point; +} // namespace chrono +#endif // ^^^ _HAS_CXX20 + +#if _HAS_CXX17 +namespace filesystem { + inline constexpr long long __std_fs_file_time_epoch_adjustment = 0x19DB1DED53E8000LL; // TRANSITION, ABI + + struct _File_time_clock { // Implementation of trivial-clock + using rep = long long; + using period = chrono::system_clock::period; + using duration = chrono::duration; + using time_point = chrono::time_point<_File_time_clock>; + static constexpr bool is_steady = false; + + _NODISCARD static time_point now() noexcept { // get current time; undo epoch adjustment + return time_point(duration(_Xtime_get_ticks() + __std_fs_file_time_epoch_adjustment)); // TRANSITION, ABI + } + +#if _HAS_CXX20 + // Assumes that FILETIME counts leap seconds only after the first 27 (i.e., after 1 January 2017), even though + // systems can opt out of this behavior. + static constexpr chrono::seconds _Skipped_filetime_leap_seconds{27}; + static constexpr chrono::sys_days _Cutoff{ + chrono::year_month_day{chrono::year{2017}, chrono::January, chrono::day{1}}}; + + template + _NODISCARD static chrono::utc_time> to_utc( + const chrono::file_time<_Duration>& _File_time) { + using namespace chrono; + using _CommonType = common_type_t<_Duration, seconds>; + const auto _Ticks = + _File_time.time_since_epoch() - duration_cast(duration{__std_fs_file_time_epoch_adjustment}); + + if (_Ticks < _Cutoff.time_since_epoch()) { + return utc_clock::from_sys(sys_time<_CommonType>{_Ticks}); + } else { + return utc_time<_CommonType>{_Ticks + _Skipped_filetime_leap_seconds}; + } + } + + template + _NODISCARD static chrono::file_time> from_utc( + const chrono::utc_time<_Duration>& _Utc_time) { + using namespace chrono; + file_time> _File_time{ + duration_cast(duration{__std_fs_file_time_epoch_adjustment})}; + + if (_Utc_time < utc_seconds{_Cutoff.time_since_epoch()} + _Skipped_filetime_leap_seconds) { + _File_time += utc_clock::to_sys(_Utc_time).time_since_epoch(); + } else { + _File_time += _Utc_time.time_since_epoch() - _Skipped_filetime_leap_seconds; + } + + return _File_time; + } +#endif // ^^^ _HAS_CXX20 + }; +} // namespace filesystem +#endif // ^^^ _HAS_CXX17 + +namespace chrono { +#if _HAS_CXX20 + // [time.clock.conv] + + // STRUCT TEMPLATE clock_time_conversion + template + struct clock_time_conversion {}; + + // [time.clock.cast.id] + + template + struct clock_time_conversion<_Clock, _Clock> { + template + _NODISCARD time_point<_Clock, _Duration> operator()(const time_point<_Clock, _Duration>& _Time) const + noexcept(is_arithmetic_v) /* strengthened */ { + return _Time; + } + }; + + template <> + struct clock_time_conversion { + template + _NODISCARD sys_time<_Duration> operator()(const sys_time<_Duration>& _Time) const + noexcept(is_arithmetic_v) /* strengthened */ { + return _Time; + } + }; + + template <> + struct clock_time_conversion { + template + _NODISCARD utc_time<_Duration> operator()(const utc_time<_Duration>& _Time) const + noexcept(is_arithmetic_v) /* strengthened */ { + return _Time; + } + }; + + // [time.clock.cast.sys.utc] + + template <> + struct clock_time_conversion { + template + _NODISCARD utc_time> operator()(const sys_time<_Duration>& _Sys_time) const { + return utc_clock::from_sys(_Sys_time); + } + }; + + template <> + struct clock_time_conversion { + template + _NODISCARD sys_time> operator()(const utc_time<_Duration>& _Utc_time) const { + return utc_clock::to_sys(_Utc_time); + } + }; + + // [time.clock.cast.sys] + + // TRANSITION, GH-395 workaround, is_same_v -> same_as + template + concept _Is_time_point = requires { + typename _Ty::duration; + requires is_same_v, _Ty>; + }; + + template + concept _Convertible_to_sys_time = + is_same_v<_Clock, system_clock> || requires(const time_point<_Clock, _Duration>& _Time) { + { _Clock::to_sys(_Time) } + ->_Is_time_point; + }; + + template + concept _Convertible_from_sys_time = is_same_v<_Clock, system_clock> || requires(const sys_time<_Duration>& _Time) { + { _Clock::from_sys(_Time) } + ->_Is_time_point<_Clock>; + }; + + template + struct clock_time_conversion { + template + _NODISCARD auto operator()(const time_point<_SourceClock, _Duration>& _Time) const + noexcept(noexcept(_SourceClock::to_sys(_Time))) /* strengthened */ + requires _Convertible_to_sys_time<_SourceClock, _Duration> { + return _SourceClock::to_sys(_Time); + } + }; + + template + struct clock_time_conversion<_DestClock, system_clock> { + template + _NODISCARD auto operator()(const sys_time<_Duration>& _Time) const + noexcept(noexcept(_DestClock::from_sys(_Time))) /* strengthened */ + requires _Convertible_from_sys_time<_DestClock, _Duration> { + return _DestClock::from_sys(_Time); + } + }; + + // [time.clock.cast.utc] + + template + concept _Convertible_to_utc_time = + is_same_v<_Clock, utc_clock> || requires(const time_point<_Clock, _Duration>& _Time) { + { _Clock::to_utc(_Time) } + ->_Is_time_point; + }; + + template + concept _Convertible_from_utc_time = is_same_v<_Clock, utc_clock> || requires(const utc_time<_Duration>& _Time) { + { _Clock::from_utc(_Time) } + ->_Is_time_point<_Clock>; + }; + + template + struct clock_time_conversion { + template + _NODISCARD auto operator()(const time_point<_SourceClock, _Duration>& _Time) const + noexcept(noexcept(_SourceClock::to_utc(_Time))) /* strengthened */ + requires _Convertible_to_utc_time<_SourceClock, _Duration> { + return _SourceClock::to_utc(_Time); + } + }; + + template + struct clock_time_conversion<_DestClock, utc_clock> { + template + _NODISCARD auto operator()(const utc_time<_Duration>& _Time) const + noexcept(noexcept(_DestClock::from_utc(_Time))) /* strengthened */ + requires _Convertible_from_utc_time<_DestClock, _Duration> { + return _DestClock::from_utc(_Time); + } + }; + + // [time.clock.cast.fn] + + // FUNCTION TEMPLATE clock_cast + template + _NODISCARD auto clock_cast(const time_point<_SourceClock, _Duration>& _Time) { + constexpr bool _Has_direct_conversion = + is_invocable_v, decltype(_Time)>; + + constexpr bool _Utc_from_src = _Convertible_to_utc_time<_SourceClock, _Duration>; + constexpr bool _Sys_from_src = _Convertible_to_sys_time<_SourceClock, _Duration>; + constexpr bool _Dest_from_utc = _Convertible_from_utc_time<_DestClock, _Duration>; + constexpr bool _Dest_from_sys = _Convertible_from_sys_time<_DestClock, _Duration>; + + constexpr bool _Has_utc_conversion = _Dest_from_utc && _Utc_from_src; + constexpr bool _Has_sys_conversion = _Dest_from_sys && _Sys_from_src; + static_assert(_Has_direct_conversion || !(_Has_utc_conversion && _Has_sys_conversion), + "A two-step clock time conversion is required to be unique, either through utc_clock or system_clock, but " + "not both (N4878 [time.clock.cast.fn]/2.)"); + + constexpr bool _Has_sys_utc_conversion = _Dest_from_sys && _Utc_from_src; + constexpr bool _Has_utc_sys_conversion = _Dest_from_utc && _Sys_from_src; + static_assert(_Has_direct_conversion || _Has_utc_conversion || _Has_sys_conversion + || !(_Has_utc_sys_conversion && _Has_sys_utc_conversion), + "A three-step clock time conversion is required to be unique, either utc-to-system or system-to-utc, but " + "not both (N4878 [time.clock.cast.fn]/2)."); + + // clang-format off + if constexpr (_Has_direct_conversion) { + return clock_time_conversion<_DestClock, _SourceClock>{}(_Time); + } else if constexpr (_Has_utc_conversion) { + return clock_time_conversion<_DestClock, utc_clock>{}( + clock_time_conversion{}(_Time)); + } else if constexpr (_Has_sys_conversion) { + return clock_time_conversion<_DestClock, system_clock>{}( + clock_time_conversion{}(_Time)); + } else if constexpr (_Has_sys_utc_conversion) { + return clock_time_conversion<_DestClock, system_clock>{}( + clock_time_conversion{}( + clock_time_conversion{}(_Time))); + } else if constexpr (_Has_utc_sys_conversion) { + return clock_time_conversion<_DestClock, utc_clock>{}( + clock_time_conversion{}( + clock_time_conversion{}(_Time))); + } else { + static_assert(_Always_false<_Duration>, "No clock time conversion exists from source clock type to " + "destination clock type (N4878 [time.clock.cast.fn]/1)."); + } + // clang-format on + } #endif // _HAS_CXX20 } // namespace chrono diff --git a/stl/inc/filesystem b/stl/inc/filesystem index 05341d65c4..559220566a 100644 --- a/stl/inc/filesystem +++ b/stl/inc/filesystem @@ -2122,20 +2122,11 @@ namespace filesystem { __std_fs_stats_flags::_Attributes | __std_fs_stats_flags::_Reparse_tag; // ALIAS file_time_type - struct _File_time_clock { // Implementation of trivial-clock - using rep = long long; - using period = chrono::system_clock::period; - using duration = chrono::duration; - using time_point = chrono::time_point<_File_time_clock>; - - static constexpr bool is_steady = false; - - _NODISCARD static time_point now() noexcept { // get current time; undo epoch adjustment - return time_point(duration(_Xtime_get_ticks() + __std_fs_file_time_epoch_adjustment)); // TRANSITION, ABI - } - }; - - using file_time_type = chrono::time_point<_File_time_clock>; +#if _HAS_CXX20 + using file_time_type = _CHRONO time_point<_CHRONO file_clock>; +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv + using file_time_type = _CHRONO time_point; +#endif // ^^^ !_HAS_CXX20 // CLASS directory_entry class directory_entry { diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index d33b9abc62..f27775fd22 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -143,6 +143,7 @@ "xtimec.h", "xtr1common", "xtree", + "xtzdb.h", "xutility", "ymath.h", "yvals.h", diff --git a/stl/inc/xfilesystem_abi.h b/stl/inc/xfilesystem_abi.h index 52f8c8f740..052af6cc93 100644 --- a/stl/inc/xfilesystem_abi.h +++ b/stl/inc/xfilesystem_abi.h @@ -19,9 +19,8 @@ _STL_DISABLE_CLANG_WARNINGS #pragma push_macro("new") #undef new -inline constexpr size_t __std_fs_max_path = 260; // #define MAX_PATH 260 -inline constexpr size_t __std_fs_temp_path_max = __std_fs_max_path + 1; -inline constexpr long long __std_fs_file_time_epoch_adjustment = 0x19DB1DED53E8000LL; // TRANSITION, ABI +inline constexpr size_t __std_fs_max_path = 260; // #define MAX_PATH 260 +inline constexpr size_t __std_fs_temp_path_max = __std_fs_max_path + 1; enum class __std_win_error : unsigned long { _Success = 0, // #define ERROR_SUCCESS 0L diff --git a/stl/inc/xtzdb.h b/stl/inc/xtzdb.h new file mode 100644 index 0000000000..734d050ecf --- /dev/null +++ b/stl/inc/xtzdb.h @@ -0,0 +1,79 @@ +// xtzdb.h internal header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _XTZDB_H +#define _XTZDB_H +#include +#if _STL_COMPILER_PREPROCESSOR +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +struct __std_tzdb_registry_leap_info { + uint16_t _Year; + uint16_t _Month; + uint16_t _Day; + uint16_t _Hour; + uint16_t _Negative; + uint16_t _Reserved; +}; + +_EXTERN_C + +__std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( + size_t _Prev_reg_ls_size, size_t* _Current_reg_ls_size) noexcept; + +void __stdcall __std_tzdb_delete_reg_leap_seconds(__std_tzdb_registry_leap_info* _Rlsi) noexcept; + +_NODISCARD void* __stdcall __std_calloc_crt(size_t _Count, size_t _Size) noexcept; +void __stdcall __std_free_crt(void* _Ptr) noexcept; + +_END_EXTERN_C + +_STD_BEGIN + +template +class _Crt_allocator { +public: + using value_type = _Ty; + using propagate_on_container_move_assignment = true_type; + using is_always_equal = true_type; + + constexpr _Crt_allocator() noexcept = default; + + constexpr _Crt_allocator(const _Crt_allocator&) noexcept = default; + template + constexpr _Crt_allocator(const _Crt_allocator<_Other>&) noexcept {} + + _NODISCARD __declspec(allocator) _Ty* allocate(_CRT_GUARDOVERFLOW const size_t _Count) { + const auto _Ptr = __std_calloc_crt(_Count, sizeof(_Ty)); + if (!_Ptr) { + _Xbad_alloc(); + } + return static_cast<_Ty*>(_Ptr); + } + + void deallocate(_Ty* const _Ptr, size_t) noexcept { + __std_free_crt(_Ptr); + } +}; + +_STD_END + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _XTZDB_H diff --git a/stl/inc/xutility b/stl/inc/xutility index 2179e1251f..028e3e288f 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5456,6 +5456,7 @@ _INLINE_VAR constexpr allocator_arg_t allocator_arg{}; [[noreturn]] _CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _Xout_of_range(_In_z_ const char*); [[noreturn]] _CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _Xoverflow_error(_In_z_ const char*); [[noreturn]] _CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _Xruntime_error(_In_z_ const char*); +[[noreturn]] _CRTIMP2_PURE void __CLRCALL_PURE_OR_CDECL _XGetLastError(); // STRUCT TEMPLATE uses_allocator template diff --git a/stl/msbuild/stl_atomic_wait/stl_atomic_wait.files.settings.targets b/stl/msbuild/stl_atomic_wait/stl_atomic_wait.files.settings.targets index 05ebeab886..6146998be6 100644 --- a/stl/msbuild/stl_atomic_wait/stl_atomic_wait.files.settings.targets +++ b/stl/msbuild/stl_atomic_wait/stl_atomic_wait.files.settings.targets @@ -9,6 +9,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception $(CrtRoot)\github\stl\src\atomic_wait.cpp; $(CrtRoot)\github\stl\src\parallel_algorithms.cpp; $(CrtRoot)\github\stl\src\syncstream.cpp; + $(CrtRoot)\github\stl\src\tzdb.cpp; "> nativecpp diff --git a/stl/msbuild/stl_base/stl.files.settings.targets b/stl/msbuild/stl_base/stl.files.settings.targets index d14a8bc528..05ff1f5ddb 100644 --- a/stl/msbuild/stl_base/stl.files.settings.targets +++ b/stl/msbuild/stl_base/stl.files.settings.targets @@ -13,6 +13,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception $(CrtRoot)\github\stl\src\parallel_algorithms.cpp; $(CrtRoot)\github\stl\src\special_math.cpp; $(CrtRoot)\github\stl\src\syncstream.cpp; + $(CrtRoot)\github\stl\src\tzdb.cpp; $(CrtRoot)\github\stl\src\ulocale.cpp; "> nativecpp diff --git a/stl/src/msvcp_atomic_wait.src b/stl/src/msvcp_atomic_wait.src index 240916023e..2c7b1b76b3 100644 --- a/stl/src/msvcp_atomic_wait.src +++ b/stl/src/msvcp_atomic_wait.src @@ -20,11 +20,15 @@ EXPORTS __std_atomic_wait_get_remaining_timeout __std_atomic_wait_indirect __std_bulk_submit_threadpool_work + __std_calloc_crt __std_close_threadpool_work __std_create_threadpool_work + __std_tzdb_delete_reg_leap_seconds __std_execution_wait_on_uchar __std_execution_wake_by_address_all + __std_free_crt __std_parallel_algorithms_hw_threads __std_release_shared_mutex_for_instance __std_submit_threadpool_work + __std_tzdb_get_reg_leap_seconds __std_wait_for_threadpool_work_callbacks diff --git a/stl/src/syncstream.cpp b/stl/src/syncstream.cpp index 77e7df373e..6a3454a56b 100644 --- a/stl/src/syncstream.cpp +++ b/stl/src/syncstream.cpp @@ -4,13 +4,12 @@ // initialize syncstream mutex map #include -#include #include #include #include #include -#include #include +#include #pragma warning(disable : 4074) #pragma init_seg(compiler) @@ -23,33 +22,7 @@ namespace { size_t _Ref_count = 0; }; - template - class _Crt_allocator { - public: - using value_type = _Ty; - using propagate_on_container_move_assignment = _STD true_type; - using is_always_equal = _STD true_type; - - constexpr _Crt_allocator() noexcept = default; - - constexpr _Crt_allocator(const _Crt_allocator&) noexcept = default; - template - constexpr _Crt_allocator(const _Crt_allocator<_Other>&) noexcept {} - - _NODISCARD __declspec(allocator) _Ty* allocate(_CRT_GUARDOVERFLOW const size_t _Count) { - const auto _Ptr = _calloc_crt(_Count, sizeof(_Ty)); - if (!_Ptr) { - throw _STD bad_alloc{}; - } - return static_cast<_Ty*>(_Ptr); - } - - void deallocate(_Ty* const _Ptr, size_t) noexcept { - _free_crt(_Ptr); - } - }; - - using _Map_alloc = _Crt_allocator<_STD pair>; + using _Map_alloc = _STD _Crt_allocator<_STD pair>; using _Map_type = _STD map, _Map_alloc>; _Map_type _Lookup_map; diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp new file mode 100644 index 0000000000..f49ff2ff79 --- /dev/null +++ b/stl/src/tzdb.cpp @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +#pragma comment(lib, "Advapi32") + +_EXTERN_C + +__std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( + const size_t prev_reg_ls_size, size_t* const current_reg_ls_size) noexcept { + // On exit--- + // *current_reg_ls_size <= prev_reg_ls_size, reg_ls_data == nullptr --> no new data + // *current_reg_ls_size > prev_reg_ls_size, reg_ls_data != nullptr --> new data, successfully read + // *current_reg_ls_size == 0, reg_ls_data != nullptr --> new data, failed reading + // *current_reg_ls_size > prev_reg_ls_size, reg_ls_data == nullptr --> new data, failed allocation + + constexpr auto reg_key_name = LR"(SYSTEM\CurrentControlSet\Control\LeapSecondInformation)"; + constexpr auto reg_subkey_name = L"LeapSeconds"; + *current_reg_ls_size = 0; + HKEY leap_sec_key = nullptr; + + LSTATUS status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_key_name, 0, KEY_READ, &leap_sec_key); + if (status != ERROR_SUCCESS) { + // May not exist on older systems. Treat this as equivalent to the key existing but with no data. + return nullptr; + } + + DWORD byte_size = 0; + status = RegQueryValueExW(leap_sec_key, reg_subkey_name, nullptr, nullptr, nullptr, &byte_size); + static_assert(sizeof(__std_tzdb_registry_leap_info) == 12); + const auto ls_size = byte_size / 12; + *current_reg_ls_size = ls_size; + + __std_tzdb_registry_leap_info* reg_ls_data = nullptr; + if ((status == ERROR_SUCCESS || status == ERROR_MORE_DATA) && ls_size > prev_reg_ls_size) { + try { + reg_ls_data = new __std_tzdb_registry_leap_info[ls_size]; + status = RegQueryValueExW( + leap_sec_key, reg_subkey_name, nullptr, nullptr, reinterpret_cast(reg_ls_data), &byte_size); + if (status != ERROR_SUCCESS) { + *current_reg_ls_size = 0; + } + } catch (...) { + } + } + + RegCloseKey(leap_sec_key); + if (status != ERROR_SUCCESS) { + SetLastError(status); + } + + return reg_ls_data; +} + +void __stdcall __std_tzdb_delete_reg_leap_seconds(__std_tzdb_registry_leap_info* _Rlsi) noexcept { + delete[] _Rlsi; +} + +_NODISCARD void* __stdcall __std_calloc_crt(const size_t count, const size_t size) noexcept { + return _calloc_crt(count, size); +} + +void __stdcall __std_free_crt(void* p) noexcept { + _free_crt(p); +} + +_END_EXTERN_C diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 229c4d2141..39c03dca85 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -291,10 +291,6 @@ std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass. std/utilities/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.clock/time.clock.file/consistency.pass.cpp FAIL -std/utilities/time/time.clock/time.clock.file/file_time.pass.cpp FAIL -std/utilities/time/time.clock/time.clock.file/now.pass.cpp FAIL -std/utilities/time/time.clock/time.clock.file/rep_signed.pass.cpp FAIL # C++20 P0466R5 "Layout-Compatibility And Pointer-Interconvertibility Traits" std/language.support/support.limits/support.limits.general/type_traits.version.pass.cpp:1 FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index a8fe00a48f..070c57f94f 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -291,10 +291,6 @@ utilities\time\time.cal\time.cal.ymd\time.cal.ymd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymdlast\time.cal.ymdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwdlast\time.cal.ymwdlast.nonmembers\streaming.pass.cpp -utilities\time\time.clock\time.clock.file\consistency.pass.cpp -utilities\time\time.clock\time.clock.file\file_time.pass.cpp -utilities\time\time.clock\time.clock.file\now.pass.cpp -utilities\time\time.clock\time.clock.file\rep_signed.pass.cpp # C++20 P0466R5 "Layout-Compatibility And Pointer-Interconvertibility Traits" language.support\support.limits\support.limits.general\type_traits.version.pass.cpp diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.compile.pass.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.compile.pass.cpp deleted file mode 100644 index 26ac91006b..0000000000 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.compile.pass.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include - -using namespace std::chrono; - -struct not_a_clock { - bool rep(); - static char period; - int duration(); - static float time_point; - using is_steady = long; - static int now; -}; - -struct real_fake_clock { - using rep = bool; - using period = char; - using duration = float; - using time_point = int; - static long is_steady; - static short now(); -}; - -struct no_rep { - using period = char; - using duration = float; - using time_point = int; - static long is_steady; - static short now(); -}; - -struct no_period { - using rep = bool; - using duration = float; - using time_point = int; - static long is_steady; - static short now(); -}; - -struct no_duration { - using rep = bool; - using period = char; - using time_point = int; - static long is_steady; - static short now(); -}; - -struct no_time_point { - using rep = bool; - using period = char; - using duration = float; - static long is_steady; - static short now(); -}; - -struct no_steady { - using rep = bool; - using period = char; - using duration = float; - using time_point = int; - static short now(); -}; - -struct no_now { - using rep = bool; - using period = char; - using duration = float; - using time_point = int; - static long is_steady; -}; - -static_assert(is_clock::value, "steady_clock is not a clock"); -static_assert(is_clock_v, "steady_clock is not a clock"); -static_assert(is_clock_v, "real_fake_clock is not a clock"); -static_assert(!is_clock_v, "not_a_clock is a clock"); - -static_assert(!is_clock_v, "no_rep is a clock"); -static_assert(!is_clock_v, "no_period is a clock"); -static_assert(!is_clock_v, "no_duration is a clock"); -static_assert(!is_clock_v, "no_time_point is a clock"); -static_assert(!is_clock_v, "no_steady is a clock"); -static_assert(!is_clock_v, "no_now is a clock"); - -int main() {} // COMPILE-ONLY diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp new file mode 100644 index 0000000000..cc8b5a8096 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp @@ -0,0 +1,463 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +struct not_a_clock { + bool rep(); + static char period; + int duration(); + static float time_point; + using is_steady = long; + static int now; +}; + +struct real_fake_clock { + using rep = bool; + using period = char; + using duration = float; + using time_point = int; + static long is_steady; + static short now(); +}; + +struct no_rep { + using period = char; + using duration = float; + using time_point = int; + static long is_steady; + static short now(); +}; + +struct no_period { + using rep = bool; + using duration = float; + using time_point = int; + static long is_steady; + static short now(); +}; + +struct no_duration { + using rep = bool; + using period = char; + using time_point = int; + static long is_steady; + static short now(); +}; + +struct no_time_point { + using rep = bool; + using period = char; + using duration = float; + static long is_steady; + static short now(); +}; + +struct no_steady { + using rep = bool; + using period = char; + using duration = float; + using time_point = int; + static short now(); +}; + +struct no_now { + using rep = bool; + using period = char; + using duration = float; + using time_point = int; + static long is_steady; +}; + +static_assert(is_clock::value, "steady_clock is not a clock"); +static_assert(is_clock_v, "steady_clock is not a clock"); +static_assert(is_clock_v, "real_fake_clock is not a clock"); +static_assert(!is_clock_v, "not_a_clock is a clock"); + +static_assert(!is_clock_v, "no_rep is a clock"); +static_assert(!is_clock_v, "no_period is a clock"); +static_assert(!is_clock_v, "no_duration is a clock"); +static_assert(!is_clock_v, "no_time_point is a clock"); +static_assert(!is_clock_v, "no_steady is a clock"); +static_assert(!is_clock_v, "no_now is a clock"); + +void test_is_leap_second(const year_month_day& ymd) { + const auto ls = sys_days{ymd}; + const auto& leap_seconds = get_tzdb().leap_seconds; + assert(find(leap_seconds.begin(), leap_seconds.end(), ls + days{1}) != leap_seconds.end()); + assert(get_leap_second_info(utc_clock::from_sys(ls) + days{1}).is_leap_second); +} + +constexpr bool test_leap_second() { + constexpr int jun_leap_second_years[] = {1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015}; + constexpr int dec_leap_second_years[] = { + 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1987, 1989, 1990, 1995, 1998, 2005, 2008, 2016}; + static_assert(size(jun_leap_second_years) + size(dec_leap_second_years) == 27); + + if (!is_constant_evaluated()) { + for (const auto& ls_year : jun_leap_second_years) { + test_is_leap_second(30d / June / year{ls_year}); + } + for (const auto& ls_year : dec_leap_second_years) { + test_is_leap_second(31d / December / year{ls_year}); + } + } + + constexpr leap_second leap{sys_seconds{42s}, true, 0s}; + leap_second leap2(leap); // copy construct + leap_second leap3{sys_seconds{41s}, true, 0s}; + leap3 = leap2; // copy assign + + constexpr sys_seconds smaller{41s}; + constexpr sys_seconds equal{42s}; + constexpr sys_seconds larger{43s}; + + assert(equal == leap); + assert(leap == equal); + static_assert(noexcept(equal == leap)); + static_assert(noexcept(leap == equal)); + + assert(leap < larger); + assert(smaller < leap); + static_assert(noexcept(leap < larger)); + static_assert(noexcept(smaller < leap)); + + assert(larger > leap); + assert(leap > smaller); + static_assert(noexcept(larger > leap)); + static_assert(noexcept(leap > smaller)); + + assert(equal <= leap); + assert(smaller <= leap); + assert(leap <= equal); + assert(leap <= larger); + static_assert(noexcept(equal <= leap)); + static_assert(noexcept(leap <= equal)); + + assert(equal >= leap); + assert(larger >= leap); + assert(leap >= equal); + assert(leap >= smaller); + static_assert(noexcept(equal >= leap)); + static_assert(noexcept(leap >= equal)); + +#ifdef __cpp_lib_concepts // TRANSITION, GH-395 + static_assert(is_eq(leap <=> equal)); + static_assert(is_lt(leap <=> larger)); + static_assert(is_gt(leap <=> smaller)); + static_assert(is_lteq(leap <=> larger)); + static_assert(is_gteq(leap <=> smaller)); + static_assert(is_lteq(leap <=> equal)); + static_assert(is_gteq(leap <=> equal)); + static_assert(noexcept(leap <=> equal)); + + static_assert(is_eq(leap <=> leap_second{equal, true, 0s})); + static_assert(is_lt(leap <=> leap_second{larger, true, 0s})); + static_assert(is_gt(leap <=> leap_second{smaller, true, 0s})); + static_assert(is_lteq(leap <=> leap_second{larger, true, 0s})); + static_assert(is_gteq(leap <=> leap_second{smaller, true, 0s})); + static_assert(is_lteq(leap <=> leap_second{equal, true, 0s})); + static_assert(is_gteq(leap <=> leap_second{equal, true, 0s})); + static_assert(noexcept(leap <=> leap_second{equal, true, 0s})); +#endif // __cpp_lib_concepts + + static_assert(noexcept(leap.date())); + static_assert(noexcept(leap.value())); + static_assert(leap_second{sys_seconds{42s}, true, 0s}.date() == sys_seconds{42s}); + static_assert(leap_second{sys_seconds{42s}, true, 0s}.value() == 1s); + static_assert(leap_second{sys_seconds{42s}, false, 0s}.value() == -1s); + + return true; +} + +constexpr bool operator==(const leap_second_info& lhs, const leap_second_info& rhs) { + return lhs.is_leap_second == rhs.is_leap_second && lhs.elapsed == rhs.elapsed; +} + +template +void test_leap_second_info(const leap_second& leap, seconds accum) { + const bool is_positive = (leap.value() == 1s); + // First UTC time when leap is counted, before insertion of a positive leap, after insertion of a negative one. + const utc_time> utc_leap{leap.date().time_since_epoch() + accum + (is_positive ? 0s : -1s)}; + + auto lsi = get_leap_second_info(utc_leap - 1s); + assert(lsi == (leap_second_info{false, accum})); + + lsi = get_leap_second_info(utc_leap - 500ms); + assert(lsi == (leap_second_info{false, accum})); + + accum += leap.value(); + lsi = get_leap_second_info(utc_leap); + assert(lsi == (leap_second_info{is_positive, accum})); + + lsi = get_leap_second_info(utc_leap + 500ms); + assert(lsi == (leap_second_info{is_positive, accum})); + + lsi = get_leap_second_info(utc_leap + 1s); + assert(lsi == (leap_second_info{false, accum})); +} + +template +void test_utc_clock_to_sys(const leap_second& leap) { + sys_time before_leap; + if constexpr (is_integral_v) { + before_leap = leap.date() - Duration{1}; + } else { + before_leap = + sys_time{Duration{nextafter(leap.date().time_since_epoch().count(), typename Duration::rep{0})}}; + } + + auto u = utc_clock::from_sys(before_leap); // just before leap second + assert(utc_clock::from_sys(utc_clock::to_sys(u)) == u); + if (leap.value() == 1s) { + u += Duration{1}; + assert(utc_clock::to_sys(u) == before_leap); // during + } else { + assert(utc_clock::from_sys(utc_clock::to_sys(u)) == u); + } + + u += 1s; + assert(utc_clock::from_sys(utc_clock::to_sys(u)) == u); // just after +} + +template +void test_file_clock_from_utc(const leap_second& leap) { + static_assert(is_same_v); + + const auto file_leap = clock_cast(leap.date()); + + auto u = utc_clock::from_sys(leap.date() - Duration{1}); // just before leap second + assert(file_clock::to_utc(file_clock::from_utc(u)) == u); + + if (leap.value() == 1s && leap.date() <= sys_days{1d / January / 2017y}) { + u += Duration{1}; + assert(file_clock::from_utc(u) == file_leap - Duration{1}); // during + } else { + assert(file_clock::to_utc(file_clock::from_utc(u)) == u); + } + + u += 1s; + assert(file_clock::to_utc(file_clock::from_utc(u)) == u); // just after +} + +void test_utc_clock_from_sys(const leap_second& leap, seconds offset) { + // Generalized from [time.clock.utc.members]/3 Example 1. + auto t = leap.date() - 2ns; + auto u = utc_clock::from_sys(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1ns; + u = utc_clock::from_sys(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1ns; + u = utc_clock::from_sys(t); + offset += leap.value(); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1ns; + u = utc_clock::from_sys(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); +} + +void test_file_clock_utc() { + const auto file_epoch{utc_clock::from_sys(sys_days{January / 1 / 1601})}; + assert(file_clock::to_utc(file_time{0s}) == file_epoch); + assert(file_clock::from_utc(file_epoch) == file_time{0s}); +} + +void test_file_clock_to_utc(const leap_second& leap, seconds offset) { + // FILETIME counts leap seconds after 1 January 2017, so offset is constant thereafter. + constexpr sys_days file_time_cutoff{1d / January / 2017y}; + auto t = clock_cast(leap.date()) - 2us; + if (leap > file_time_cutoff) { + offset = 27s; + } + + offset -= duration_cast(file_clock::duration{filesystem::__std_fs_file_time_epoch_adjustment}); + + auto u = file_clock::to_utc(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1us; + u = file_clock::to_utc(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1us; + u = file_clock::to_utc(t); + if (leap.date() <= file_time_cutoff) { + offset += leap.value(); + } + + assert(u.time_since_epoch() - t.time_since_epoch() == offset); + + t += 1us; + u = file_clock::to_utc(t); + assert(u.time_since_epoch() - t.time_since_epoch() == offset); +} + +void test_gps_tai_clocks_utc() { + const auto tai_epoch{utc_clock::from_sys(sys_days{January / 1 / 1958} - seconds{10})}; + const auto gps_epoch{utc_clock::from_sys(sys_days{January / Sunday[1] / 1980})}; + + assert(tai_clock::to_utc(tai_seconds{0s}) == tai_epoch); + assert(tai_clock::from_utc(tai_epoch) == tai_seconds{0s}); + + assert(gps_clock::to_utc(gps_seconds{0s}) == gps_epoch); + assert(gps_clock::from_utc(gps_epoch) == gps_seconds{0s}); +} + +void test_clock_now() { + auto sys_now = system_clock::now(); + auto utc_now = utc_clock::now(); + auto tai_now = tai_clock::now(); + auto gps_now = gps_clock::now(); + auto file_now = file_clock::now(); + + static_assert(is_same_v, decltype(sys_now)>); + static_assert(is_same_v, decltype(utc_now)>); + static_assert(is_same_v, decltype(tai_now)>); + static_assert(is_same_v, decltype(gps_now)>); + static_assert(is_same_v, decltype(file_now)>); +} + +void test_clock_cast() { + sys_days st(2020y / January / 1); + const auto ut = utc_clock::from_sys(st); + const auto tt = tai_clock::from_utc(ut); + const auto gt = gps_clock::from_utc(ut); + const auto ft = file_clock::from_utc(ut); + + assert(clock_cast(ut) == ut); + assert(clock_cast(st) == ut); + assert(clock_cast(tt) == ut); + assert(clock_cast(gt) == ut); + assert(clock_cast(ft) == ut); + + assert(clock_cast(ut) == st); + assert(clock_cast(st) == st); + assert(clock_cast(tt) == st); + assert(clock_cast(gt) == st); + assert(clock_cast(ft) == st); + + assert(clock_cast(ut) == tt); + assert(clock_cast(st) == tt); + assert(clock_cast(tt) == tt); + assert(clock_cast(gt) == tt); + assert(clock_cast(ft) == tt); + + assert(clock_cast(ut) == gt); + assert(clock_cast(st) == gt); + assert(clock_cast(tt) == gt); + assert(clock_cast(gt) == gt); + assert(clock_cast(ft) == gt); + + assert(clock_cast(ut) == ft); + assert(clock_cast(st) == ft); + assert(clock_cast(tt) == ft); + assert(clock_cast(gt) == ft); + assert(clock_cast(ft) == ft); + + // [time.clock.utc.overview]/1 Example 1 + assert(clock_cast(sys_seconds{sys_days{1970y / January / 1}}).time_since_epoch() == 0s); + assert(clock_cast(sys_seconds{sys_days{2000y / January / 1}}).time_since_epoch() == 946'684'822s); +} + +static_assert(is_clock_v); +static_assert(is_clock_v); +static_assert(is_clock_v); +static_assert(is_clock_v); + +int main() { + assert(test_leap_second()); + static_assert(test_leap_second()); + + // This is the only date/time of a leap second insertion that can be represented by a duration. + assert(utc_clock::to_sys(utc_time>{duration{78796800.0f}}).time_since_epoch().count() + == nextafter(78796800.0f, 0.0f)); + + test_clock_now(); + + seconds offset{0}; + for (const auto& leap : get_tzdb().leap_seconds) { + test_leap_second_info(leap, offset); + test_leap_second_info(leap, offset); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys>(leap); + test_file_clock_from_utc(leap); + test_file_clock_from_utc(leap); + test_utc_clock_from_sys(leap, offset); + test_file_clock_to_utc(leap, offset); + offset += leap.value(); + assert(leap._Elapsed() == offset); + } + test_gps_tai_clocks_utc(); + test_file_clock_utc(); + test_clock_cast(); + + // a negative leap second when the accumulated offset is positive + { + auto my_tzdb = get_tzdb_list().front(); + auto& leap_vec = my_tzdb.leap_seconds; + leap_vec.erase(leap_vec.begin() + 27, leap_vec.end()); + leap_vec.emplace_back(sys_days{1d / January / 2020y}, false, leap_vec.back()._Elapsed()); + leap_vec.emplace_back(sys_days{1d / January / 2021y}, true, leap_vec.back()._Elapsed()); + my_tzdb._All_ls_positive = false; + get_tzdb_list()._Emplace_front(move(my_tzdb)); + } + + offset = 0s; + for (const auto& leap : get_tzdb().leap_seconds) { + test_leap_second_info(leap, offset); + test_leap_second_info(leap, offset); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys>(leap); + test_file_clock_from_utc(leap); + test_file_clock_from_utc(leap); + test_utc_clock_from_sys(leap, offset); + test_file_clock_to_utc(leap, offset); + offset += leap.value(); + assert(leap._Elapsed() == offset); + } + + // positive and negative leap seconds when the accumulated offset is negative + { + auto my_tzdb = get_tzdb_list().front(); + auto& leap_vec = my_tzdb.leap_seconds; + leap_vec.erase(leap_vec.begin() + 27, leap_vec.end()); + for (int i = 0; i < 30; ++i) { + leap_vec.emplace_back(sys_days{1d / January / year{i + 2020}}, false, leap_vec.back()._Elapsed()); + } + leap_vec.emplace_back(sys_days{1d / January / 2060y}, true, leap_vec.back()._Elapsed()); + get_tzdb_list()._Emplace_front(move(my_tzdb)); + } + + offset = 0s; + for (const auto& leap : get_tzdb().leap_seconds) { + test_leap_second_info(leap, offset); + test_leap_second_info(leap, offset); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys(leap); + test_utc_clock_to_sys>(leap); + test_file_clock_from_utc(leap); + test_file_clock_from_utc(leap); + test_utc_clock_from_sys(leap, offset); + test_file_clock_to_utc(leap, offset); + offset += leap.value(); + assert(leap._Elapsed() == offset); + } + + return 0; +} From bf56ad9d7dab46015aa9fc32f064af31dd10e5ff Mon Sep 17 00:00:00 2001 From: d-winsor Date: Fri, 12 Mar 2021 12:43:08 -0800 Subject: [PATCH 02/22] Loading `time_zone` and `time_zone_link` names (#1723) * Loading time_zone names from icu.dll * formatting * Addressed feedback and fixed tests. * Removed manual link map and altered tests. * 121 chars in test script hacks :( * Try again with tests * Test should run now * I think my previous changes got flagged? * Trying to get output from tests. * Win10 20H2 VMSS. * Reverted CI test hacks * Feedback and error handling. * Version, error handling and feedback * More feedback * Remove qualifiers from test Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> Co-authored-by: Stephan T. Lavavej Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- azure-devops/create-vmss.ps1 | 12 +- azure-pipelines.yml | 2 +- stl/inc/chrono | 153 +++++++- stl/inc/xtzdb.h | 30 ++ stl/src/msvcp_atomic_wait.src | 4 + stl/src/tzdb.cpp | 357 ++++++++++++++++++ tests/std/test.lst | 1 + .../test.cpp | 17 +- .../env.lst | 4 + .../test.cpp | 107 ++++++ 10 files changed, 673 insertions(+), 14 deletions(-) create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/env.lst create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp diff --git a/azure-devops/create-vmss.ps1 b/azure-devops/create-vmss.ps1 index 64fa971fdc..a927b1cc16 100644 --- a/azure-devops/create-vmss.ps1 +++ b/azure-devops/create-vmss.ps1 @@ -25,7 +25,9 @@ $Prefix = 'StlBuild-' + (Get-Date -Format 'yyyy-MM-dd') $VMSize = 'Standard_D32ds_v4' $ProtoVMName = 'PROTOTYPE' $LiveVMPrefix = 'BUILD' -$WindowsServerSku = '2019-Datacenter' +$ImagePublisher = 'MicrosoftWindowsDesktop' +$ImageOffer = 'Windows-10' +$ImageSku = '20h2-ent-g2' $ProgressActivity = 'Creating Scale Set' $TotalProgress = 12 @@ -268,9 +270,9 @@ $VM = Set-AzVMOperatingSystem ` $VM = Add-AzVMNetworkInterface -VM $VM -Id $Nic.Id $VM = Set-AzVMSourceImage ` -VM $VM ` - -PublisherName 'MicrosoftWindowsServer' ` - -Offer 'WindowsServer' ` - -Skus $WindowsServerSku ` + -PublisherName $ImagePublisher ` + -Offer $ImageOffer ` + -Skus $ImageSku ` -Version latest $VM = Set-AzVMBootDiagnostic -VM $VM -Disable @@ -340,7 +342,7 @@ Set-AzVM ` $VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName $PrototypeOSDiskName = $VM.StorageProfile.OsDisk.Name -$ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.ID +$ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.ID -HyperVGeneration 'V2' $Image = New-AzImage -Image $ImageConfig -ImageName $ProtoVMName -ResourceGroupName $ResourceGroupName #################################################################################################### diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8e8832a925..9c0fa71b56 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,7 +8,7 @@ variables: buildOutputLocation: 'D:\build' vcpkgLocation: '$(Build.SourcesDirectory)/vcpkg' -pool: 'StlBuild-2021-03-02' +pool: 'StlBuild-2021-03-09-win10' stages: - stage: Code_Format diff --git a/stl/inc/chrono b/stl/inc/chrono index de7457d263..d62210e67a 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2215,6 +2215,34 @@ namespace chrono { return hours{_Ret}; } + // [time.zone.timezone] + + // CLASS time_zone + class time_zone { + public: + explicit time_zone(string_view _Name_) : _Name(_Name_) {} + + time_zone(time_zone&&) = default; + time_zone& operator=(time_zone&&) = default; + + _NODISCARD string_view name() const noexcept { + return _Name; + } + + private: + string _Name; + }; + + _NODISCARD inline bool operator==(const time_zone& _Left, const time_zone& _Right) noexcept { + return _Left.name() == _Right.name(); + } + +#ifdef __cpp_lib_concepts + _NODISCARD inline strong_ordering operator<=>(const time_zone& _Left, const time_zone& _Right) noexcept { + return _Left.name() <=> _Right.name(); + } +#endif // __cpp_lib_concepts + // [time.zone.leap] // CLASS leap_second @@ -2309,15 +2337,116 @@ namespace chrono { } #endif // __cpp_lib_concepts + // [time.zone.link] + + // CLASS time_zone_link + class time_zone_link { + public: + explicit time_zone_link(string_view _Name_, string_view _Target_) : _Name(_Name_), _Target(_Target_) {} + + time_zone_link(time_zone_link&&) = default; + time_zone_link& operator=(time_zone_link&&) = default; + + _NODISCARD string_view name() const noexcept { + return _Name; + } + + _NODISCARD string_view target() const noexcept { + return _Target; + } + + private: + string _Name; + string _Target; + }; + + _NODISCARD inline bool operator==(const time_zone_link& _Left, const time_zone_link& _Right) noexcept { + return _Left.name() == _Right.name(); + } + +#ifdef __cpp_lib_concepts + _NODISCARD inline strong_ordering operator<=>(const time_zone_link& _Left, const time_zone_link& _Right) noexcept { + return _Left.name() <=> _Right.name(); + } +#endif // __cpp_lib_concepts + // [time.zone.db] - // TRANSITION: work in progress - // STRUCT tzdb + _NODISCARD inline string _Xtzdb_generate_current_zone() { + unique_ptr<__std_tzdb_current_zone_info, decltype(&__std_tzdb_delete_current_zone)> _Info{ + __std_tzdb_get_current_zone(), &__std_tzdb_delete_current_zone}; + if (_Info == nullptr) { + _Xbad_alloc(); + } else if (_Info->_Err == __std_tzdb_error::_Win_error) { + _XGetLastError(); + } else if (_Info->_Err == __std_tzdb_error::_Icu_error) { + _Xruntime_error("Internal error loading IANA database information"); + } + + return {_Info->_Tz_name}; + } + + template + _NODISCARD const _Ty* _Locate_zone_impl(const vector<_Ty>& _Vec, string_view _Name) { + const auto _Result = _STD find_if(_Vec.begin(), _Vec.end(), [&](auto& _Tz) { return _Tz.name() == _Name; }); + return _Result == _Vec.end() ? nullptr : &*_Result; + } + + // STRUCT tzdb struct tzdb { - vector> leap_seconds; + string version; + vector zones; + vector links; + vector leap_seconds; bool _All_ls_positive; + + _NODISCARD const time_zone* locate_zone(string_view _Tz_name) const { + auto _Tz = _Locate_zone_impl(zones, _Tz_name); + if (_Tz == nullptr) { + const auto _Link = _Locate_zone_impl(links, _Tz_name); + if (_Link != nullptr) { + _Tz = _Locate_zone_impl(zones, _Link->target()); + } + } + + if (_Tz == nullptr) { + _Xruntime_error("unable to locate time_zone with given name"); + } + + return _Tz; + } + + _NODISCARD const time_zone* current_zone() const { + return locate_zone(_Xtzdb_generate_current_zone()); + } }; + _NODISCARD inline tuple _Xtzdb_generate_time_zones() { + unique_ptr<__std_tzdb_time_zones_info, decltype(&__std_tzdb_delete_time_zones)> _Info{ + __std_tzdb_get_time_zones(), &__std_tzdb_delete_time_zones}; + if (_Info == nullptr) { + _Xbad_alloc(); + } else if (_Info->_Err == __std_tzdb_error::_Win_error) { + _XGetLastError(); + } else if (_Info->_Err == __std_tzdb_error::_Icu_error) { + _Xruntime_error("Internal error loading IANA database information"); + } + + decltype(tzdb::zones) _Time_zones; + decltype(tzdb::links) _Time_zone_links; + for (size_t _Idx = 0; _Idx < _Info->_Num_time_zones; ++_Idx) { + const string_view _Name{_Info->_Names[_Idx]}; + if (_Info->_Links[_Idx] == nullptr) { + _Time_zones.emplace_back(_Name); + } else { + const string_view _Target{_Info->_Links[_Idx]}; + _Time_zone_links.emplace_back(_Name, _Target); + } + } + + return {_Info->_Version, _STD move(_Time_zones), _STD move(_Time_zone_links)}; + } + _NODISCARD inline pair _Xtzdb_generate_leap_seconds( const size_t _Current_size) { // Returns empty vector if no new leap seconds are found. @@ -2391,7 +2520,7 @@ namespace chrono { } } - return {_Leap_sec_info, _All_ls_positive}; + return {_STD move(_Leap_sec_info), _All_ls_positive}; } // TRANSITION: work in progress @@ -2407,8 +2536,10 @@ namespace chrono { tzdb_list& operator=(const tzdb_list&) = delete; tzdb_list() { + auto [_Version, _Zones, _Links] = _Xtzdb_generate_time_zones(); auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(0); - _Tzdb_list.emplace_front(tzdb{_STD move(_Leap_sec), _All_ls_positive}); + _Tzdb_list.emplace_front(tzdb{ + _STD move(_Version), _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); } _NODISCARD const tzdb& front() const noexcept { @@ -2426,7 +2557,17 @@ namespace chrono { _Unique_lock _Lk(_Tzdb_mutex); auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(_Tzdb_list.front().leap_seconds.size()); if (!_Leap_sec.empty()) { - _Tzdb_list.emplace_front(tzdb{_STD move(_Leap_sec), _All_ls_positive}); + const auto& _Tzdb = _Tzdb_list.front(); + vector _Zones; + _STD transform(_Tzdb.zones.begin(), _Tzdb.zones.end(), _STD back_inserter(_Zones), + [](const auto& _Tz) { return time_zone{_Tz.name()}; }); + vector _Links; + _STD transform( + _Tzdb.links.begin(), _Tzdb.links.end(), _STD back_inserter(_Links), [](const auto& _Link) { + return time_zone_link{_Link.name(), _Link.target()}; + }); + _Tzdb_list.emplace_front( + tzdb{_Tzdb.version, _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); } return _Tzdb_list.front(); } diff --git a/stl/inc/xtzdb.h b/stl/inc/xtzdb.h index 734d050ecf..abd6aeaeb8 100644 --- a/stl/inc/xtzdb.h +++ b/stl/inc/xtzdb.h @@ -29,8 +29,38 @@ struct __std_tzdb_registry_leap_info { uint16_t _Reserved; }; +enum class __std_tzdb_error { + _Success = 0, + _Win_error = 1, + _Icu_error = 2, +}; + +struct __std_tzdb_time_zones_info { + __std_tzdb_error _Err; + // timezone data version currently being used + const char* _Version; + size_t _Num_time_zones; + // ordered list of null-terminated time_zone/time_zone_link names + const char** _Names; + // contains corresponding entry for every name, if: + // (_Links[i] == nullptr) then _Names[i] is a time_zone + // (_Links[i] != nullptr) then _Names[i] is a time_zone_link to time_zone with name _Links[i] + const char** _Links; +}; + +struct __std_tzdb_current_zone_info { + __std_tzdb_error _Err; + const char* _Tz_name; +}; + _EXTERN_C +_NODISCARD __std_tzdb_time_zones_info* __stdcall __std_tzdb_get_time_zones() noexcept; +void __stdcall __std_tzdb_delete_time_zones(__std_tzdb_time_zones_info* _Info) noexcept; + +_NODISCARD __std_tzdb_current_zone_info* __stdcall __std_tzdb_get_current_zone() noexcept; +void __stdcall __std_tzdb_delete_current_zone(__std_tzdb_current_zone_info* _Info) noexcept; + __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( size_t _Prev_reg_ls_size, size_t* _Current_reg_ls_size) noexcept; diff --git a/stl/src/msvcp_atomic_wait.src b/stl/src/msvcp_atomic_wait.src index 2c7b1b76b3..cd8f13dcaf 100644 --- a/stl/src/msvcp_atomic_wait.src +++ b/stl/src/msvcp_atomic_wait.src @@ -30,5 +30,9 @@ EXPORTS __std_parallel_algorithms_hw_threads __std_release_shared_mutex_for_instance __std_submit_threadpool_work + __std_tzdb_delete_current_zone + __std_tzdb_delete_time_zones + __std_tzdb_get_current_zone __std_tzdb_get_reg_leap_seconds + __std_tzdb_get_time_zones __std_wait_for_threadpool_work_callbacks diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp index f49ff2ff79..ab065abdee 100644 --- a/stl/src/tzdb.cpp +++ b/stl/src/tzdb.cpp @@ -1,15 +1,372 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include +#include #include +#include +#include +#include #include #include #pragma comment(lib, "Advapi32") +namespace { + enum class _Icu_api_level : unsigned long { + _Not_set, + _Detecting, + _Has_failed, + _Has_icu_addresses, + }; + + struct _Icu_functions_table { + _STD atomic _Pfn_ucal_getCanonicalTimeZoneID{nullptr}; + _STD atomic _Pfn_ucal_getDefaultTimeZone{nullptr}; + _STD atomic _Pfn_ucal_getTZDataVersion{nullptr}; + _STD atomic _Pfn_ucal_openTimeZoneIDEnumeration{nullptr}; + _STD atomic _Pfn_uenum_close{nullptr}; + _STD atomic _Pfn_uenum_count{nullptr}; + _STD atomic _Pfn_uenum_unext{nullptr}; + _STD atomic<_Icu_api_level> _Api_level{_Icu_api_level::_Not_set}; + }; + + _Icu_functions_table _Icu_functions; + + template + void _Load_address( + const HMODULE _Module, _STD atomic<_Ty>& _Stored_Pfn, LPCSTR _Fn_name, DWORD& _Last_error) noexcept { + const auto _Pfn = reinterpret_cast<_Ty>(GetProcAddress(_Module, _Fn_name)); + if (_Pfn != nullptr) { + _Stored_Pfn.store(_Pfn, _STD memory_order_relaxed); + } else { + _Last_error = GetLastError(); + } + } + + _NODISCARD _Icu_api_level _Init_icu_functions(_Icu_api_level _Level) noexcept { + while (!_Icu_functions._Api_level.compare_exchange_weak( + _Level, _Icu_api_level::_Detecting, _STD memory_order_acq_rel)) { + if (_Level > _Icu_api_level::_Detecting) { + return _Level; + } + } + + _Level = _Icu_api_level::_Has_failed; + + const HMODULE _Icu_module = LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (_Icu_module != nullptr) { + // collect at least one error if any GetProcAddress call fails + DWORD _Last_error{ERROR_SUCCESS}; + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getCanonicalTimeZoneID, "ucal_getCanonicalTimeZoneID", + _Last_error); + _Load_address( + _Icu_module, _Icu_functions._Pfn_ucal_getDefaultTimeZone, "ucal_getDefaultTimeZone", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_openTimeZoneIDEnumeration, + "ucal_openTimeZoneIDEnumeration", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getTZDataVersion, "ucal_getTZDataVersion", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_close, "uenum_close", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_count, "uenum_count", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_unext, "uenum_unext", _Last_error); + if (_Last_error == ERROR_SUCCESS) { + _Level = _Icu_api_level::_Has_icu_addresses; + } else { + // reset last error code for thread in case a later GetProcAddress resets it + SetLastError(_Last_error); + } + } + + _Icu_functions._Api_level.store(_Level, _STD memory_order_release); + return _Level; + } + + _NODISCARD _Icu_api_level _Acquire_icu_functions() noexcept { + auto _Level = _Icu_functions._Api_level.load(_STD memory_order_acquire); + if (_Level <= _Icu_api_level::_Detecting) { + _Level = _Init_icu_functions(_Level); + } + + return _Level; + } + + _NODISCARD int32_t __icu_ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, + int32_t resultCapacity, UBool* isSystemID, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getCanonicalTimeZoneID.load(_STD memory_order_relaxed); + return _Fun(id, len, result, resultCapacity, isSystemID, status); + } + + _NODISCARD int32_t __icu_ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* ec) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getDefaultTimeZone.load(_STD memory_order_relaxed); + return _Fun(result, resultCapacity, ec); + } + + _NODISCARD UEnumeration* __icu_ucal_openTimeZoneIDEnumeration( + USystemTimeZoneType zoneType, const char* region, const int32_t* rawOffset, UErrorCode* ec) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_openTimeZoneIDEnumeration.load(_STD memory_order_relaxed); + return _Fun(zoneType, region, rawOffset, ec); + } + + _NODISCARD const char* __icu_ucal_getTZDataVersion(UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getTZDataVersion.load(_STD memory_order_relaxed); + return _Fun(status); + } + + _NODISCARD void __icu_uenum_close(UEnumeration* en) noexcept { + const auto _Fun = _Icu_functions._Pfn_uenum_close.load(_STD memory_order_relaxed); + return _Fun(en); + } + + _NODISCARD int32_t __icu_uenum_count(UEnumeration* en, UErrorCode* ec) noexcept { + const auto _Fun = _Icu_functions._Pfn_uenum_count.load(_STD memory_order_relaxed); + return _Fun(en, ec); + } + + _NODISCARD const UChar* __icu_uenum_unext(UEnumeration* en, int32_t* resultLength, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_uenum_unext.load(_STD memory_order_relaxed); + return _Fun(en, resultLength, status); + } + + _NODISCARD const char* _Allocate_wide_to_narrow( + const char16_t* const _Input, const int _Input_len, __std_tzdb_error& _Err) noexcept { + const auto _Code_page = __std_fs_code_page(); + const auto _Input_as_wchar = reinterpret_cast(_Input); + const auto _Count_result = __std_fs_convert_wide_to_narrow(_Code_page, _Input_as_wchar, _Input_len, nullptr, 0); + if (_Count_result._Err != __std_win_error::_Success) { + _Err = __std_tzdb_error::_Win_error; + return nullptr; + } + + _STD unique_ptr _Data{new (_STD nothrow) char[_Count_result._Len + 1]}; + if (_Data == nullptr) { + return nullptr; + } + + _Data[_Count_result._Len] = '\0'; + + const auto _Result = + __std_fs_convert_wide_to_narrow(_Code_page, _Input_as_wchar, _Input_len, _Data.get(), _Count_result._Len); + if (_Result._Err != __std_win_error::_Success) { + _Err = __std_tzdb_error::_Win_error; + return nullptr; + } + + return _Data.release(); + } + + _NODISCARD _STD unique_ptr _Get_canonical_id( + const char16_t* _Id, const int32_t _Len, int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + constexpr int32_t _Link_buf_len = 32; + _STD unique_ptr _Link_buf{new (_STD nothrow) char16_t[_Link_buf_len]}; + if (_Link_buf == nullptr) { + return nullptr; + } + + UErrorCode _UErr{U_ZERO_ERROR}; + UBool _Is_system{}; + _Result_len = __icu_ucal_getCanonicalTimeZoneID(_Id, _Len, _Link_buf.get(), _Link_buf_len, &_Is_system, &_UErr); + if (_UErr == U_BUFFER_OVERFLOW_ERROR && _Result_len > 0) { + _Link_buf.reset(new (_STD nothrow) char16_t[_Result_len + 1]); + if (_Link_buf == nullptr) { + return nullptr; + } + + _UErr = U_ZERO_ERROR; // reset error. + _Result_len = + __icu_ucal_getCanonicalTimeZoneID(_Id, _Len, _Link_buf.get(), _Result_len, &_Is_system, &_UErr); + if (U_FAILURE(_UErr)) { + _Err = __std_tzdb_error::_Icu_error; + return nullptr; + } + } else if (U_FAILURE(_UErr) || _Result_len <= 0) { + _Err = __std_tzdb_error::_Icu_error; + return nullptr; + } + + return _Link_buf; + } + + _NODISCARD _STD unique_ptr _Get_default_timezone( + int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + constexpr int32_t _Name_buf_len = 32; + _STD unique_ptr _Name_buf{new (_STD nothrow) char16_t[_Name_buf_len]}; + if (_Name_buf == nullptr) { + return nullptr; + } + + UErrorCode _UErr{U_ZERO_ERROR}; + _Result_len = __icu_ucal_getDefaultTimeZone(_Name_buf.get(), _Name_buf_len, &_UErr); + if (_UErr == U_BUFFER_OVERFLOW_ERROR && _Result_len > 0) { + _Name_buf.reset(new (_STD nothrow) char16_t[_Result_len + 1]); + if (_Name_buf == nullptr) { + return nullptr; + } + + _UErr = U_ZERO_ERROR; // reset error. + _Result_len = __icu_ucal_getDefaultTimeZone(_Name_buf.get(), _Name_buf_len, &_UErr); + if (U_FAILURE(_UErr)) { + _Err = __std_tzdb_error::_Icu_error; + return nullptr; + } + } else if (U_FAILURE(_UErr) || _Result_len <= 0) { + _Err = __std_tzdb_error::_Icu_error; + return nullptr; + } + + return _Name_buf; + } + + template + _NODISCARD _Ty* _Report_error(_STD unique_ptr<_Ty, _Dx>& _Info, __std_tzdb_error _Err) { + _Info->_Err = _Err; + return _Info.release(); + } + + template + _NODISCARD _Ty* _Propagate_error(_STD unique_ptr<_Ty, _Dx>& _Info) { + // a bad_alloc returns nullptr and does not set __std_tzdb_error + return _Info->_Err == __std_tzdb_error::_Success ? nullptr : _Info.release(); + } + +} // unnamed namespace + _EXTERN_C +_NODISCARD __std_tzdb_time_zones_info* __stdcall __std_tzdb_get_time_zones() noexcept { + // On exit--- + // _Info == nullptr --> bad_alloc + // _Info->_Err == _Win_error --> failed, call GetLastError() + // _Info->_Err == _Icu_error --> runtime_error interacting with ICU + _STD unique_ptr<__std_tzdb_time_zones_info, decltype(&__std_tzdb_delete_time_zones)> _Info{ + new (_STD nothrow) __std_tzdb_time_zones_info{}, &__std_tzdb_delete_time_zones}; + if (_Info == nullptr) { + return nullptr; + } + + if (_Acquire_icu_functions() < _Icu_api_level::_Has_icu_addresses) { + return _Report_error(_Info, __std_tzdb_error::_Win_error); + } + + UErrorCode _UErr{U_ZERO_ERROR}; + _Info->_Version = __icu_ucal_getTZDataVersion(&_UErr); + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _STD unique_ptr _Enum{ + __icu_ucal_openTimeZoneIDEnumeration(USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, nullptr, nullptr, &_UErr), + &__icu_uenum_close}; + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + // uenum_count may be expensive but is required to pre-allocate arrays. + const int32_t _Num_time_zones = __icu_uenum_count(_Enum.get(), &_UErr); + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _Info->_Num_time_zones = static_cast(_Num_time_zones); + // value-init to ensure __std_tzdb_delete_time_zones() cleanup is valid + _Info->_Names = new (_STD nothrow) const char* [_Info->_Num_time_zones] {}; + if (_Info->_Names == nullptr) { + return nullptr; + } + + // value-init to ensure __std_tzdb_delete_time_zones() cleanup is valid + _Info->_Links = new (_STD nothrow) const char* [_Info->_Num_time_zones] {}; + if (_Info->_Links == nullptr) { + return nullptr; + } + + for (size_t _Name_idx = 0; _Name_idx < _Info->_Num_time_zones; ++_Name_idx) { + int32_t _Elem_len{}; + const auto* const _Elem = __icu_uenum_unext(_Enum.get(), &_Elem_len, &_UErr); + if (U_FAILURE(_UErr) || _Elem == nullptr) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _Info->_Names[_Name_idx] = _Allocate_wide_to_narrow(_Elem, _Elem_len, _Info->_Err); + if (_Info->_Names[_Name_idx] == nullptr) { + return _Propagate_error(_Info); + } + + int32_t _Link_len{}; + const auto _Link = _Get_canonical_id(_Elem, _Elem_len, _Link_len, _Info->_Err); + if (_Link == nullptr) { + return _Propagate_error(_Info); + } + + if (_STD u16string_view{_Elem, static_cast(_Elem_len)} + != _STD u16string_view{_Link.get(), static_cast(_Link_len)}) { + _Info->_Links[_Name_idx] = _Allocate_wide_to_narrow(_Link.get(), _Link_len, _Info->_Err); + if (_Info->_Links[_Name_idx] == nullptr) { + return _Propagate_error(_Info); + } + } + } + + return _Info.release(); +} + +void __stdcall __std_tzdb_delete_time_zones(__std_tzdb_time_zones_info* const _Info) noexcept { + if (_Info != nullptr) { + if (_Info->_Names != nullptr) { + for (size_t _Idx = 0; _Idx < _Info->_Num_time_zones; ++_Idx) { + delete[] _Info->_Names[_Idx]; + } + + delete[] _Info->_Names; + _Info->_Names = nullptr; + } + + if (_Info->_Links != nullptr) { + for (size_t _Idx = 0; _Idx < _Info->_Num_time_zones; ++_Idx) { + delete[] _Info->_Links[_Idx]; + } + + delete[] _Info->_Links; + _Info->_Links = nullptr; + } + } +} + +_NODISCARD __std_tzdb_current_zone_info* __stdcall __std_tzdb_get_current_zone() noexcept { + // On exit--- + // _Info == nullptr --> bad_alloc + // _Info->_Err == _Win_error --> failed, call GetLastError() + // _Info->_Err == _Icu_error --> runtime_error interacting with ICU + _STD unique_ptr<__std_tzdb_current_zone_info, decltype(&__std_tzdb_delete_current_zone)> _Info{ + new (_STD nothrow) __std_tzdb_current_zone_info{}, &__std_tzdb_delete_current_zone}; + if (_Info == nullptr) { + return nullptr; + } + + if (_Acquire_icu_functions() < _Icu_api_level::_Has_icu_addresses) { + return _Report_error(_Info, __std_tzdb_error::_Win_error); + } + + int32_t _Id_len{}; + const auto _Id_name = _Get_default_timezone(_Id_len, _Info->_Err); + if (_Id_name == nullptr) { + return _Propagate_error(_Info); + } + + _Info->_Tz_name = _Allocate_wide_to_narrow(_Id_name.get(), _Id_len, _Info->_Err); + if (_Info->_Tz_name == nullptr) { + return _Propagate_error(_Info); + } + + return _Info.release(); +} + +void __stdcall __std_tzdb_delete_current_zone(__std_tzdb_current_zone_info* const _Info) noexcept { + if (_Info) { + delete[] _Info->_Tz_name; + _Info->_Tz_name = nullptr; + } +} + __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( const size_t prev_reg_ls_size, size_t* const current_reg_ls_size) noexcept { // On exit--- diff --git a/tests/std/test.lst b/tests/std/test.lst index e5866c93af..3b7270fd45 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -235,6 +235,7 @@ tests\P0355R7_calendars_and_time_zones_dates_literals tests\P0355R7_calendars_and_time_zones_hms tests\P0355R7_calendars_and_time_zones_io tests\P0355R7_calendars_and_time_zones_time_point_and_durations +tests\P0355R7_calendars_and_time_zones_time_zones tests\P0356R5_bind_front tests\P0357R3_supporting_incomplete_types_in_reference_wrapper tests\P0408R7_efficient_access_to_stringbuf_buffer diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp index cc8b5a8096..e4f17ac061 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp @@ -378,6 +378,19 @@ static_assert(is_clock_v); static_assert(is_clock_v); static_assert(is_clock_v); +tzdb copy_tzdb() { + const auto& my_tzdb = get_tzdb_list().front(); + vector zones; + vector links; + transform(my_tzdb.zones.begin(), my_tzdb.zones.end(), back_inserter(zones), + [](const auto& _Tz) { return time_zone{_Tz.name()}; }); + transform(my_tzdb.links.begin(), my_tzdb.links.end(), back_inserter(links), [](const auto& link) { + return time_zone_link{link.name(), link.target()}; + }); + + return {my_tzdb.version, move(zones), move(links), my_tzdb.leap_seconds, my_tzdb._All_ls_positive}; +} + int main() { assert(test_leap_second()); static_assert(test_leap_second()); @@ -408,7 +421,7 @@ int main() { // a negative leap second when the accumulated offset is positive { - auto my_tzdb = get_tzdb_list().front(); + auto my_tzdb = copy_tzdb(); auto& leap_vec = my_tzdb.leap_seconds; leap_vec.erase(leap_vec.begin() + 27, leap_vec.end()); leap_vec.emplace_back(sys_days{1d / January / 2020y}, false, leap_vec.back()._Elapsed()); @@ -434,7 +447,7 @@ int main() { // positive and negative leap seconds when the accumulated offset is negative { - auto my_tzdb = get_tzdb_list().front(); + auto my_tzdb = copy_tzdb(); auto& leap_vec = my_tzdb.leap_seconds; leap_vec.erase(leap_vec.begin() + 27, leap_vec.end()); for (int i = 0; i < 30; ++i) { diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/env.lst b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/env.lst new file mode 100644 index 0000000000..642f530ffa --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp new file mode 100644 index 0000000000..04ced25c09 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +void test_time_zone_and_link(const tzdb& tzdb, string_view tz_name, string_view tz_link_name) { + const auto orginal_tz = tzdb.locate_zone(tz_name); + assert(orginal_tz != nullptr); + assert(orginal_tz->name() == tz_name); + + const auto linked_tz = tzdb.locate_zone(tz_link_name); + assert(linked_tz != nullptr); + assert(linked_tz->name() == tz_name); + assert(orginal_tz == linked_tz); + + const auto tz_link = _Locate_zone_impl(tzdb.links, tz_link_name); + assert(tz_link != nullptr); + assert(tz_link->name() == tz_link_name); + assert(tz_link->target() == tz_name); + assert(tzdb.locate_zone(tz_link->target()) == orginal_tz); + + assert(_Locate_zone_impl(tzdb.zones, tz_name) != nullptr); + assert(_Locate_zone_impl(tzdb.zones, tz_link_name) == nullptr); + assert(_Locate_zone_impl(tzdb.links, tz_name) == nullptr); +} + +void try_locate_invalid_zone(const tzdb& tzdb, string_view name) { + try { + (void) tzdb.locate_zone(name); + assert(false); + } catch (runtime_error) { + } +} + +void timezone_names_test() { + const auto& tzdb = get_tzdb(); + + assert(tzdb.version.empty() == false); + + test_time_zone_and_link(tzdb, "Asia/Thimphu", "Asia/Thimbu"); + test_time_zone_and_link(tzdb, "America/Tijuana", "America/Ensenada"); + + const auto current_zone = tzdb.current_zone(); + assert(current_zone != nullptr); + assert(current_zone->name().empty() == false); + + try_locate_invalid_zone(tzdb, "Non/Existent"); + + // Abbreviations should not be time_zones or time_zone_links + try_locate_invalid_zone(tzdb, "PDT"); + try_locate_invalid_zone(tzdb, "AEST"); + + // Comparison operators + const time_zone tz1{"Earlier"}; + const time_zone tz2{"Earlier"}; + const time_zone tz3{"Later"}; + assert(tz1 == tz2); + assert(tz1 != tz3); +#ifdef __cpp_lib_concepts + assert(tz1 <=> tz2 == strong_ordering::equal); + assert(tz1 <=> tz3 == strong_ordering::less); + assert(tz3 <=> tz1 == strong_ordering::greater); +#endif // __cpp_lib_concepts + + const time_zone_link link1{"Earlier", "Target"}; + const time_zone_link link2{"Earlier", "Is"}; + const time_zone_link link3{"Later", "Ignored"}; + assert(link1 == link2); + assert(link1 != link3); +#ifdef __cpp_lib_concepts + assert(link1 <=> link2 == strong_ordering::equal); + assert(link1 <=> link3 == strong_ordering::less); + assert(link3 <=> link1 == strong_ordering::greater); +#endif // __cpp_lib_concepts + + // FIXME: add a link to an issue. These may change overtime and might have to be removed from tests. + + // these are some example in which the ICU.dll and IANA database diverge in what they consider a zone or a link + assert(_Locate_zone_impl(tzdb.links, "Atlantic/Faroe") != nullptr); // is a time_zone in IANA + assert(_Locate_zone_impl(tzdb.zones, "Africa/Addis_Ababa") != nullptr); // is a time_zone_link in IANA + assert(_Locate_zone_impl(tzdb.links, "PST") != nullptr); // time_zone_link does not exist in IANA + assert(_Locate_zone_impl(tzdb.links, "Africa/Asmara") != nullptr); // matches IANA but target is wrong + assert(_Locate_zone_impl(tzdb.links, "Africa/Asmara")->target() == "Africa/Asmera"); // target == Africa/Nairobi + assert(_Locate_zone_impl(tzdb.zones, "America/Nuuk") == nullptr); // does not exist in ICU (very rare) +} + +bool test() { + try { + timezone_names_test(); + } catch (exception& ex) { + cerr << "Test threw exception: " << ex.what() << "\n"; + assert(false); + } + + return true; +} + +int main() { + test(); +} From 2bc79d2ad25726d6e27f876b1d055895976338b1 Mon Sep 17 00:00:00 2001 From: d-winsor Date: Tue, 16 Mar 2021 14:04:28 -0700 Subject: [PATCH 03/22] Logic for time_zone sys to local conversion (#1732) * Initial draft * Addressed feedback * Final feedback --- stl/inc/chrono | 177 +++++++++ stl/inc/xtzdb.h | 15 + stl/src/msvcp_atomic_wait.src | 4 +- stl/src/tzdb.cpp | 257 ++++++++++--- .../test.cpp | 339 ++++++++++++++++-- .../timezone_data.h | 141 ++++++++ 6 files changed, 861 insertions(+), 72 deletions(-) create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h diff --git a/stl/inc/chrono b/stl/inc/chrono index d62210e67a..92d380c3d6 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2215,8 +2215,49 @@ namespace chrono { return hours{_Ret}; } + // [time.zone.info] + + // STRUCT sys_info + struct sys_info { + sys_seconds begin; + sys_seconds end; + seconds offset; + minutes save; + string abbrev; + }; + + // STRUCT local_info + struct local_info { + static constexpr int unique = 0; + static constexpr int nonexistent = 1; + static constexpr int ambiguous = 2; + + int result; + sys_info first; + sys_info second; + }; + + // CLASS nonexistent_local_time + class nonexistent_local_time : public runtime_error { + public: + template + nonexistent_local_time(const local_time<_Duration>&, const local_info&) + : runtime_error("TRANSITION: work in progress") {} + }; + + // CLASS ambiguous_local_time + class ambiguous_local_time : public runtime_error { + public: + template + ambiguous_local_time(const local_time<_Duration>&, const local_info&) + : runtime_error("TRANSITION: work in progress") {} + }; + // [time.zone.timezone] + // ENUM CLASS choose + enum class choose { earliest, latest }; + // CLASS time_zone class time_zone { public: @@ -2229,7 +2270,143 @@ namespace chrono { return _Name; } + template + _NODISCARD sys_info get_info(const sys_time<_Duration>& _Sys) const { + return _Get_info(_Sys.time_since_epoch()); + } + + template + _NODISCARD local_info get_info(const local_time<_Duration>& _Local) const { + local_info _Info{}; + _Info.first = _Get_info(_Local.time_since_epoch()); + + const sys_seconds _Local_sys{_CHRONO duration_cast(_Local.time_since_epoch())}; + const auto _Curr_sys = _Local_sys - _Info.first.offset; + if (_Info.first.begin != _Min_seconds && _Curr_sys < _Info.first.begin + days{1}) { + // get previous transition information + _Info.second = get_info(_Info.first.begin - seconds{1}); + + const auto _Transition = _Info.first.begin; + const auto _Prev_sys = _Local_sys - _Info.second.offset; + if (_Curr_sys >= _Transition) { + if (_Prev_sys < _Transition) { + _Info.result = local_info::ambiguous; + _STD swap(_Info.first, _Info.second); + } else { + _Info.result = local_info::unique; + _Info.second = {}; + } + } else { + if (_Prev_sys >= _Transition) { + _Info.result = local_info::nonexistent; + _STD swap(_Info.first, _Info.second); + } else { + _Info.result = local_info::unique; + _Info.first = _STD move(_Info.second); + _Info.second = {}; + } + } + } else if (_Info.first.end != _Max_seconds && _Curr_sys > _Info.first.end - days{1}) { + // get next transition information + _Info.second = get_info(_Info.first.end + seconds{1}); + + const auto _Transition = _Info.first.end; + const auto _Next_sys = _Local_sys - _Info.second.offset; + if (_Curr_sys < _Transition) { + if (_Next_sys >= _Transition) { + _Info.result = local_info::ambiguous; + } else { + _Info.result = local_info::unique; + _Info.second = {}; + } + } else { + if (_Next_sys < _Transition) { + _Info.result = local_info::nonexistent; + } else { + _Info.result = local_info::unique; + _Info.first = _STD move(_Info.second); + _Info.second = {}; + } + } + } else { + // local time is contained inside of first transition boundaries by at least 1 day + _Info.result = local_info::unique; + _Info.second = {}; + } + + return _Info; + } + + template + _NODISCARD sys_time> to_sys(const local_time<_Duration>& _Local) const { + const auto _Info = get_info(_Local); + if (_Info.result == local_info::nonexistent) { + _THROW(nonexistent_local_time(_Local, _Info)); + } else if (_Info.result == local_info::ambiguous) { + _THROW(ambiguous_local_time(_Local, _Info)); + } + + return sys_time>{_Local.time_since_epoch() - _Info.first.offset}; + } + + template + _NODISCARD sys_time> to_sys( + const local_time<_Duration>& _Local, const choose _Choose) const { + const auto _Info = get_info(_Local); + if (_Info.result == local_info::nonexistent) { + return _Info.first.end; + } + + const auto _Offset = (_Info.result == local_info::unique || _Choose == choose::earliest) + ? _Info.first.offset + : _Info.second.offset; + return sys_time>{_Local.time_since_epoch() - _Offset}; + } + + template + _NODISCARD local_time> to_local(const sys_time<_Duration>& _Sys) const { + const auto _Info = get_info(_Sys); + return local_time>{_Sys.time_since_epoch() + _Info.offset}; + } + + static constexpr sys_seconds _Min_seconds{sys_days{(year::min)() / January / 1}}; + static constexpr sys_seconds _Max_seconds{sys_seconds{sys_days{(year::max)() / December / 32}} - seconds{1}}; + private: + template + _NODISCARD sys_info _Get_info(const _Duration& _Dur) const { + using _Internal_duration = duration<__std_tzdb_epoch_milli, milli>; + const auto _Internal_dur = _CHRONO duration_cast<_Internal_duration>(_Dur); + const unique_ptr<__std_tzdb_sys_info, decltype(&__std_tzdb_delete_sys_info)> _Info{ + __std_tzdb_get_sys_info(_Name.c_str(), _Name.length(), _Internal_dur.count()), + &__std_tzdb_delete_sys_info}; + if (_Info == nullptr) { + _Xbad_alloc(); + } else if (_Info->_Err == __std_tzdb_error::_Win_error) { + _XGetLastError(); + } else if (_Info->_Err == __std_tzdb_error::_Icu_error) { + _Xruntime_error("Internal error loading IANA database information"); + } + + constexpr auto _Min_internal = + _CHRONO duration_cast<_Internal_duration>(_Min_seconds.time_since_epoch()).count(); + constexpr auto _Max_internal = + _CHRONO duration_cast<_Internal_duration>(_Max_seconds.time_since_epoch()).count(); + const auto _Begin = + _Info->_Begin <= _Min_internal + ? _Min_seconds + : sys_seconds{_CHRONO duration_cast(_Internal_duration{_Info->_Begin})}; + const auto _End = + _Info->_End >= _Max_internal + ? _Max_seconds + : sys_seconds{_CHRONO duration_cast(_Internal_duration{_Info->_End})}; + return {.begin = _Begin, + .end = _End, + .offset = _CHRONO duration_cast(_Internal_duration{_Info->_Offset}), + .save = _CHRONO duration_cast(_Internal_duration{_Info->_Save}), + .abbrev = _Info->_Abbrev}; + } + string _Name; }; diff --git a/stl/inc/xtzdb.h b/stl/inc/xtzdb.h index abd6aeaeb8..a0f1ffb4d8 100644 --- a/stl/inc/xtzdb.h +++ b/stl/inc/xtzdb.h @@ -20,6 +20,8 @@ _STL_DISABLE_CLANG_WARNINGS #pragma push_macro("new") #undef new +using __std_tzdb_epoch_milli = double; + struct __std_tzdb_registry_leap_info { uint16_t _Year; uint16_t _Month; @@ -53,6 +55,15 @@ struct __std_tzdb_current_zone_info { const char* _Tz_name; }; +struct __std_tzdb_sys_info { + __std_tzdb_error _Err; + __std_tzdb_epoch_milli _Begin; + __std_tzdb_epoch_milli _End; + int32_t _Offset; + int32_t _Save; + const char* _Abbrev; +}; + _EXTERN_C _NODISCARD __std_tzdb_time_zones_info* __stdcall __std_tzdb_get_time_zones() noexcept; @@ -61,6 +72,10 @@ void __stdcall __std_tzdb_delete_time_zones(__std_tzdb_time_zones_info* _Info) n _NODISCARD __std_tzdb_current_zone_info* __stdcall __std_tzdb_get_current_zone() noexcept; void __stdcall __std_tzdb_delete_current_zone(__std_tzdb_current_zone_info* _Info) noexcept; +_NODISCARD __std_tzdb_sys_info* __stdcall __std_tzdb_get_sys_info( + const char* _Tz, size_t _Tz_len, __std_tzdb_epoch_milli _Local) noexcept; +void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* _Info) noexcept; + __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( size_t _Prev_reg_ls_size, size_t* _Current_reg_ls_size) noexcept; diff --git a/stl/src/msvcp_atomic_wait.src b/stl/src/msvcp_atomic_wait.src index cd8f13dcaf..cbcd3153c4 100644 --- a/stl/src/msvcp_atomic_wait.src +++ b/stl/src/msvcp_atomic_wait.src @@ -23,7 +23,6 @@ EXPORTS __std_calloc_crt __std_close_threadpool_work __std_create_threadpool_work - __std_tzdb_delete_reg_leap_seconds __std_execution_wait_on_uchar __std_execution_wake_by_address_all __std_free_crt @@ -31,8 +30,11 @@ EXPORTS __std_release_shared_mutex_for_instance __std_submit_threadpool_work __std_tzdb_delete_current_zone + __std_tzdb_delete_reg_leap_seconds + __std_tzdb_delete_sys_info __std_tzdb_delete_time_zones __std_tzdb_get_current_zone __std_tzdb_get_reg_leap_seconds + __std_tzdb_get_sys_info __std_tzdb_get_time_zones __std_wait_for_threadpool_work_callbacks diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp index ab065abdee..0e13a44636 100644 --- a/stl/src/tzdb.cpp +++ b/stl/src/tzdb.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include #include #include #include @@ -22,10 +23,17 @@ namespace { }; struct _Icu_functions_table { + _STD atomic _Pfn_ucal_close{nullptr}; + _STD atomic _Pfn_ucal_get{nullptr}; _STD atomic _Pfn_ucal_getCanonicalTimeZoneID{nullptr}; _STD atomic _Pfn_ucal_getDefaultTimeZone{nullptr}; + _STD atomic _Pfn_ucal_getTimeZoneDisplayName{nullptr}; + _STD atomic _Pfn_ucal_getTimeZoneTransitionDate{nullptr}; _STD atomic _Pfn_ucal_getTZDataVersion{nullptr}; + _STD atomic _Pfn_ucal_inDaylightTime{nullptr}; + _STD atomic _Pfn_ucal_open{nullptr}; _STD atomic _Pfn_ucal_openTimeZoneIDEnumeration{nullptr}; + _STD atomic _Pfn_ucal_setMillis{nullptr}; _STD atomic _Pfn_uenum_close{nullptr}; _STD atomic _Pfn_uenum_count{nullptr}; _STD atomic _Pfn_uenum_unext{nullptr}; @@ -59,13 +67,22 @@ namespace { if (_Icu_module != nullptr) { // collect at least one error if any GetProcAddress call fails DWORD _Last_error{ERROR_SUCCESS}; + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_close, "ucal_close", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_get, "ucal_get", _Last_error); _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getCanonicalTimeZoneID, "ucal_getCanonicalTimeZoneID", _Last_error); _Load_address( _Icu_module, _Icu_functions._Pfn_ucal_getDefaultTimeZone, "ucal_getDefaultTimeZone", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getTimeZoneDisplayName, "ucal_getTimeZoneDisplayName", + _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getTimeZoneTransitionDate, + "ucal_getTimeZoneTransitionDate", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getTZDataVersion, "ucal_getTZDataVersion", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_inDaylightTime, "ucal_inDaylightTime", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_open, "ucal_open", _Last_error); _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_openTimeZoneIDEnumeration, "ucal_openTimeZoneIDEnumeration", _Last_error); - _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_getTZDataVersion, "ucal_getTZDataVersion", _Last_error); + _Load_address(_Icu_module, _Icu_functions._Pfn_ucal_setMillis, "ucal_setMillis", _Last_error); _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_close, "uenum_close", _Last_error); _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_count, "uenum_count", _Last_error); _Load_address(_Icu_module, _Icu_functions._Pfn_uenum_unext, "uenum_unext", _Last_error); @@ -90,6 +107,16 @@ namespace { return _Level; } + void __icu_ucal_close(UCalendar* cal) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_close.load(_STD memory_order_relaxed); + _Fun(cal); + } + + _NODISCARD int32_t __icu_ucal_get(const UCalendar* cal, UCalendarDateFields field, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_get.load(_STD memory_order_relaxed); + return _Fun(cal, field, status); + } + _NODISCARD int32_t __icu_ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, int32_t resultCapacity, UBool* isSystemID, UErrorCode* status) noexcept { const auto _Fun = _Icu_functions._Pfn_ucal_getCanonicalTimeZoneID.load(_STD memory_order_relaxed); @@ -101,20 +128,48 @@ namespace { return _Fun(result, resultCapacity, ec); } + _NODISCARD int32_t __icu_ucal_getTimeZoneDisplayName(const UCalendar* cal, UCalendarDisplayNameType type, + const char* locale, UChar* result, int32_t resultLength, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getTimeZoneDisplayName.load(_STD memory_order_relaxed); + return _Fun(cal, type, locale, result, resultLength, status); + } + + _NODISCARD UBool __icu_ucal_getTimeZoneTransitionDate( + const UCalendar* cal, UTimeZoneTransitionType type, UDate* transition, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getTimeZoneTransitionDate.load(_STD memory_order_relaxed); + return _Fun(cal, type, transition, status); + } + + _NODISCARD const char* __icu_ucal_getTZDataVersion(UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_getTZDataVersion.load(_STD memory_order_relaxed); + return _Fun(status); + } + + _NODISCARD UBool __icu_ucal_inDaylightTime(const UCalendar* cal, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_inDaylightTime.load(_STD memory_order_relaxed); + return _Fun(cal, status); + } + + _NODISCARD UCalendar* __icu_ucal_open( + const UChar* zoneID, int32_t len, const char* locale, UCalendarType type, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_open.load(_STD memory_order_relaxed); + return _Fun(zoneID, len, locale, type, status); + } + _NODISCARD UEnumeration* __icu_ucal_openTimeZoneIDEnumeration( USystemTimeZoneType zoneType, const char* region, const int32_t* rawOffset, UErrorCode* ec) noexcept { const auto _Fun = _Icu_functions._Pfn_ucal_openTimeZoneIDEnumeration.load(_STD memory_order_relaxed); return _Fun(zoneType, region, rawOffset, ec); } - _NODISCARD const char* __icu_ucal_getTZDataVersion(UErrorCode* status) noexcept { - const auto _Fun = _Icu_functions._Pfn_ucal_getTZDataVersion.load(_STD memory_order_relaxed); - return _Fun(status); + void __icu_ucal_setMillis(UCalendar* cal, UDate dateTime, UErrorCode* status) noexcept { + const auto _Fun = _Icu_functions._Pfn_ucal_setMillis.load(_STD memory_order_relaxed); + _Fun(cal, dateTime, status); } - _NODISCARD void __icu_uenum_close(UEnumeration* en) noexcept { + void __icu_uenum_close(UEnumeration* en) noexcept { const auto _Fun = _Icu_functions._Pfn_uenum_close.load(_STD memory_order_relaxed); - return _Fun(en); + _Fun(en); } _NODISCARD int32_t __icu_uenum_count(UEnumeration* en, UErrorCode* ec) noexcept { @@ -154,56 +209,51 @@ namespace { return _Data.release(); } - _NODISCARD _STD unique_ptr _Get_canonical_id( - const char16_t* _Id, const int32_t _Len, int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { - constexpr int32_t _Link_buf_len = 32; - _STD unique_ptr _Link_buf{new (_STD nothrow) char16_t[_Link_buf_len]}; - if (_Link_buf == nullptr) { + _NODISCARD _STD unique_ptr _Allocate_narrow_to_wide( + const char* const _Input, const int _Input_len, __std_tzdb_error& _Err) noexcept { + const auto _Code_page = __std_fs_code_page(); + const auto _Count = __std_fs_convert_narrow_to_wide(_Code_page, _Input, _Input_len, nullptr, 0); + if (_Count._Err != __std_win_error::_Success) { + _Err = __std_tzdb_error::_Win_error; return nullptr; } - UErrorCode _UErr{U_ZERO_ERROR}; - UBool _Is_system{}; - _Result_len = __icu_ucal_getCanonicalTimeZoneID(_Id, _Len, _Link_buf.get(), _Link_buf_len, &_Is_system, &_UErr); - if (_UErr == U_BUFFER_OVERFLOW_ERROR && _Result_len > 0) { - _Link_buf.reset(new (_STD nothrow) char16_t[_Result_len + 1]); - if (_Link_buf == nullptr) { - return nullptr; - } + _STD unique_ptr _Data{new (_STD nothrow) char16_t[_Count._Len + 1]}; + if (_Data == nullptr) { + return nullptr; + } - _UErr = U_ZERO_ERROR; // reset error. - _Result_len = - __icu_ucal_getCanonicalTimeZoneID(_Id, _Len, _Link_buf.get(), _Result_len, &_Is_system, &_UErr); - if (U_FAILURE(_UErr)) { - _Err = __std_tzdb_error::_Icu_error; - return nullptr; - } - } else if (U_FAILURE(_UErr) || _Result_len <= 0) { - _Err = __std_tzdb_error::_Icu_error; + _Data[_Count._Len] = u'\0'; + const auto _Output_as_wchar = reinterpret_cast(_Data.get()); + + const auto _Result = + __std_fs_convert_narrow_to_wide(_Code_page, _Input, _Input_len, _Output_as_wchar, _Count._Len); + if (_Result._Err != __std_win_error::_Success) { + _Err = __std_tzdb_error::_Win_error; return nullptr; } - return _Link_buf; + return _Data; } - _NODISCARD _STD unique_ptr _Get_default_timezone( - int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { - constexpr int32_t _Name_buf_len = 32; - _STD unique_ptr _Name_buf{new (_STD nothrow) char16_t[_Name_buf_len]}; - if (_Name_buf == nullptr) { + template + _NODISCARD _STD unique_ptr _Get_icu_string_impl(const _Function _Icu_fn, + const int32_t _Initial_buf_len, int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + _STD unique_ptr _Str_buf{new (_STD nothrow) char16_t[_Initial_buf_len]}; + if (_Str_buf == nullptr) { return nullptr; } UErrorCode _UErr{U_ZERO_ERROR}; - _Result_len = __icu_ucal_getDefaultTimeZone(_Name_buf.get(), _Name_buf_len, &_UErr); + _Result_len = _Icu_fn(_Str_buf.get(), _Initial_buf_len, &_UErr); if (_UErr == U_BUFFER_OVERFLOW_ERROR && _Result_len > 0) { - _Name_buf.reset(new (_STD nothrow) char16_t[_Result_len + 1]); - if (_Name_buf == nullptr) { + _Str_buf.reset(new (_STD nothrow) char16_t[_Result_len + 1]); + if (_Str_buf == nullptr) { return nullptr; } _UErr = U_ZERO_ERROR; // reset error. - _Result_len = __icu_ucal_getDefaultTimeZone(_Name_buf.get(), _Name_buf_len, &_UErr); + _Result_len = _Icu_fn(_Str_buf.get(), _Result_len, &_UErr); if (U_FAILURE(_UErr)) { _Err = __std_tzdb_error::_Icu_error; return nullptr; @@ -213,21 +263,64 @@ namespace { return nullptr; } - return _Name_buf; + return _Str_buf; + } + + _NODISCARD _STD unique_ptr _Get_canonical_id( + const char16_t* _Id, const int32_t _Len, int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + const auto _Icu_fn = [_Id, _Len](UChar* _Result, int32_t _Result_capacity, UErrorCode* _UErr) { + UBool _Is_system{}; + return __icu_ucal_getCanonicalTimeZoneID(_Id, _Len, _Result, _Result_capacity, &_Is_system, _UErr); + }; + return _Get_icu_string_impl(_Icu_fn, 32, _Result_len, _Err); + } + + _NODISCARD _STD unique_ptr _Get_default_timezone( + int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + const auto _Icu_fn = [](UChar* _Result, int32_t _Result_capacity, UErrorCode* _UErr) { + return __icu_ucal_getDefaultTimeZone(_Result, _Result_capacity, _UErr); + }; + return _Get_icu_string_impl(_Icu_fn, 32, _Result_len, _Err); + } + + _NODISCARD _STD unique_ptr _Get_timezone_short_id( + UCalendar* const _Cal, const bool _Is_daylight, int32_t& _Result_len, __std_tzdb_error& _Err) noexcept { + const auto _Display_type = + _Is_daylight ? UCalendarDisplayNameType::UCAL_SHORT_DST : UCalendarDisplayNameType::UCAL_SHORT_STANDARD; + const auto _Icu_fn = [_Cal, _Display_type](UChar* _Result, int32_t _Result_capacity, UErrorCode* _UErr) { + return __icu_ucal_getTimeZoneDisplayName(_Cal, _Display_type, nullptr, _Result, _Result_capacity, _UErr); + }; + return _Get_icu_string_impl(_Icu_fn, 12, _Result_len, _Err); + } + + _NODISCARD _STD unique_ptr _Get_cal( + const char* _Tz, const size_t _Tz_len, __std_tzdb_error& _Err) noexcept { + const auto _Tz_name = _Allocate_narrow_to_wide(_Tz, static_cast(_Tz_len), _Err); + if (_Tz_name == nullptr) { + return {nullptr, &__icu_ucal_close}; + } + + UErrorCode _UErr{U_ZERO_ERROR}; + _STD unique_ptr _Cal{ + __icu_ucal_open(_Tz_name.get(), -1, nullptr, UCalendarType::UCAL_DEFAULT, &_UErr), &__icu_ucal_close}; + if (U_FAILURE(_UErr)) { + _Err = __std_tzdb_error::_Icu_error; + } + + return _Cal; } template - _NODISCARD _Ty* _Report_error(_STD unique_ptr<_Ty, _Dx>& _Info, __std_tzdb_error _Err) { + _NODISCARD _Ty* _Report_error(_STD unique_ptr<_Ty, _Dx>& _Info, const __std_tzdb_error _Err) noexcept { _Info->_Err = _Err; return _Info.release(); } template - _NODISCARD _Ty* _Propagate_error(_STD unique_ptr<_Ty, _Dx>& _Info) { + _NODISCARD _Ty* _Propagate_error(_STD unique_ptr<_Ty, _Dx>& _Info) noexcept { // a bad_alloc returns nullptr and does not set __std_tzdb_error return _Info->_Err == __std_tzdb_error::_Success ? nullptr : _Info.release(); } - } // unnamed namespace _EXTERN_C @@ -367,6 +460,86 @@ void __stdcall __std_tzdb_delete_current_zone(__std_tzdb_current_zone_info* cons } } +_NODISCARD __std_tzdb_sys_info* __stdcall __std_tzdb_get_sys_info( + const char* _Tz, const size_t _Tz_len, __std_tzdb_epoch_milli _Sys) noexcept { + // On exit--- + // _Info == nullptr --> bad_alloc + // _Info->_Err == _Win_error --> failed, call GetLastError() + // _Info->_Err == _Icu_error --> runtime_error interacting with ICU + _STD unique_ptr<__std_tzdb_sys_info, decltype(&__std_tzdb_delete_sys_info)> _Info{ + new (_STD nothrow) __std_tzdb_sys_info{}, &__std_tzdb_delete_sys_info}; + if (_Info == nullptr) { + return nullptr; + } + + if (_Acquire_icu_functions() < _Icu_api_level::_Has_icu_addresses) { + return _Report_error(_Info, __std_tzdb_error::_Win_error); + } + + const auto _Cal = _Get_cal(_Tz, _Tz_len, _Info->_Err); + if (_Cal == nullptr) { + return _Propagate_error(_Info); + } + + UErrorCode _UErr{}; + __icu_ucal_setMillis(_Cal.get(), _Sys, &_UErr); + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + const auto _Is_daylight = __icu_ucal_inDaylightTime(_Cal.get(), &_UErr); + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _Info->_Save = _Is_daylight ? __icu_ucal_get(_Cal.get(), UCalendarDateFields::UCAL_DST_OFFSET, &_UErr) : 0; + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _Info->_Offset = __icu_ucal_get(_Cal.get(), UCalendarDateFields::UCAL_ZONE_OFFSET, &_UErr) + _Info->_Save; + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + UDate _Transition{}; + _Info->_Begin = __icu_ucal_getTimeZoneTransitionDate(_Cal.get(), + UTimeZoneTransitionType::UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE, &_Transition, &_UErr) + ? _Transition + : U_DATE_MIN; + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + _Info->_End = __icu_ucal_getTimeZoneTransitionDate( + _Cal.get(), UTimeZoneTransitionType::UCAL_TZ_TRANSITION_NEXT, &_Transition, &_UErr) + ? _Transition + : U_DATE_MAX; + if (U_FAILURE(_UErr)) { + return _Report_error(_Info, __std_tzdb_error::_Icu_error); + } + + int32_t _Abbrev_len{}; + const auto _Abbrev = _Get_timezone_short_id(_Cal.get(), _Is_daylight, _Abbrev_len, _Info->_Err); + if (_Abbrev == nullptr) { + return _Propagate_error(_Info); + } + + _Info->_Abbrev = _Allocate_wide_to_narrow(_Abbrev.get(), _Abbrev_len, _Info->_Err); + if (_Info->_Abbrev == nullptr) { + return _Propagate_error(_Info); + } + + return _Info.release(); +} + +void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* const _Info) noexcept { + if (_Info) { + delete[] _Info->_Abbrev; + _Info->_Abbrev = nullptr; + } +} + __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( const size_t prev_reg_ls_size, size_t* const current_reg_ls_size) noexcept { // On exit--- diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index 04ced25c09..ba6b4bd14a 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -1,61 +1,76 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include "timezone_data.h" #include #include #include #include +#include +#include #include +#include using namespace std; using namespace std::chrono; -void test_time_zone_and_link(const tzdb& tzdb, string_view tz_name, string_view tz_link_name) { - const auto orginal_tz = tzdb.locate_zone(tz_name); - assert(orginal_tz != nullptr); - assert(orginal_tz->name() == tz_name); +// NOTE: +// These test suites will assume all data from the IANA database is correct +// and will not test historical changes in transitions. Instead the focus +// will be on using a select sample of transitions in both a positive and +// negative UTC offset zone (different corner cases). - const auto linked_tz = tzdb.locate_zone(tz_link_name); +constexpr bool sys_equal(const sys_info& left, const sys_info& right) { + return left.begin == right.begin && left.end == right.end && left.offset == right.offset && left.save == right.save + && left.abbrev == right.abbrev; +} + +void test_time_zone_and_link(const tzdb& my_tzdb, string_view tz_name, string_view tz_link_name) { + const auto original_tz = my_tzdb.locate_zone(tz_name); + assert(original_tz != nullptr); + assert(original_tz->name() == tz_name); + + const auto linked_tz = my_tzdb.locate_zone(tz_link_name); assert(linked_tz != nullptr); assert(linked_tz->name() == tz_name); - assert(orginal_tz == linked_tz); + assert(original_tz == linked_tz); - const auto tz_link = _Locate_zone_impl(tzdb.links, tz_link_name); + const auto tz_link = _Locate_zone_impl(my_tzdb.links, tz_link_name); assert(tz_link != nullptr); assert(tz_link->name() == tz_link_name); assert(tz_link->target() == tz_name); - assert(tzdb.locate_zone(tz_link->target()) == orginal_tz); + assert(my_tzdb.locate_zone(tz_link->target()) == original_tz); - assert(_Locate_zone_impl(tzdb.zones, tz_name) != nullptr); - assert(_Locate_zone_impl(tzdb.zones, tz_link_name) == nullptr); - assert(_Locate_zone_impl(tzdb.links, tz_name) == nullptr); + assert(_Locate_zone_impl(my_tzdb.zones, tz_name) != nullptr); + assert(_Locate_zone_impl(my_tzdb.zones, tz_link_name) == nullptr); + assert(_Locate_zone_impl(my_tzdb.links, tz_name) == nullptr); } -void try_locate_invalid_zone(const tzdb& tzdb, string_view name) { +void try_locate_invalid_zone(const tzdb& my_tzdb, string_view name) { try { - (void) tzdb.locate_zone(name); + (void) my_tzdb.locate_zone(name); assert(false); - } catch (runtime_error) { + } catch (const runtime_error&) { } } void timezone_names_test() { - const auto& tzdb = get_tzdb(); + const auto& my_tzdb = get_tzdb(); - assert(tzdb.version.empty() == false); + assert(my_tzdb.version.empty() == false); - test_time_zone_and_link(tzdb, "Asia/Thimphu", "Asia/Thimbu"); - test_time_zone_and_link(tzdb, "America/Tijuana", "America/Ensenada"); + test_time_zone_and_link(my_tzdb, "Asia/Thimphu", "Asia/Thimbu"); + test_time_zone_and_link(my_tzdb, "America/Tijuana", "America/Ensenada"); - const auto current_zone = tzdb.current_zone(); + const auto current_zone = my_tzdb.current_zone(); assert(current_zone != nullptr); assert(current_zone->name().empty() == false); - try_locate_invalid_zone(tzdb, "Non/Existent"); + try_locate_invalid_zone(my_tzdb, "Non/Existent"); // Abbreviations should not be time_zones or time_zone_links - try_locate_invalid_zone(tzdb, "PDT"); - try_locate_invalid_zone(tzdb, "AEST"); + try_locate_invalid_zone(my_tzdb, "PDT"); + try_locate_invalid_zone(my_tzdb, "AEST"); // Comparison operators const time_zone tz1{"Earlier"}; @@ -80,21 +95,287 @@ void timezone_names_test() { assert(link3 <=> link1 == strong_ordering::greater); #endif // __cpp_lib_concepts + try { + // ensure locate_zone returns time_zone with given name + assert(all_of(my_tzdb.zones.begin(), my_tzdb.zones.end(), + [&](const auto& zone) { return my_tzdb.locate_zone(zone.name())->name() == zone.name(); })); + // ensure locate_zone returns correct target of time_zone_link + assert(all_of(my_tzdb.links.begin(), my_tzdb.links.end(), + [&](const auto& link) { return my_tzdb.locate_zone(link.name())->name() == link.target(); })); + // ensure locate_zone does NOT return time_zone that is also a time_zone_link + assert(all_of(my_tzdb.links.begin(), my_tzdb.links.end(), + [&](const auto& link) { return my_tzdb.locate_zone(link.name())->name() != link.name(); })); + } catch (const runtime_error&) { + assert(false); + } + // FIXME: add a link to an issue. These may change overtime and might have to be removed from tests. // these are some example in which the ICU.dll and IANA database diverge in what they consider a zone or a link - assert(_Locate_zone_impl(tzdb.links, "Atlantic/Faroe") != nullptr); // is a time_zone in IANA - assert(_Locate_zone_impl(tzdb.zones, "Africa/Addis_Ababa") != nullptr); // is a time_zone_link in IANA - assert(_Locate_zone_impl(tzdb.links, "PST") != nullptr); // time_zone_link does not exist in IANA - assert(_Locate_zone_impl(tzdb.links, "Africa/Asmara") != nullptr); // matches IANA but target is wrong - assert(_Locate_zone_impl(tzdb.links, "Africa/Asmara")->target() == "Africa/Asmera"); // target == Africa/Nairobi - assert(_Locate_zone_impl(tzdb.zones, "America/Nuuk") == nullptr); // does not exist in ICU (very rare) + assert(_Locate_zone_impl(my_tzdb.links, "Atlantic/Faroe") != nullptr); // is a time_zone in IANA + assert(_Locate_zone_impl(my_tzdb.zones, "Africa/Addis_Ababa") != nullptr); // is a time_zone_link in IANA + assert(_Locate_zone_impl(my_tzdb.links, "PST") != nullptr); // time_zone_link does not exist in IANA + assert(_Locate_zone_impl(my_tzdb.links, "Africa/Asmara") != nullptr); // matches IANA but target is wrong + assert(_Locate_zone_impl(my_tzdb.links, "Africa/Asmara")->target() == "Africa/Asmera"); // target == Africa/Nairobi + assert(_Locate_zone_impl(my_tzdb.zones, "America/Nuuk") == nullptr); // does not exist in ICU (very rare) +} + +void validate_timezone_transitions(const time_zone* tz, const Transition& transition) { + auto info = tz->get_info(transition.begin()); + assert(info.begin == transition.begin()); + assert(info.end == transition.end()); + assert(info.offset == transition.offset()); + assert(info.save == transition.save()); + assert(info.abbrev == transition.abbrev()); +} + +void timezone_sys_info_test() { + const auto& my_tzdb = get_tzdb(); + + auto sydney_tz = my_tzdb.locate_zone(Sydney::Tz_name); + assert(sydney_tz != nullptr); + validate_timezone_transitions(sydney_tz, Sydney::Day_1); + validate_timezone_transitions(sydney_tz, Sydney::Std_1); + validate_timezone_transitions(sydney_tz, Sydney::Day_2); + + auto la_tz = my_tzdb.locate_zone(LA::Tz_name); + assert(la_tz != nullptr); + validate_timezone_transitions(la_tz, LA::Day_1); + validate_timezone_transitions(la_tz, LA::Std_1); + validate_timezone_transitions(la_tz, LA::Day_2); + + auto begin_info = sydney_tz->get_info(Sydney::Std_1.begin()); + auto middle_info = sydney_tz->get_info(Sydney::Std_1.begin() + days{1}); + auto end_info = sydney_tz->get_info(Sydney::Std_1.end()); + assert(sys_equal(begin_info, middle_info)); + assert(!sys_equal(begin_info, end_info)); + + auto min_info = sydney_tz->get_info(time_zone::_Min_seconds); + auto max_info = sydney_tz->get_info(time_zone::_Max_seconds - seconds{1}); + assert(min_info.begin == time_zone::_Min_seconds); + assert(min_info.end != time_zone::_Max_seconds); + assert(max_info.begin != time_zone::_Min_seconds); + assert(max_info.end == time_zone::_Max_seconds); + + auto utc_zone = my_tzdb.locate_zone("Etc/UTC"); + assert(utc_zone != nullptr); + + auto min_utc = utc_zone->get_info(time_zone::_Min_seconds); + auto max_utc = utc_zone->get_info(time_zone::_Max_seconds - seconds{1}); + // Only a single transition in UTC + assert(sys_equal(min_utc, max_utc)); + assert(min_utc.begin < max_utc.end); + assert(min_utc.begin == time_zone::_Min_seconds); + assert(min_utc.end == time_zone::_Max_seconds); + + // Test abbreviations other than standard/daylight savings such as war time. + // These scenarios are not handled correctly by icu.dll + auto war_time = la_tz->get_info(sys_days{year{1942} / April / day{1}}); + assert(war_time.abbrev == "PDT"); // IANA database == "PWT" +} + +void timezone_to_local_test() { + const auto& my_tzdb = get_tzdb(); + { + using namespace Sydney; + auto tz = my_tzdb.locate_zone(Tz_name); + assert(tz != nullptr); + + const auto& to_standard = Day_to_Std.first.end(); + assert(tz->to_local(to_standard) == Day_to_Std.second.local_begin()); + assert(tz->to_local(to_standard + minutes{30}) == Day_to_Std.second.local_begin() + minutes{30}); + assert(tz->to_local(to_standard - minutes{30}) == Day_to_Std.first.local_end() - minutes{30}); + + const auto& to_daylight = Std_to_Day.first.end(); + assert(tz->to_local(to_daylight) == Std_to_Day.second.local_begin()); + assert(tz->to_local(to_daylight + minutes{30}) == Std_to_Day.second.local_begin() + minutes{30}); + assert(tz->to_local(to_daylight - minutes{30}) == Std_to_Day.first.local_end() - minutes{30}); + } + { + using namespace LA; + auto tz = my_tzdb.locate_zone(Tz_name); + assert(tz != nullptr); + + const auto& to_standard = Day_to_Std.first.end(); + assert(tz->to_local(to_standard) == Day_to_Std.second.local_begin()); + assert(tz->to_local(to_standard + minutes{30}) == Day_to_Std.second.local_begin() + minutes{30}); + assert(tz->to_local(to_standard - minutes{30}) == Day_to_Std.first.local_end() - minutes{30}); + + const auto& to_daylight = Std_to_Day.first.end(); + assert(tz->to_local(to_daylight) == Std_to_Day.second.local_begin()); + assert(tz->to_local(to_daylight + minutes{30}) == Std_to_Day.second.local_begin() + minutes{30}); + assert(tz->to_local(to_daylight - minutes{30}) == Std_to_Day.first.local_end() - minutes{30}); + } +} + +void assert_local(const time_zone* tz, local_seconds local, int result, const sys_info& first, const sys_info& second) { + const auto info = tz->get_info(local); + assert(info.result == result); + assert(sys_equal(info.first, first)); + assert(sys_equal(info.second, second)); + + // time_zone::to_sys depends heavily on local_info so just test it here + // to exhaust all corner cases. + sys_seconds sys_earliest{local.time_since_epoch() - info.first.offset}; + sys_seconds sys_latest{local.time_since_epoch() - info.second.offset}; + try { + assert(tz->to_sys(local) == sys_earliest); + assert(result == local_info::unique); + } catch (const nonexistent_local_time&) { + assert(result == local_info::nonexistent); + } catch (const ambiguous_local_time&) { + assert(result == local_info::ambiguous); + } + + if (result == local_info::unique) { + assert(tz->to_sys(local, choose::earliest) == sys_earliest); + assert(tz->to_sys(local, choose::latest) == sys_earliest); + } else if (result == local_info::nonexistent) { + assert(tz->to_sys(local, choose::earliest) == info.first.end); + assert(tz->to_sys(local, choose::latest) == info.first.end); + } else if (result == local_info::ambiguous) { + assert(tz->to_sys(local, choose::earliest) == sys_earliest); + assert(tz->to_sys(local, choose::latest) == sys_latest); + } +} + +void validate_get_local_info(const time_zone* tz, const pair& transition, int result) { + sys_info default_info{}; + sys_info first = tz->get_info(transition.first.begin()); + sys_info second = tz->get_info(transition.second.begin()); + + // Get the local time for the beginning of the ambiguous/nonexistent section + const auto danger_begin = get_danger_begin(transition.first, transition.second); + const auto danger_end = get_danger_end(transition.first, transition.second); + assert_local(tz, danger_begin - days{2}, local_info::unique, first, default_info); // two days before + assert_local(tz, danger_begin - hours{1}, local_info::unique, first, default_info); // one hour before + assert_local(tz, danger_begin, result, first, second); // danger begin + assert_local(tz, danger_begin + minutes{30}, result, first, second); // danger middle + assert_local(tz, danger_end, local_info::unique, second, default_info); // danger end + assert_local(tz, danger_end + hours{1}, local_info::unique, second, default_info); // one hour after + assert_local(tz, danger_end + days{2}, local_info::unique, second, default_info); // two days after +} + +void timezone_local_info_test() { + const auto& my_tzdb = get_tzdb(); + { + // positive offset (UTC+10/+11) can fall in previous transition + using namespace Sydney; + auto tz = my_tzdb.locate_zone(Tz_name); + assert(tz != nullptr); + validate_get_local_info(tz, Day_to_Std, local_info::ambiguous); + validate_get_local_info(tz, Std_to_Day, local_info::nonexistent); + } + { + // negative offset (UTC-8/-7) can fall in next transition + using namespace LA; + auto tz = my_tzdb.locate_zone(Tz_name); + assert(tz != nullptr); + validate_get_local_info(tz, Day_to_Std, local_info::ambiguous); + validate_get_local_info(tz, Std_to_Day, local_info::nonexistent); + } +} + +template +void validate_precision(const time_zone* tz, const pair& transition_pair, Dur precision) { + const auto& first = transition_pair.first; + const auto& second = transition_pair.second; + const auto transition = first.end(); + const auto danger_begin = get_danger_begin(first, second); + const auto danger_end = get_danger_end(first, second); + sys_info first_info = tz->get_info(first.begin()); + sys_info second_info = tz->get_info(second.begin()); + + // test correct transition is picked + assert(sys_equal(tz->get_info(transition), second_info)); + assert(sys_equal(tz->get_info(transition - precision), first_info)); + + // test ambiguous/nonexistent info is handled + assert(tz->get_info(danger_end).result == local_info::unique); // exact end of danger zone + assert(tz->get_info(danger_end - precision).result != local_info::unique); // just inside danger zone + assert(tz->get_info(danger_begin).result != local_info::unique); // exact start of danger zone + assert(tz->get_info(danger_begin - precision).result == local_info::unique); // just before danger zone + + // test precision is not lost when converting to local + assert(tz->to_local(transition) == second.local_begin()); + assert(tz->to_local(transition + precision) == second.local_begin() + precision); + assert(tz->to_local(transition - precision) == first.local_end() - precision); + + // test precision is not lost when converting to sys + try { + const sys_time sys_danger_begin = transition - first.save(); + const sys_time sys_danger_end = transition + first.save(); + assert(tz->to_sys(danger_end) == sys_danger_end); + assert(tz->to_sys(danger_end + precision) == sys_danger_end + precision); + assert(tz->to_sys(danger_begin - precision) == sys_danger_begin - precision); + } catch (const nonexistent_local_time&) { + assert(false); + } catch (const ambiguous_local_time&) { + assert(false); + } + + try { + // test ambiguous/nonexistent info is found + (void) tz->to_sys(danger_end - precision); + assert(false); + } catch (const nonexistent_local_time&) { + } catch (const ambiguous_local_time&) { + } +} + +void timezone_precision_test() { + const auto& my_tzdb = get_tzdb(); + using MilliDur = duration; + using MicroDur = duration; + + { + using namespace Sydney; + auto tz = my_tzdb.locate_zone(Tz_name); + validate_precision(tz, Std_to_Day, sys_seconds::duration{1}); + validate_precision(tz, Std_to_Day, MilliDur{1}); + validate_precision(tz, Std_to_Day, MilliDur{0.5}); + validate_precision(tz, Std_to_Day, MilliDur{0.05}); + validate_precision(tz, Std_to_Day, MilliDur{0.005}); + validate_precision(tz, Std_to_Day, MilliDur{0.0005}); + // precision limit... + + validate_precision(tz, Std_to_Day, MicroDur{1}); + validate_precision(tz, Std_to_Day, MicroDur{0.5}); + // precision limit... + + // validate opposite transition + validate_precision(tz, Day_to_Std, MicroDur{0.5}); + validate_precision(tz, Day_to_Std, MilliDur{0.0005}); + } + { + using namespace LA; + auto tz = my_tzdb.locate_zone(Tz_name); + validate_precision(tz, Std_to_Day, sys_seconds::duration{1}); + validate_precision(tz, Std_to_Day, MilliDur{1}); + validate_precision(tz, Std_to_Day, MilliDur{0.5}); + validate_precision(tz, Std_to_Day, MilliDur{0.05}); + validate_precision(tz, Std_to_Day, MilliDur{0.005}); + validate_precision(tz, Std_to_Day, MilliDur{0.0005}); + // precision limit... + + validate_precision(tz, Std_to_Day, MicroDur{1}); + validate_precision(tz, Std_to_Day, MicroDur{0.5}); + // precision limit... + + // validate opposite transition + validate_precision(tz, Day_to_Std, MicroDur{0.5}); + validate_precision(tz, Day_to_Std, MilliDur{0.0005}); + } } bool test() { try { timezone_names_test(); - } catch (exception& ex) { + timezone_sys_info_test(); + timezone_to_local_test(); + timezone_local_info_test(); + timezone_precision_test(); + } catch (const exception& ex) { cerr << "Test threw exception: " << ex.what() << "\n"; assert(false); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h new file mode 100644 index 0000000000..9f72666013 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +class Transition { +public: + constexpr Transition( + string_view name, seconds offset, minutes save, string_view abbrev, sys_seconds sys_begin, sys_seconds sys_end) + : _name(name), _offset(offset), _save(save), _abbrev(abbrev), _begin(sys_begin), _end(sys_end) {} + + constexpr string_view name() const { + return _name; + } + + constexpr seconds offset() const { + return _offset; + } + + constexpr minutes save() const { + return _save; + } + + constexpr string_view abbrev() const { + return _abbrev; + } + + constexpr bool is_daylight() const { + return _save != minutes{0}; + } + + template + constexpr sys_time begin() const { + return sys_time{duration_cast(_begin.time_since_epoch())}; + } + + template + constexpr sys_time end() const { + return sys_time{duration_cast(_end.time_since_epoch())}; + } + + template + constexpr local_time local_begin() const { + return local_time{duration_cast(_begin.time_since_epoch() + _offset)}; + } + + template + constexpr local_time local_end() const { + return local_time{duration_cast(_end.time_since_epoch() + _offset)}; + } + +private: + string_view _name; + seconds _offset; + minutes _save; + string_view _abbrev; + sys_seconds _begin; + sys_seconds _end; +}; + +// start of ambiguous/nonexistent zone between transitions +template +constexpr local_time get_danger_begin(const Transition& first, const Transition& second) { + assert(first.end() == second.begin()); + return first.local_end() - first.save(); +} + +// end of ambiguous/nonexistent zone between transitions +template +constexpr local_time get_danger_end(const Transition& first, const Transition& second) { + assert(first.end() == second.begin()); + return second.local_begin() + first.save(); +} + +// Sydney +// Standard time (AEST : UTC+10) -1 @ 3am +// Daylight time (AEDT : UTC+11) +1 @ 2am +namespace Sydney { + inline constexpr string_view Tz_name{"Australia/Sydney"sv}; + inline constexpr string_view Standard_abbrev{"GMT+10"sv}; // IANA database == "AEST" + inline constexpr string_view Daylight_abbrev{"GMT+11"sv}; // IANA database == "AEDT" + inline constexpr seconds Standard_offset{hours{10}}; + inline constexpr seconds Daylight_offset{hours{11}}; + inline constexpr auto Daylight_begin_2019 = + sys_seconds{sys_days{year{2019} / October / day{6}}} + hours{2} - Standard_offset; + inline constexpr auto Standard_begin_2020 = + sys_seconds{sys_days{year{2020} / April / day{5}}} + hours{3} - Daylight_offset; + inline constexpr auto Daylight_begin_2020 = + sys_seconds{sys_days{year{2020} / October / day{4}}} + hours{2} - Standard_offset; + inline constexpr auto Standard_begin_2021 = + sys_seconds{sys_days{year{2021} / April / day{4}}} + hours{3} - Daylight_offset; + + inline constexpr Transition Day_1{ + Tz_name, Daylight_offset, hours{1}, Daylight_abbrev, Daylight_begin_2019, Standard_begin_2020}; + inline constexpr Transition Std_1{ + Tz_name, Standard_offset, hours{0}, Standard_abbrev, Standard_begin_2020, Daylight_begin_2020}; + inline constexpr Transition Day_2{ + Tz_name, Daylight_offset, hours{1}, Daylight_abbrev, Daylight_begin_2020, Standard_begin_2021}; + + inline constexpr pair Day_to_Std{Day_1, Std_1}; + inline constexpr pair Std_to_Day{Std_1, Day_2}; + +} // namespace Sydney + +// Los Angeles +// Standard time (PST : UTC-8) +1 @ 2am +// Daylight time (PDT : UTC-7) -1 @ 2am +namespace LA { + inline constexpr string_view Tz_name{"America/Los_Angeles"sv}; + inline constexpr string_view Standard_abbrev{"PST"sv}; + inline constexpr string_view Daylight_abbrev{"PDT"sv}; + inline constexpr seconds Standard_offset{hours{-8}}; + inline constexpr seconds Daylight_offset{hours{-7}}; + inline constexpr auto Daylight_begin_2020 = + sys_seconds{sys_days{year{2020} / March / day{8}}} + hours{2} - Standard_offset; + inline constexpr auto Standard_begin_2020 = + sys_seconds{sys_days{year{2020} / November / day{1}}} + hours{2} - Daylight_offset; + inline constexpr auto Daylight_begin_2021 = + sys_seconds{sys_days{year{2021} / March / day{14}}} + hours{2} - Standard_offset; + inline constexpr auto Standard_begin_2021 = + sys_seconds{sys_days{year{2021} / November / day{7}}} + hours{2} - Daylight_offset; + + inline constexpr Transition Day_1{ + Tz_name, Daylight_offset, hours{1}, Daylight_abbrev, Daylight_begin_2020, Standard_begin_2020}; + inline constexpr Transition Std_1{ + Tz_name, Standard_offset, hours{0}, Standard_abbrev, Standard_begin_2020, Daylight_begin_2021}; + inline constexpr Transition Day_2{ + Tz_name, Daylight_offset, hours{1}, Daylight_abbrev, Daylight_begin_2021, Standard_begin_2021}; + + inline constexpr pair Day_to_Std{Day_1, Std_1}; + inline constexpr pair Std_to_Day{Std_1, Day_2}; + +} // namespace LA From cb2b9853417c6d8df22f9789aa25b7eb57c39eb1 Mon Sep 17 00:00:00 2001 From: d-winsor Date: Thu, 18 Mar 2021 20:09:11 -0700 Subject: [PATCH 04/22] Support for zoned_time (#1752) Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 184 +++++++++ .../timezone_data.hpp} | 0 tests/std/test.lst | 1 + .../test.cpp | 3 +- .../env.lst | 4 + .../test.cpp | 355 ++++++++++++++++++ 6 files changed, 546 insertions(+), 1 deletion(-) rename tests/std/{tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h => include/timezone_data.hpp} (100%) create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/env.lst create mode 100644 tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp diff --git a/stl/inc/chrono b/stl/inc/chrono index 92d380c3d6..3d13b2b862 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2836,6 +2836,190 @@ namespace chrono { } } + // [time.zone.zonedtraits] + + // STRUCT TEMPLATE zoned_traits + template + struct zoned_traits {}; + + // STRUCT zoned_traits + template <> + struct zoned_traits { + _NODISCARD static const time_zone* default_zone() { + return _CHRONO get_tzdb().locate_zone("UTC"); + } + + _NODISCARD static const time_zone* locate_zone(string_view _Name) { + return _CHRONO get_tzdb().locate_zone(_Name); + } + }; + + // [time.zone.zonedtime] + + // CLASS TEMPLATE zoned_time + template + class zoned_time { + private: + static_assert(_Is_duration_v<_Duration>, + "N4878 [time.zone.zonedtime.overview]/2 requires Duration to be a specialization of chrono::duration."); + + using _Traits = zoned_traits<_TimeZonePtr>; + + public: + using duration = common_type_t<_Duration, seconds>; + + template + zoned_time() : _Zone{_Traits::default_zone()} {} + zoned_time(const zoned_time&) = default; + zoned_time& operator=(const zoned_time&) = default; + + template + zoned_time(const sys_time<_Duration>& _Sys) : _Zone{_Traits::default_zone()}, _Tp{_Sys} {} + + explicit zoned_time(_TimeZonePtr _Tz) noexcept /* strengthened */ : _Zone{_STD move(_Tz)} {} + + template , int> = 0> + explicit zoned_time(string_view _Name) : _Zone{_Traits::locate_zone(_Name)} {} + + template , sys_time<_Duration>>, int> = 0> + zoned_time(const zoned_time<_Duration2, _TimeZonePtr>& _Zt) noexcept /* strengthened */ + : _Zone{_Zt.get_time_zone()}, _Tp{_Zt.get_sys_time()} {} + + zoned_time(_TimeZonePtr _Tz, const sys_time<_Duration>& _Sys) : _Zone{_STD move(_Tz)}, _Tp{_Sys} {} + + template &>, + int> = 0> + zoned_time(string_view _Name, type_identity_t&> _Sys) + : zoned_time{_Traits::locate_zone(_Name), _Sys} {} + + template ()->to_sys(local_time<_Duration>{})), sys_time>, + int> = 0> + zoned_time(_TimeZonePtr _Tz, const local_time<_Duration>& _Local) + : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local)} {} + + template &>, + int> = 0> + zoned_time(string_view _Name, type_identity_t>& _Local) + : zoned_time{_Traits::locate_zone(_Name), _Local} {} + + template ()->to_sys(local_time<_Duration>{}, choose::earliest)), + sys_time>, + int> = 0> + zoned_time(_TimeZonePtr _Tz, const local_time<_Duration>& _Local, choose _Choose) + : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local, _Choose)} {} + + template &, choose>, + int> = 0> + zoned_time(string_view _Name, type_identity_t&> _Local, choose _Choose) + : zoned_time{_Traits::locate_zone(_Name), _Local, _Choose} {} + + template , sys_time<_Duration>>, int> = 0> + zoned_time(_TimeZonePtr _Tz, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt) noexcept /* strengthened */ + : _Zone{_STD move(_Tz)}, _Tp{_Zt.get_sys_time()} {} + + template , sys_time<_Duration>>, int> = 0> + zoned_time( + _TimeZonePtr _Tz, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt, choose) noexcept /* strengthened */ + : zoned_time{_Tz, _Zt} {} + + template &>, + int> = 0> + zoned_time(string_view _Name, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt) + : zoned_time{_Traits::locate_zone(_Name), _Zt} {} + + template &, choose>, + int> = 0> + zoned_time(string_view _Name, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt, choose _Choose) + : zoned_time{_Traits::locate_zone(_Name), _Zt, _Choose} {} + + zoned_time& operator=(const sys_time<_Duration>& _Sys) noexcept /* strengthened */ { + _Tp = _Sys; + return *this; + } + + zoned_time& operator=(const local_time<_Duration>& _Local) { + _Tp = _Zone->to_sys(_Local); + return *this; + } + + operator sys_time() const noexcept /* strengthened */ { + return get_sys_time(); + } + + explicit operator local_time() const { + return get_local_time(); + } + + _NODISCARD _TimeZonePtr get_time_zone() const noexcept /* strengthened */ { + return _Zone; + } + + _NODISCARD local_time get_local_time() const { + return _Zone->to_local(_Tp); + } + + _NODISCARD sys_time get_sys_time() const noexcept /* strengthened */ { + return _Tp; + } + + _NODISCARD sys_info get_info() const { + return _Zone->get_info(_Tp); + } + + private: + _TimeZonePtr _Zone; + sys_time _Tp{}; + }; + + zoned_time()->zoned_time; + + template + zoned_time(sys_time<_Duration>) -> zoned_time>; + + template + using _Time_zone_representation = conditional_t, const time_zone*, + remove_cvref_t<_TimeZonePtrOrName>>; + + template + zoned_time(_TimeZonePtrOrName&&) -> zoned_time>; + + template + zoned_time(_TimeZonePtrOrName&&, sys_time<_Duration>) + -> zoned_time, _Time_zone_representation<_TimeZonePtrOrName>>; + + template + zoned_time(_TimeZonePtrOrName&&, local_time<_Duration>, choose = choose::earliest) + -> zoned_time, _Time_zone_representation<_TimeZonePtrOrName>>; + + template + zoned_time(_TimeZonePtrOrName&&, zoned_time<_Duration, _TimeZonePtr2>, choose = choose::earliest) + -> zoned_time, _Time_zone_representation<_TimeZonePtrOrName>>; + + using zoned_seconds = zoned_time; + + template + _NODISCARD bool operator==( + const zoned_time<_Duration1, _TimeZonePtr>& _Left, const zoned_time<_Duration2, _TimeZonePtr>& _Right) { + return _Left.get_time_zone() == _Right.get_time_zone() && _Left.get_sys_time() == _Right.get_sys_time(); + } + // [time.clock.utc] class utc_clock; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h b/tests/std/include/timezone_data.hpp similarity index 100% rename from tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/timezone_data.h rename to tests/std/include/timezone_data.hpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 3b7270fd45..64213f1ee4 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -236,6 +236,7 @@ tests\P0355R7_calendars_and_time_zones_hms tests\P0355R7_calendars_and_time_zones_io tests\P0355R7_calendars_and_time_zones_time_point_and_durations tests\P0355R7_calendars_and_time_zones_time_zones +tests\P0355R7_calendars_and_time_zones_zoned_time tests\P0356R5_bind_front tests\P0357R3_supporting_incomplete_types_in_reference_wrapper tests\P0408R7_efficient_access_to_stringbuf_buffer diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index ba6b4bd14a..f6423830a0 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include "timezone_data.h" #include #include #include @@ -11,6 +10,8 @@ #include #include +#include + using namespace std; using namespace std::chrono; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/env.lst b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/env.lst new file mode 100644 index 0000000000..642f530ffa --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp new file mode 100644 index 0000000000..6d18ade717 --- /dev/null +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp @@ -0,0 +1,355 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace std::chrono; +using ZT = zoned_traits; + +struct time_zone_info { + time_zone_info(string_view _name_, const time_zone* _tz_, seconds _sys_) : name(_name_), tz(_tz_), sys(_sys_) {} + + string_view name; + const time_zone* tz; + sys_seconds sys; + local_seconds local{tz->to_local(sys)}; + zoned_time zone{tz, sys}; +}; + +void zonedtime_constructor_test() { + const time_zone_info utc{"Etc/UTC", ZT::default_zone(), seconds{42}}; + const time_zone_info syd{Sydney::Tz_name, ZT::locate_zone(Sydney::Tz_name), seconds{20}}; + + // ensure local conversions are valid + assert(utc.tz->get_info(utc.local).result == local_info::unique); + assert(syd.tz->get_info(syd.local).result == local_info::unique); + + { + // defaulted copy + zoned_time zone{syd.zone}; + assert(zone.get_time_zone() == syd.tz); + assert(zone.get_sys_time() == syd.sys); + } + { + // (1) zoned_time(); + zoned_time zone{}; + assert(zone.get_time_zone() == utc.tz); + assert(zone.get_sys_time() == sys_seconds{}); + } + { + // (2) zoned_time(const sys_time& st); + zoned_time zone{sys_seconds{seconds{1}}}; + assert(zone.get_time_zone() == utc.tz); + assert(zone.get_sys_time() == sys_seconds{seconds{1}}); + } + { + // (3) explicit zoned_time(TimeZonePtr z); + zoned_time zone{&*syd.tz}; + assert(zone.get_time_zone() == syd.tz); + assert(zone.get_sys_time() == sys_seconds{}); + + // (4) explicit zoned_time(string_view name); + assert(zoned_time{syd.name} == zone); + } + { + // (5) template + // zoned_time(const zoned_time& y); + zoned_time zone_hours{syd.name, sys_time{hours{1}}}; + assert(zone_hours.get_time_zone() == syd.tz); + assert(zone_hours.get_sys_time() == sys_time{hours{1}}); + } + { + // (6) zoned_time(TimeZonePtr z, const sys_time& st); + zoned_time zone{&*syd.tz, syd.sys}; + assert(zone.get_time_zone() == syd.tz); + assert(zone.get_sys_time() == syd.sys); + + // (7) zoned_time(string_view name, const sys_time& st); + assert((zoned_time{syd.name, syd.sys}) == zone); + } + { + // (8) zoned_time(TimeZonePtr z, const local_time& tp); + zoned_time zone{&*syd.tz, syd.local}; + assert(zone.get_time_zone() == syd.tz); + assert(zone.get_sys_time() == syd.sys); + + // (9) zoned_time(string_view name, const local_time& st); + assert((zoned_time{syd.name, syd.local}) == zone); + } + { + // (10) zoned_time(TimeZonePtr z, const local_time& tp, choose c); + zoned_time zone{&*syd.tz, syd.local, choose::earliest}; + assert(zone.get_time_zone() == syd.tz); + assert(zone.get_sys_time() == syd.sys); + + // (11) zoned_time(string_view name, const local_time& tp, choose c); + assert((zoned_time{syd.name, syd.local, choose::earliest}) == zone); + } + { + // (12) template + // zoned_time(TimeZonePtr z, const zoned_time& y); + zoned_time z1{&*utc.tz, syd.zone}; + zoned_time z2{&*syd.tz, utc.zone}; + assert(z1.get_time_zone() == utc.tz); + assert(z1.get_sys_time() == syd.sys); + assert(z2.get_time_zone() == syd.tz); + assert(z2.get_sys_time() == utc.sys); + + // (13) template + // zoned_time(TimeZonePtr z, const zoned_time& y, choose); + assert((zoned_time{&*utc.tz, syd.zone, choose::earliest}) == z1); + assert((zoned_time{&*syd.tz, utc.zone, choose::earliest}) == z2); + + // (14) template + // zoned_time(string_view name, const zoned_time& y); + assert((zoned_time{utc.name, syd.zone}) == z1); + assert((zoned_time{syd.name, utc.zone}) == z2); + + // (15) template + // zoned_time(string_view name, const zoned_time& y, choose); + assert((zoned_time{utc.name, syd.zone, choose::earliest}) == z1); + assert((zoned_time{syd.name, utc.zone, choose::earliest}) == z2); + + // when (_Duration2 != _Duration) + zoned_time zone_hours{&*syd.tz, sys_time{hours{1}}}; + zoned_time zone{&*utc.tz, zone_hours}; + assert(zone.get_time_zone() == utc.tz); + assert(zone.get_sys_time() == sys_time{hours{1}}); + + assert((zoned_time{&*utc.tz, zone_hours, choose::earliest}) == zone); + assert((zoned_time{utc.name, zone_hours}) == zone); + assert((zoned_time{utc.name, zone_hours, choose::earliest}) == zone); + } +} + +void zonedtime_operator_test() { + const auto utc_tz = ZT::default_zone(); + const zoned_time utc_zone{}; + assert(utc_zone.get_time_zone() == utc_tz); + assert(utc_zone.get_sys_time() == sys_seconds{}); + assert(utc_zone.get_local_time() == local_seconds{}); + + const auto syd_tz = ZT::locate_zone(Sydney::Tz_name); + const auto transition = Sydney::Day_1; + const auto sys = transition.begin(); + const auto local = syd_tz->to_local(transition.begin()); + assert(syd_tz->get_info(local).result == local_info::unique); + + zoned_time zone{ZT::locate_zone(Sydney::Tz_name), sys}; + assert(zone.get_time_zone() == syd_tz); + assert(zone.get_sys_time() == sys); + assert(zone.get_local_time() == local); + assert(zone.get_info().begin == sys); + + // set time to be == epoch + zone = sys_seconds{}; + assert(zone.get_time_zone() == syd_tz); + assert(zone.get_sys_time() == sys_seconds{}); + assert(zone.get_local_time() == syd_tz->to_local(sys_seconds{})); + assert(zone.get_info().begin != sys); + + // reset sys_time to transition.begin() via local_time + zone = local; + assert(zone.get_time_zone() == syd_tz); + assert(zone.get_sys_time() == sys); + assert(zone.get_local_time() == local); + assert(zone.get_info().begin == sys); +} + +void zonedtime_exception_tests() { + const auto syd_tz = ZT::locate_zone(Sydney::Tz_name); + const auto ambiguous_local = syd_tz->to_local(Sydney::Standard_begin_2020 - minutes{1}); + assert(syd_tz->get_info(ambiguous_local).result == local_info::ambiguous); + + // unsafe constructors + try { + (void) zoned_time{ZT::locate_zone(Sydney::Tz_name), ambiguous_local}; + assert(false); + } catch (nonexistent_local_time&) { + } catch (ambiguous_local_time&) { + } + + try { + (void) zoned_time{Sydney::Tz_name, ambiguous_local}; + assert(false); + } catch (nonexistent_local_time&) { + } catch (ambiguous_local_time&) { + } + + // safe constructors + try { + (void) zoned_time{ZT::locate_zone(Sydney::Tz_name), ambiguous_local, choose::earliest}; + (void) zoned_time{Sydney::Tz_name, ambiguous_local, choose::earliest}; + } catch (nonexistent_local_time&) { + assert(false); + } catch (ambiguous_local_time&) { + assert(false); + } + + // unsafe operator + try { + zoned_time zone{Sydney::Tz_name}; + zone = ambiguous_local; + (void) zone; + assert(false); + } catch (nonexistent_local_time&) { + } catch (ambiguous_local_time&) { + } +} + +struct Always_zero { + [[nodiscard]] string_view name() const noexcept { + return "Zero"; + } + + template + [[nodiscard]] sys_info get_info(const sys_time&) const { + return {}; + } + + template + [[nodiscard]] local_info get_info(const local_time&) const { + return {}; + } + + template + [[nodiscard]] sys_time> to_sys(const local_time&) const { + return sys_time>{}; + } + + template + [[nodiscard]] sys_time> to_sys(const local_time&, const choose) const { + return sys_time>{}; + } + + template + [[nodiscard]] local_time> to_local(const sys_time&) const { + return local_time>{}; + } +}; + +struct Has_default : Always_zero {}; +struct Has_locate : Always_zero {}; + +Always_zero zero_zone{}; +Has_default has_default_zone{}; +Has_locate has_locate_zone{}; + +template <> +struct zoned_traits { + [[nodiscard]] static const Has_default* default_zone() { + return &has_default_zone; + } + + // missing string_view parameter... + [[nodiscard]] static const Has_locate* locate_zone() { + return &has_locate_zone; + } +}; + +template <> +struct zoned_traits { + [[nodiscard]] static const Has_locate* locate_zone(string_view) { + return &has_locate_zone; + } +}; + +void zonedtime_traits_test() { + // operation using timezone should always result in zero + using Always_zero_ptr = const Always_zero*; + + zoned_time zone{&zero_zone, sys_seconds{seconds{1}}}; + assert(zone.get_time_zone() == &zero_zone); + assert(zone.get_sys_time() == sys_seconds{seconds{1}}); + assert(sys_seconds{zone} == sys_seconds{seconds{1}}); + assert(zone.get_local_time() == local_seconds{}); + assert(local_seconds{zone} == local_seconds{}); + assert(zone.get_info().begin == sys_seconds{}); + + zone = sys_seconds{seconds{2}}; + assert(zone.get_sys_time() == sys_seconds{seconds{2}}); + assert(sys_seconds{zone} == sys_seconds{seconds{2}}); + assert(zone.get_local_time() == local_seconds{}); + assert(local_seconds{zone} == local_seconds{}); + assert(zone.get_info().begin == sys_seconds{}); + + zone = local_seconds{seconds{3}}; + assert(zone.get_sys_time() == sys_seconds{}); // zero because timezone is used to compute sys_seconds + assert(sys_seconds{zone} == sys_seconds{}); + assert(zone.get_local_time() == local_seconds{}); + assert(local_seconds{zone} == local_seconds{}); + assert(zone.get_info().begin == sys_seconds{}); +} + +template +constexpr void assert_constructible_durations() { + using Zoned_seconds = zoned_time; + using Zoned_duration = zoned_time; + + static_assert(is_constructible_v == Result); + static_assert(is_constructible_v == Result); + static_assert(is_constructible_v == (Result && Has_locate_zone)); + static_assert(is_constructible_v == Result); + static_assert(is_constructible_v == (Result && Has_locate_zone)); +} + +template +constexpr void assert_constructible() { + using Zoned = zoned_time; + using Zoned_no_locate = zoned_time; + + static_assert(is_constructible_v == Has_default_zone); + static_assert(is_constructible_v&> == Has_default_zone); + static_assert(is_constructible_v == true); + static_assert(is_constructible_v == Has_locate_zone); + static_assert(is_constructible_v == true); + static_assert(is_constructible_v&> == true); + static_assert(is_constructible_v&> == Has_locate_zone); + static_assert(is_constructible_v&> == true); + static_assert(is_constructible_v&> == Has_locate_zone); + static_assert(is_constructible_v&, choose> == true); + static_assert(is_constructible_v&, choose> == Has_locate_zone); + static_assert(is_constructible_v == true); + static_assert(is_constructible_v == Has_locate_zone); + static_assert(is_constructible_v == true); + static_assert(is_constructible_v == Has_locate_zone); + + // when (_Duration2 != _Duration) + assert_constructible_durations(); + static_assert(is_convertible_v, sys_time> == true); + assert_constructible_durations(); + static_assert(is_convertible_v, sys_time> == false); + assert_constructible_durations(); +} + +constexpr void zonedtime_constraints_test() { + assert_constructible(); + assert_constructible(); + assert_constructible(); + assert_constructible(); +} + +void test() { + try { + zonedtime_constructor_test(); + zonedtime_operator_test(); + zonedtime_exception_tests(); + zonedtime_traits_test(); + zonedtime_constraints_test(); + } catch (const exception& ex) { + cerr << "Test threw exception: " << ex.what() << "\n"; + assert(false); + } +} + +int main() { + test(); +} From 74abbc2a6aeb7de91542f5a587de209467bcf6b9 Mon Sep 17 00:00:00 2001 From: d-winsor Date: Mon, 22 Mar 2021 19:46:58 -0700 Subject: [PATCH 05/22] Misc items for tzdb (#1765) Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 71 +++++++++++++++---- stl/inc/xtzdb.h | 32 ++++++++- stl/src/tzdb.cpp | 37 ++++++---- .../test.cpp | 48 +++++++++++-- 4 files changed, 155 insertions(+), 33 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 3d13b2b862..02af136b01 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2377,9 +2377,8 @@ namespace chrono { _NODISCARD sys_info _Get_info(const _Duration& _Dur) const { using _Internal_duration = duration<__std_tzdb_epoch_milli, milli>; const auto _Internal_dur = _CHRONO duration_cast<_Internal_duration>(_Dur); - const unique_ptr<__std_tzdb_sys_info, decltype(&__std_tzdb_delete_sys_info)> _Info{ - __std_tzdb_get_sys_info(_Name.c_str(), _Name.length(), _Internal_dur.count()), - &__std_tzdb_delete_sys_info}; + const unique_ptr<__std_tzdb_sys_info, _Tzdb_deleter<__std_tzdb_sys_info>> _Info{ + __std_tzdb_get_sys_info(_Name.c_str(), _Name.length(), _Internal_dur.count())}; if (_Info == nullptr) { _Xbad_alloc(); } else if (_Info->_Err == __std_tzdb_error::_Win_error) { @@ -2550,8 +2549,8 @@ namespace chrono { // [time.zone.db] _NODISCARD inline string _Xtzdb_generate_current_zone() { - unique_ptr<__std_tzdb_current_zone_info, decltype(&__std_tzdb_delete_current_zone)> _Info{ - __std_tzdb_get_current_zone(), &__std_tzdb_delete_current_zone}; + unique_ptr<__std_tzdb_current_zone_info, _Tzdb_deleter<__std_tzdb_current_zone_info>> _Info{ + __std_tzdb_get_current_zone()}; if (_Info == nullptr) { _Xbad_alloc(); } else if (_Info->_Err == __std_tzdb_error::_Win_error) { @@ -2599,8 +2598,8 @@ namespace chrono { }; _NODISCARD inline tuple _Xtzdb_generate_time_zones() { - unique_ptr<__std_tzdb_time_zones_info, decltype(&__std_tzdb_delete_time_zones)> _Info{ - __std_tzdb_get_time_zones(), &__std_tzdb_delete_time_zones}; + unique_ptr<__std_tzdb_time_zones_info, _Tzdb_deleter<__std_tzdb_time_zones_info>> _Info{ + __std_tzdb_get_time_zones()}; if (_Info == nullptr) { _Xbad_alloc(); } else if (_Info->_Err == __std_tzdb_error::_Win_error) { @@ -2666,9 +2665,8 @@ namespace chrono { (_STD max)(_Current_size, _STD size(_Known_leap_seconds)) - _Pre_2018_count; size_t _Reg_post_2018_ls_size; // number of post-2018 LSs found in the registry - unique_ptr<__std_tzdb_registry_leap_info[], decltype(&__std_tzdb_delete_reg_leap_seconds)> _Reg_ls_data{ - __std_tzdb_get_reg_leap_seconds(_Known_post_2018_ls_size, &_Reg_post_2018_ls_size), - &__std_tzdb_delete_reg_leap_seconds}; + unique_ptr<__std_tzdb_registry_leap_info[], _Tzdb_deleter<__std_tzdb_registry_leap_info[]>> _Reg_ls_data{ + __std_tzdb_get_reg_leap_seconds(_Known_post_2018_ls_size, &_Reg_post_2018_ls_size)}; if (_Reg_post_2018_ls_size > _Known_post_2018_ls_size && !_Reg_ls_data) { _Xbad_alloc(); // registry has new data, but failed to allocate storage @@ -2700,6 +2698,11 @@ namespace chrono { return {_STD move(_Leap_sec_info), _All_ls_positive}; } + _NODISCARD inline string _Xtzdb_update_version(const string_view _Version, const size_t _Num_leap_seconds) { + string _Icu_version{_Version.substr(0, _Version.find_last_of('.'))}; + return _STD move(_Icu_version) + "." + _STD to_string(_Num_leap_seconds); + } + // TRANSITION: work in progress // CLASS tzdb_list class tzdb_list { @@ -2713,8 +2716,9 @@ namespace chrono { tzdb_list& operator=(const tzdb_list&) = delete; tzdb_list() { - auto [_Version, _Zones, _Links] = _Xtzdb_generate_time_zones(); - auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(0); + auto [_Icu_version, _Zones, _Links] = _Xtzdb_generate_time_zones(); + auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(0); + auto _Version = _Icu_version + "." + _STD to_string(_Leap_sec.size()); _Tzdb_list.emplace_front(tzdb{ _STD move(_Version), _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); } @@ -2724,6 +2728,26 @@ namespace chrono { return _Tzdb_list.front(); } + const_iterator erase_after(const_iterator _Where) noexcept /* strengthened */ { + return _Tzdb_list.erase_after(_Where); + } + + _NODISCARD const_iterator begin() const noexcept { + return _Tzdb_list.begin(); + } + + _NODISCARD const_iterator end() const noexcept { + return _Tzdb_list.end(); + } + + _NODISCARD const_iterator cbegin() const noexcept { + return _Tzdb_list.cbegin(); + } + + _NODISCARD const_iterator cend() const noexcept { + return _Tzdb_list.cend(); + } + template void _Emplace_front(_ArgsTy&&... _Args) { _Unique_lock _Lk(_Tzdb_mutex); @@ -2743,8 +2767,9 @@ namespace chrono { _Tzdb.links.begin(), _Tzdb.links.end(), _STD back_inserter(_Links), [](const auto& _Link) { return time_zone_link{_Link.name(), _Link.target()}; }); - _Tzdb_list.emplace_front( - tzdb{_Tzdb.version, _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); + auto _Version = _Xtzdb_update_version(_Tzdb.version, _Leap_sec.size()); + _Tzdb_list.emplace_front(tzdb{ + _STD move(_Version), _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); } return _Tzdb_list.front(); } @@ -2827,6 +2852,16 @@ namespace chrono { return _CHRONO get_tzdb_list().front(); } + // FUNCTION locate_zone + _NODISCARD inline const time_zone* locate_zone(string_view _Tz_name) { + return _CHRONO get_tzdb().locate_zone(_Tz_name); + } + + // FUNCTION current_zone + _NODISCARD inline const time_zone* current_zone() { + return _CHRONO get_tzdb().current_zone(); + } + // FUNCTION reload_tzdb inline const tzdb& reload_tzdb() { try { @@ -2836,6 +2871,14 @@ namespace chrono { } } + // FUNCTION remote_version + _NODISCARD inline string remote_version() { + const auto& _Tzdb = _CHRONO get_tzdb(); + const auto& _Version = _Tzdb.version; + const auto [_Leap_sec, _Ignored] = _Xtzdb_generate_leap_seconds(_Tzdb.leap_seconds.size()); + return _Leap_sec.empty() ? _Version : _Xtzdb_update_version(_Version, _Leap_sec.size()); + } + // [time.zone.zonedtraits] // STRUCT TEMPLATE zoned_traits diff --git a/stl/inc/xtzdb.h b/stl/inc/xtzdb.h index a0f1ffb4d8..73747054ce 100644 --- a/stl/inc/xtzdb.h +++ b/stl/inc/xtzdb.h @@ -78,7 +78,6 @@ void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* _Info) noexcept; __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( size_t _Prev_reg_ls_size, size_t* _Current_reg_ls_size) noexcept; - void __stdcall __std_tzdb_delete_reg_leap_seconds(__std_tzdb_registry_leap_info* _Rlsi) noexcept; _NODISCARD void* __stdcall __std_calloc_crt(size_t _Count, size_t _Size) noexcept; @@ -88,6 +87,37 @@ _END_EXTERN_C _STD_BEGIN +template +struct _Tzdb_deleter; + +template <> +struct _Tzdb_deleter<__std_tzdb_time_zones_info> { + void operator()(__std_tzdb_time_zones_info* _Info) const noexcept { + __std_tzdb_delete_time_zones(_Info); + } +}; + +template <> +struct _Tzdb_deleter<__std_tzdb_current_zone_info> { + void operator()(__std_tzdb_current_zone_info* _Info) const noexcept { + __std_tzdb_delete_current_zone(_Info); + } +}; + +template <> +struct _Tzdb_deleter<__std_tzdb_sys_info> { + void operator()(__std_tzdb_sys_info* _Info) const noexcept { + __std_tzdb_delete_sys_info(_Info); + } +}; + +template <> +struct _Tzdb_deleter<__std_tzdb_registry_leap_info[]> { + void operator()(__std_tzdb_registry_leap_info* _Info) const noexcept { + __std_tzdb_delete_reg_leap_seconds(_Info); + } +}; + template class _Crt_allocator { public: diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp index 0e13a44636..c68f6d61ff 100644 --- a/stl/src/tzdb.cpp +++ b/stl/src/tzdb.cpp @@ -182,6 +182,18 @@ namespace { return _Fun(en, resultLength, status); } + struct _UEnumeration_deleter { + void operator()(UEnumeration* _En) const noexcept { + __icu_uenum_close(_En); + } + }; + + struct _UCalendar_deleter { + void operator()(UCalendar* _Cal) const noexcept { + __icu_ucal_close(_Cal); + } + }; + _NODISCARD const char* _Allocate_wide_to_narrow( const char16_t* const _Input, const int _Input_len, __std_tzdb_error& _Err) noexcept { const auto _Code_page = __std_fs_code_page(); @@ -293,16 +305,16 @@ namespace { return _Get_icu_string_impl(_Icu_fn, 12, _Result_len, _Err); } - _NODISCARD _STD unique_ptr _Get_cal( + _NODISCARD _STD unique_ptr _Get_cal( const char* _Tz, const size_t _Tz_len, __std_tzdb_error& _Err) noexcept { const auto _Tz_name = _Allocate_narrow_to_wide(_Tz, static_cast(_Tz_len), _Err); if (_Tz_name == nullptr) { - return {nullptr, &__icu_ucal_close}; + return nullptr; } UErrorCode _UErr{U_ZERO_ERROR}; - _STD unique_ptr _Cal{ - __icu_ucal_open(_Tz_name.get(), -1, nullptr, UCalendarType::UCAL_DEFAULT, &_UErr), &__icu_ucal_close}; + _STD unique_ptr _Cal{ + __icu_ucal_open(_Tz_name.get(), -1, nullptr, UCalendarType::UCAL_DEFAULT, &_UErr)}; if (U_FAILURE(_UErr)) { _Err = __std_tzdb_error::_Icu_error; } @@ -330,8 +342,8 @@ _NODISCARD __std_tzdb_time_zones_info* __stdcall __std_tzdb_get_time_zones() noe // _Info == nullptr --> bad_alloc // _Info->_Err == _Win_error --> failed, call GetLastError() // _Info->_Err == _Icu_error --> runtime_error interacting with ICU - _STD unique_ptr<__std_tzdb_time_zones_info, decltype(&__std_tzdb_delete_time_zones)> _Info{ - new (_STD nothrow) __std_tzdb_time_zones_info{}, &__std_tzdb_delete_time_zones}; + _STD unique_ptr<__std_tzdb_time_zones_info, _STD _Tzdb_deleter<__std_tzdb_time_zones_info>> _Info{ + new (_STD nothrow) __std_tzdb_time_zones_info{}}; if (_Info == nullptr) { return nullptr; } @@ -346,9 +358,8 @@ _NODISCARD __std_tzdb_time_zones_info* __stdcall __std_tzdb_get_time_zones() noe return _Report_error(_Info, __std_tzdb_error::_Icu_error); } - _STD unique_ptr _Enum{ - __icu_ucal_openTimeZoneIDEnumeration(USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, nullptr, nullptr, &_UErr), - &__icu_uenum_close}; + _STD unique_ptr _Enum{ + __icu_ucal_openTimeZoneIDEnumeration(USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, nullptr, nullptr, &_UErr)}; if (U_FAILURE(_UErr)) { return _Report_error(_Info, __std_tzdb_error::_Icu_error); } @@ -429,8 +440,8 @@ _NODISCARD __std_tzdb_current_zone_info* __stdcall __std_tzdb_get_current_zone() // _Info == nullptr --> bad_alloc // _Info->_Err == _Win_error --> failed, call GetLastError() // _Info->_Err == _Icu_error --> runtime_error interacting with ICU - _STD unique_ptr<__std_tzdb_current_zone_info, decltype(&__std_tzdb_delete_current_zone)> _Info{ - new (_STD nothrow) __std_tzdb_current_zone_info{}, &__std_tzdb_delete_current_zone}; + _STD unique_ptr<__std_tzdb_current_zone_info, _STD _Tzdb_deleter<__std_tzdb_current_zone_info>> _Info{ + new (_STD nothrow) __std_tzdb_current_zone_info{}}; if (_Info == nullptr) { return nullptr; } @@ -466,8 +477,8 @@ _NODISCARD __std_tzdb_sys_info* __stdcall __std_tzdb_get_sys_info( // _Info == nullptr --> bad_alloc // _Info->_Err == _Win_error --> failed, call GetLastError() // _Info->_Err == _Icu_error --> runtime_error interacting with ICU - _STD unique_ptr<__std_tzdb_sys_info, decltype(&__std_tzdb_delete_sys_info)> _Info{ - new (_STD nothrow) __std_tzdb_sys_info{}, &__std_tzdb_delete_sys_info}; + _STD unique_ptr<__std_tzdb_sys_info, _STD _Tzdb_deleter<__std_tzdb_sys_info>> _Info{ + new (_STD nothrow) __std_tzdb_sys_info{}}; if (_Info == nullptr) { return nullptr; } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index f6423830a0..a2fa09ee3d 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -55,17 +56,52 @@ void try_locate_invalid_zone(const tzdb& my_tzdb, string_view name) { } } -void timezone_names_test() { - const auto& my_tzdb = get_tzdb(); +void timezone_tzdb_list_test() { + const auto& my_tzdb_list = get_tzdb_list(); + + // only one entry in the list unless leap seconds were to change + assert(&my_tzdb_list.front() == &get_tzdb()); + assert(&my_tzdb_list.front() == &reload_tzdb()); + assert(++my_tzdb_list.begin() == my_tzdb_list.end()); + assert(++my_tzdb_list.cbegin() == my_tzdb_list.cend()); +} +void timezone_version_test() { + const auto& my_tzdb = get_tzdb(); assert(my_tzdb.version.empty() == false); + // version should end in .X where X == number of leap seconds + const auto pos = my_tzdb.version.find_last_of('.'); + assert(pos != decltype(my_tzdb.version)::npos); + const string leap_seconds{my_tzdb.version, pos + 1}; + assert(leap_seconds.empty() == false); + assert(leap_seconds == to_string(my_tzdb.leap_seconds.size())); + + // remote version will only differ if leap seconds info changes, will not occur in tests + const auto& reloaded_tzdb = reload_tzdb(); + assert(reloaded_tzdb.version.empty() == false); + assert(&reloaded_tzdb == &my_tzdb); + + const auto& remote_ver = remote_version(); + assert(remote_ver.empty() == false); + assert(remote_ver == my_tzdb.version); +} + +void timezone_names_test() { + const auto& my_tzdb = get_tzdb(); + test_time_zone_and_link(my_tzdb, "Asia/Thimphu", "Asia/Thimbu"); test_time_zone_and_link(my_tzdb, "America/Tijuana", "America/Ensenada"); - const auto current_zone = my_tzdb.current_zone(); - assert(current_zone != nullptr); - assert(current_zone->name().empty() == false); + const auto curr_zone = current_zone(); + assert(curr_zone != nullptr); + assert(curr_zone->name().empty() == false); + assert(curr_zone == my_tzdb.current_zone()); + + const auto located_zone = locate_zone("UTC"); + assert(located_zone != nullptr); + assert(located_zone->name() == "Etc/UTC"); + assert(located_zone == my_tzdb.locate_zone("UTC")); try_locate_invalid_zone(my_tzdb, "Non/Existent"); @@ -371,6 +407,8 @@ void timezone_precision_test() { bool test() { try { + timezone_tzdb_list_test(); + timezone_version_test(); timezone_names_test(); timezone_sys_info_test(); timezone_to_local_test(); From 027fc9ffb0bf5d788ad82b216e516101495dcb90 Mon Sep 17 00:00:00 2001 From: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Date: Fri, 26 Mar 2021 01:11:08 -0700 Subject: [PATCH 06/22] [time.parse] (#1768) Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 1592 ++++++++++++++++- .../test.cpp | 1012 ++++++++++- 2 files changed, 2601 insertions(+), 3 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 02af136b01..351cfa5cbf 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -21,8 +21,11 @@ #include #include #include +#include #include +#include #include +#include #include #include #endif // _HAS_CXX20 @@ -3441,13 +3444,15 @@ namespace chrono { template concept _Convertible_to_utc_time = - is_same_v<_Clock, utc_clock> || requires(const time_point<_Clock, _Duration>& _Time) { + is_same_v<_Clock, + utc_clock> || is_same_v<_Clock, system_clock> || requires(const time_point<_Clock, _Duration>& _Time) { { _Clock::to_utc(_Time) } ->_Is_time_point; }; template - concept _Convertible_from_utc_time = is_same_v<_Clock, utc_clock> || requires(const utc_time<_Duration>& _Time) { + concept _Convertible_from_utc_time = + is_same_v<_Clock, utc_clock> || is_same_v<_Clock, system_clock> || requires(const utc_time<_Duration>& _Time) { { _Clock::from_utc(_Time) } ->_Is_time_point<_Clock>; }; @@ -3521,6 +3526,1589 @@ namespace chrono { } // clang-format on } + + // [time.parse] + struct _Time_parse_fields { + using _SubsecondType = duration; + + // These are the primary fields, used to set the chrono type being parsed. + optional _Subsecond; + optional _Second; + optional _Minute; + optional _Hour_24; + optional _Weekday; // 0-based, starts Sunday + optional _Day; // 1-based + optional _Month; // 1-based + optional _Day_of_year; // 1-based + optional _Two_dig_year; + optional _Century; + optional _Utc_offset; // in minutes + optional _Tz_name; + + // These are the secondary fields, used to store parsed data. They must be converted to primary fields and + // checked for consistency. + optional _Hour_12; + optional _Ampm; + optional _Iso_year; + optional _Two_dig_iso_year; + optional _Iso_week; // 1-based + optional _Week_u; // week number, W01 begins on first Sunday + optional _Week_w; // week number, W01 begins on first Monday + + enum _FieldFlags : unsigned int { + _F_sec = 0x01, + _F_min = 0x02, + _F_hr = 0x04, + _F_day = 0x08, + _F_wkday = 0x10, + _F_mon = 0x20, + _F_doy = 0x40, + _F_year = 0x80, + }; + + _NODISCARD unsigned int _Used_fields() const { + unsigned int _Ret{0}; + if (_Second || _Subsecond) { + _Ret |= _F_sec; + } + if (_Minute) { + _Ret |= _F_min; + } + if (_Hour_24) { + _Ret |= _F_hr; + } + if (_Day) { + _Ret |= _F_day; + } + if (_Weekday) { + _Ret |= _F_wkday; + } + if (_Month) { + _Ret |= _F_mon; + } + if (_Day_of_year) { + _Ret |= _F_doy; + } + if (_Two_dig_year && _Century) { + _Ret |= _F_year; + } + return _Ret; + } + + _NODISCARD static bool _Test_bits( + const unsigned int _Bits, const unsigned int _Must_set, const unsigned int _Optional = 0) { + return (_Bits & ~_Optional) == _Must_set; + } + + + template + static constexpr _Ty _Invalid_time_field{numeric_limits<_Ty>::lowest()}; + static constexpr const char* _Invalid_time_string = "!"; + + static void _Initialize_time_point(tm& _Tp) { + _Tp.tm_sec = _Invalid_time_field; + _Tp.tm_min = _Invalid_time_field; + _Tp.tm_hour = _Invalid_time_field; + _Tp.tm_wday = _Invalid_time_field; + _Tp.tm_mday = _Invalid_time_field; + _Tp.tm_mon = _Invalid_time_field; + _Tp.tm_yday = _Invalid_time_field; + _Tp.tm_year = _Invalid_time_field; + } + + template + _NODISCARD static bool _Update(optional<_Ty>& _Val, const _Ty& _New) { + // Update a field. Ignores invalid values. If _Val already has a value, returns true or false according to + // whether the new value matches the current one or not, so that inconsistencies can be detected. + + if constexpr (is_same_v<_Ty, string>) { + if (_New == _Invalid_time_string) { + return true; + } + } else { + if (_New == _Invalid_time_field<_Ty>) { + return true; + } + } + + if (!_Val.has_value()) { + _Val = _New; + return true; + } else { + return _STD exchange(_Val, _New) == _New; + } + } + + _NODISCARD static pair _Decompose_year(const int _Year) { + int _Two_d_year = _Year % 100; + if (_Two_d_year < 0) { + _Two_d_year += 100; + } + return {_Year - _Two_d_year, _Two_d_year}; + } + + _NODISCARD bool _Update_if_valid(const tm& _Tp, const bool _Full_year) { + bool _No_err{true}; + if (_Tp.tm_hour != _Invalid_time_field) { + _No_err = _No_err && _Update(_Hour_24, _Tp.tm_hour); + _No_err = _No_err && _Update(_Ampm, _Hour_24 >= 12 ? 1 : 0); + _No_err = _No_err && _Update(_Hour_12, _CHRONO make12(hours{*_Hour_24}).count()); + } + + _No_err = _No_err && _Update(_Minute, _Tp.tm_min); + _No_err = _No_err && _Update(_Second, _Tp.tm_sec); + _No_err = _No_err && _Update(_Day, _Tp.tm_mday); + _No_err = _No_err && _Update(_Weekday, _Tp.tm_wday); + + if (_Tp.tm_mon != _Invalid_time_field) { + _No_err = _No_err && _Update(_Month, _Tp.tm_mon + 1); + } + + if (_Tp.tm_yday != _Invalid_time_field) { + _No_err = _No_err && _Update(_Day_of_year, _Tp.tm_yday + 1); + } + + if (_Tp.tm_year != _Invalid_time_field) { + // Sometimes we expect only the last two digits. + const auto _Year_parts = _Decompose_year(_Tp.tm_year + 1900); + _No_err = _No_err && _Update(_Two_dig_year, _Year_parts.second); + if (_Full_year) { + _No_err = _No_err && _Update(_Century, _Year_parts.first); + } + } + + return _No_err; + } + + _NODISCARD bool _Yday_to_month_day(const int _Yday, const int _Year) { + // A day-of-year that's February 28 or earlier, by itself, is a valid month_day. Any later day is + // ambiguous without a year. + if (_Out_of_range(_Day_of_year, 1, _Two_dig_year || _Iso_year ? _Days_in_year(_Year) : 59)) { + return false; + } + + if (_Day_of_year <= 31) { + return _Update(_Day, _Yday) && _Update(_Month, 1); + } + + const int _Feb_end{_Is_leap(_Year) ? 60 : 59}; + if (_Day_of_year <= _Feb_end) { + return _Update(_Day, _Yday - 31) && _Update(_Month, 2); + } + + // Shift day-of-year so that 1 == March 1. This is the same as year_month_day::_Civil_from_days, except + // _Day_of_year --> _Shifted_yday-1 and _Mp --> _Month - 3. + const int _Shifted_yday{*_Day_of_year - _Feb_end}; + return _Update(_Month, (535 * _Shifted_yday + 48950) >> 14) + && _Update(_Day, _Shifted_yday - ((979 * *_Month - 2918) >> 5)); + } + + static constexpr int _Era_begin_wday{3}; // Wednesday + + _NODISCARD static constexpr int _Jan1_weekday(int _Year) { + --_Year; + const int _Era = (_Year >= 0 ? _Year : _Year - 399) / 400; + const int _Yoe = _Year - _Era * 400; + // Jan. 1 is always day 306 of the shifted [Mar, ..., Dec, Jan, Feb] year. + const int _Doe = ((1461 * _Yoe) >> 2) - _Yoe / 100 + 306; + return (_Doe + _Era_begin_wday) % 7; + } + + _NODISCARD static constexpr int _Iso8601_weeks(int _Year) { + const int _P_y = (_Year + _Year / 4 - _Year / 100 + _Year / 400) % 7; + --_Year; + const int _P_ym1 = (_Year + _Year / 4 - _Year / 100 + _Year / 400) % 7; + return 52 + (_P_y == 4 || _P_ym1 == 3); + } + + _NODISCARD static constexpr int _Iso8601_week(const int _Day_of_year, const int _Weekday, const int _Year) { + // Jan. 4 is always week 1; rollover to next week always happens on Monday. + const auto _Week{(_Day_of_year + 9 - _Prev_weekday(_Weekday, 1)) / 7}; + if (_Week < 1) { + return _Iso8601_weeks(_Year - 1); + } else if (_Week > _Iso8601_weeks(_Year)) { + return 1; + } else { + return _Week; + } + } + + _NODISCARD static constexpr bool _Is_leap(const int _Year) { + return _Year % 4 == 0 && (_Year % 100 != 0 || _Year % 400 == 0); + } + + _NODISCARD static constexpr int _Days_in_year(const int _Year) { + return _Is_leap(_Year) ? 366 : 365; + } + + _NODISCARD static constexpr int _Next_weekday(const int _Wday, const int _Shift) { + // 0 <= _Shift <= 6 + int _Result = _Wday + _Shift; + if (_Result >= 7) { + _Result -= 7; + } + return _Result; + } + + _NODISCARD static constexpr int _Prev_weekday(const int _Wday, const int _Shift) { + // 0 <= _Shift <= 6 + return (_Wday >= _Shift ? 0 : 7) + (_Wday - _Shift); + } + + _NODISCARD bool _Calculate_ymd_from_week_date(const int _Starting_wday, const int _Week, const int _Year) { + // (a) Calculate day-of-year of first _Starting_wday in January. + // (b) Shift *_Weekday so that it's relative to _Starting_wday. + // (c) Offset to desired week. + const int _Jan1_wday = _Jan1_weekday(_Year); + const int _Yday = 1 + _Prev_weekday(_Starting_wday, _Jan1_wday) // (a) + + _Prev_weekday(*_Weekday, _Starting_wday) // (b) + + 7 * (_Week - 1); // (c) + return _Update(_Day_of_year, _Yday) && !_Out_of_range(_Day_of_year, 1, _Days_in_year(_Year)) + && _Yday_to_month_day(*_Day_of_year, _Year); + } + + _NODISCARD bool _Calculate_ymd() { + bool _No_err = true; + // Flags to indicate if a field should be checked for consistency with other data. Set to false when the + // field is used to calculate the date, as it's necessarily self-consistent in that case (barring a bug). + bool _Check_u = true; + bool _Check_w = true; + bool _Check_iso = true; + bool _Check_wday = true; + + if (_Day_of_year && _Out_of_range(_Day_of_year, 1, 366)) { + return false; + } + + bool _Have_year = false; + int _Year{0}; + if (_Two_dig_year) { + _Year = *_Century + *_Two_dig_year; + _Have_year = true; + if (_Day_of_year) { + _No_err = _No_err && _Yday_to_month_day(*_Day_of_year, _Year); + } else if (_Week_u || _Week_w) { + _Check_wday = false; + if (_Week_u) { + _Check_u = false; + _No_err = _No_err && _Calculate_ymd_from_week_date(0 /*Sunday*/, *_Week_u, _Year); + } + + if (_Week_w) { + _Check_w = false; + _No_err = _No_err && _Calculate_ymd_from_week_date(1 /*Monday*/, *_Week_w, _Year); + } + } + } + + if (_Iso_year) { + // ISO weeks begin on Monday. W01 always contains January 4. There is no W00, so the beginning of + // January can be in W52 or W53 of the previous year. Likewise, the end of December can occur at the + // beginning of W01 of the following year, depending on where in the week Jan. 4 falls. + _Check_wday = false; + _Check_iso = false; + _Year = *_Iso_year; + _Have_year = true; + + // Shift weekdays to Monday-based. Jan. 4 is the anchor of week 1, so calculate where the parsed weekday + // is relative to that point. + const int _Jan4_wday = _Next_weekday(_Jan1_weekday(_Year), 3 - 1); + const int _Offset_from_jan4 = _Prev_weekday(*_Weekday, 1) - _Jan4_wday; + int _Trial_yday = 4 + 7 * (*_Iso_week - 1) + _Offset_from_jan4; + const int _Ref_num_days = _Trial_yday < 1 ? _Days_in_year(_Year - 1) : _Days_in_year(_Year); + if (_Trial_yday < 1) { + _Trial_yday += _Ref_num_days; + --_Year; + } else if (_Trial_yday > _Ref_num_days) { + _Trial_yday -= _Ref_num_days; + ++_Year; + } + + const auto _Year_parts = _Decompose_year(_Year); + _No_err = _No_err && _Update(_Day_of_year, _Trial_yday) && _Yday_to_month_day(*_Day_of_year, _Year) + && _Update(_Century, _Year_parts.first) && _Update(_Two_dig_year, _Year_parts.second); + } + + // Must have YMD by this point, either parsed directly or calculated above. + if (!_Have_year || !_Month || !_Day || !_No_err) { + return false; + } + + // consistency checks + if (_Check_wday && _Weekday) { + const auto _Era_year = _Year - (*_Month <= 2); + const int _Era = (_Era_year >= 0 ? _Era_year : _Era_year - 399) / 400; + const int _Yoe = _Era_year - _Era * 400; + const int _Yday_era = ((979 * (*_Month + (*_Month > 2 ? -3 : 9)) + 19) >> 5) + *_Day - 1; + const int _Doe = ((1461 * _Yoe) >> 2) - _Yoe / 100 + _Yday_era; + _No_err = _No_err && _Update(_Weekday, (_Doe + _Era_begin_wday) % 7); + } + + if (_Check_u && _Week_u) { + _No_err = _No_err && _Update(_Week_u, (*_Day_of_year + 6 - *_Weekday) / 7); + } + + if (_Check_w && _Week_w) { + _No_err = _No_err && _Update(_Week_w, (*_Day_of_year + 6 - _Prev_weekday(*_Weekday, 1)) / 7); + } + + if (_Check_iso && _Iso_week) { + _No_err = _No_err && _Update(_Iso_week, _Iso8601_week(*_Day_of_year, *_Weekday, _Year)); + } + + return _No_err; + } + + _NODISCARD bool _Calculate_hour24() { + if (_Hour_12) { + return _Update(_Hour_24, _CHRONO make24(hours{*_Hour_12}, _Ampm.value_or(0)).count()); + } else { + return true; + } + } + + _NODISCARD bool _Calculate_year_fields() { + bool _No_err = true; + // The order of these updates is significant. Updating the ISO date second allows + // formats with %g and %y, but not %C, to get the century implicitly from %y. + if (_Two_dig_year && !_Century) { + _No_err = _No_err && _Update(_Century, _Two_dig_year >= 69 ? 1900 : 2000); + } + + // %C is only combined with %g if %G is missing, to avoid an unnecessary parse failure when the ISO and + // Gregorian years are in different centuries. + if (_Two_dig_iso_year && _Century && !_Iso_year) { + _No_err = _No_err && _Update(_Iso_year, *_Century + *_Two_dig_iso_year); + } + + return _No_err; + } + + _NODISCARD static bool _Out_of_range(const optional& _Field, const int _Min, const int _Max) { + return _Field && (_Field < _Min || _Max < _Field); + } + + _NODISCARD bool _Is_complete() const { + // Check for data that is incomplete, ambiguous, or obviously out-of-range. The exception is 12-hour time + // without am/pm. Most strptime implementations will assume am in this case, so we'll do that too. Don't + // check for consistency yet, because the data might not even be representable by the type being parsed, + // and calendar computations are relatively expensive. + + // Most time-of-day fields are deferred until we know if we're parsing a time_point. + if (_Out_of_range(_Hour_12, 1, 12) // + || _Out_of_range(_Weekday, 0, 6) // + || _Out_of_range(_Day, 1, 31) // + || _Out_of_range(_Month, 1, 12)) { + return false; + } + + if (_Iso_year || _Two_dig_iso_year || _Iso_week) { + // Need to have %G or %C+%g. The century can be parsed explicitly, or derived implicitly from %y. + const bool _Has_complete_year{_Iso_year || ((_Century || _Two_dig_year) && _Two_dig_iso_year)}; + if (!_Has_complete_year || !_Iso_week || !_Weekday || _Out_of_range(_Iso_week, 1, 53)) { + return false; + } + } + + if (_Week_u || _Week_w) { + // Need a weekday and year to be complete. + if (!_Weekday || !_Two_dig_year) { + return false; + } + + if (_Out_of_range(_Week_u, 0, 53) || _Out_of_range(_Week_w, 0, 53)) { + return false; + } + } + + return true; + } + + template + _NODISCARD static constexpr bool _Can_represent() { + using _Rep1 = typename _Duration1::rep; + using _Period1 = typename _Duration1::period; + using _Period2 = typename _Duration2::period; + + // Returns whether _Duration1 can represent _Duration2{1}. Assumes 1 <= _Period2 <= + // 86,400, i.e., we're interested in time periods between seconds and days. + if constexpr (is_integral_v<_Rep1>) { + // Must have _Period1 <= _Period2 and numeric_limits<_Rep1>::max() >= _Period2 / _Period1. For example, + // std::days can't represent std::seconds, and duration::max() is ~9.2 seconds. + constexpr auto _Max_tick = static_cast((numeric_limits>::max)()); + + // clang-format off + return ratio_less_equal_v<_Period1, _Period2> + && ratio_greater_equal_v, ratio_divide, _Period1>>; + // clang-format on + } else if (is_floating_point_v<_Rep1>) { + // With the smallest possible _Period1, ratio<1,INTMAX_MAX>, one day has a tick count of + // 86,400*INTMAX_MAX ~= 7.97e23. This is representable by float and double, so they can always represent + // at least one day. On the other hand, one second with the largest possible _Period1 needs a tick count + // of 1/(INTMAX_MAX) ~= 1.08e-19, which is also representable in both float and double. So, both + // floating point types can represent durations between one second and one day, regardless of _Period1. + return true; + } else { + // TRANSITION: user-defined arithmetic-like types + return true; + } + } + + template + _NODISCARD bool _Apply_duration_fields(_DurationType& _Result, const bool _For_time_point) { + constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); + constexpr bool _Can_rep_min = _Can_represent<_DurationType, minutes>(); + constexpr bool _Can_rep_hr = _Can_represent<_DurationType, hours>(); + constexpr bool _Can_rep_day = _Can_represent<_DurationType, days>(); + + const auto _Required{(_For_time_point ? _F_day | _F_mon | _F_year : 0)}; + const auto _Optional{(_For_time_point ? _F_wkday : 0) | (_Can_rep_sec ? _F_sec : 0) + | (_Can_rep_min ? _F_min : 0) | (_Can_rep_hr ? _F_hr : 0) + | (_Can_rep_day ? _F_doy : 0)}; + + const auto _Used{_Used_fields()}; + const auto _Time_out_of_range{ + _For_time_point + && (_Out_of_range(_Second, 0, 60) || _Out_of_range(_Minute, 0, 59) || _Out_of_range(_Hour_24, 0, 23))}; + + if (_Time_out_of_range || !_Test_bits(_Used, _Required, _Optional)) { + return false; + } + + _Result = _DurationType::zero(); + if constexpr (_Can_rep_sec) { + if (_Used & _F_sec) { + if (_Subsecond) { + using _CastedType = duration; + const _SubsecondType _Sub{*_Subsecond}; + if (treat_as_floating_point_v<_DurationType> // + || _CHRONO duration_cast<_CastedType>(_Sub) == _Sub) { + _Result += _CHRONO duration_cast<_DurationType>(_Sub); + } else { + return false; + } + } + + if (_Second) { + _Result += _CHRONO duration_cast<_DurationType>(seconds{*_Second}); + } + } + } + + if constexpr (_Can_rep_min) { + if (_Used & _F_min) { + _Result += _CHRONO duration_cast<_DurationType>(minutes{*_Minute}); + } + + if (_Utc_offset) { + _Result -= _CHRONO duration_cast<_DurationType>(minutes{*_Utc_offset}); + } + } + + if constexpr (_Can_rep_hr) { + if (_Used & _F_hr) { + _Result += _CHRONO duration_cast<_DurationType>(hours{*_Hour_24}); + } + } + + if constexpr (_Can_rep_day) { + if (_For_time_point) { + year_month_day _Ymd; + if (_Make_year_month_day(_Ymd, true)) { + _Result += _CHRONO duration_cast<_DurationType>(static_cast(_Ymd).time_since_epoch()); + } else { + return false; + } + } else if (_Used & _F_doy) { + _Result += _CHRONO duration_cast<_DurationType>(days{*_Day_of_year}); + } + } + + return true; + } + + template + _NODISCARD bool _Make_duration(duration<_Rep, _Period>& _Duration_result) { + const bool _Consistent = _Calculate_hour24(); + if (_Consistent) { + _Duration_result = duration<_Rep, _Period>::zero(); // TRANSITION: LWG-3536 & GH-1740 + } + + return _Consistent && _Apply_duration_fields(_Duration_result, false); + } + + enum class _LeapSecondRep : unsigned int { + _None, // tai_clock, gps_clock; oblivious to leap seconds + _Negative, // system_clock, observes negative, but not positive, leap seconds + _All, // utc_clock + _File_time // _Negative before 1 January 2017, _All afterwards. + }; + + template + _NODISCARD bool _Make_time_point(_DurationType& _Dur, _LeapSecondRep _Leap) { + + const bool _Consistent{_Calculate_hour24() && _Calculate_year_fields()}; + if (!_Consistent || !_Apply_duration_fields(_Dur, true)) { + return false; + } + + const _LeapSecondRep _Original_leap{_Leap}; + if (_Leap == _LeapSecondRep::_File_time) { + const int _Year{*_Century + *_Two_dig_year}; + _Leap = _Year <= 2016 ? _LeapSecondRep::_Negative : _LeapSecondRep::_All; + } + + if (_Second > (_Leap == _LeapSecondRep::_All ? 60 : 59)) { + return false; + } + + // A distinction has to be made here between clocks that tick monotonically, even around a positive leap + // second (everything except system_clock) and ones that have a special representation for leap seconds + // (utc_clock and, for negative leap seconds, system_clock). gps_clock and tai_clock, in particular, always + // monotonically count 86,400 seconds/day. The correct tick count, therefore, can be determined without + // knowing whether any leap seconds have occurred, and there aren't any invalid times due to negative leap + // second deletions. + // + // system_clock also has 86,400 seconds/day, but observes negative leap seconds by skipping them in its tick + // count. So leap second data is needed to check for a valid time, but not to calculate the tick count. + + if (_Leap != _LeapSecondRep::_None) { + if (_Hour_24 == 23 && _Minute == 59 && _Second >= 59) { + // It's possible that the parsed time doesn't exist because (a) _Seconds == 60 and there *isn't* a + // leap second insertion or (b) _Seconds == 59 and there *is* a leap second subtraction. + + // Check for quick exit. + const auto& _Tzdb{_CHRONO get_tzdb()}; + if (_Leap == _LeapSecondRep::_Negative && _Tzdb._All_ls_positive) { + return true; + } + + const bool _Possible_insertion{_Second == 60}; + const sys_seconds _Target_sys_time{ + _CHRONO floor(_Dur) + (_Possible_insertion ? seconds{0} : seconds{1})}; + const auto& _Ls_vector{_Tzdb.leap_seconds}; + const auto _It{_STD lower_bound(_Ls_vector.begin(), _Ls_vector.end(), _Target_sys_time)}; + const bool _Match_leap{_It != _Ls_vector.end() && *_It == _Target_sys_time}; + + // Condition for a good parse: (_Seconds == 59 && !_Match_leap) || (_Match_leap && + // _It->_Is_positive()). Below is the inverse of this. + if (!(_Match_leap ? _It->_Positive() : !_Possible_insertion)) { + return false; + } + } + + if (_Leap == _LeapSecondRep::_All) { + constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); + if constexpr (_Can_rep_sec) { + _Dur = utc_clock::from_sys(sys_time<_DurationType>{_Dur}).time_since_epoch(); + // If we parsed a positive leap second, then _Dur, interpreted as a system time, refers to the + // second *after* insertion. Need to back up one second in this case. + if (_Second == 60) { + _Dur -= _CHRONO duration_cast<_DurationType>(seconds{1}); + } + + if (_Original_leap == _LeapSecondRep::_File_time) { + _Dur -= _CHRONO duration_cast<_DurationType>( + filesystem::_File_time_clock::_Skipped_filetime_leap_seconds); + } + } else { + // Error if _Dur can't represent the above adjustment. + return false; + } + } + } + + return true; + } + + _NODISCARD bool _Make_day(day& _Day_result) { + if (_Used_fields() == _F_day) { + _Day_result = day{static_cast(*_Day)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_weekday(weekday& _Weekday_result) { + if (_Used_fields() == _F_wkday) { + _Weekday_result = weekday{static_cast(*_Weekday)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_month(month& _Month_result) { + if (_Used_fields() == _F_mon) { + _Month_result = month{static_cast(*_Month)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_month_day(month_day& _Month_day_result) { + if (_Day_of_year && !_Yday_to_month_day(*_Day_of_year, 0)) { + return false; + } + + constexpr auto _Required = _F_mon | _F_day; + constexpr auto _Optional = _F_doy; + if (_Test_bits(_Used_fields(), _Required, _Optional)) { + _Month_day_result = + month_day{month{static_cast(*_Month)}, day{static_cast(*_Day)}}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year(year& _Year_result) { + if (_Calculate_year_fields() && _Used_fields() == _F_year) { + _Year_result = year{*_Century + *_Two_dig_year}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year_month(year_month& _Year_month_result) { + if (_Calculate_year_fields() && _Used_fields() == (_F_mon | _F_year)) { + _Year_month_result = + year_month{year{*_Century + *_Two_dig_year}, month{static_cast(*_Month)}}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year_month_day( + year_month_day& _Year_month_day_result, const bool _For_time_point = false) { + const bool _Consistent = _Calculate_year_fields() && _Calculate_ymd(); + constexpr auto _Required = _F_day | _F_mon | _F_year; + auto _Optional = _F_wkday | _F_doy; + if (_For_time_point) { + // These fields aren't used here, but they might be used later if converting to a time_point. + _Optional |= _F_sec | _F_min | _F_hr; + } + + if (_Consistent && _Test_bits(_Used_fields(), _Required, _Optional)) { + _Year_month_day_result = year_month_day{year{*_Century + *_Two_dig_year}, + month{static_cast(*_Month)}, day{static_cast(*_Day)}}; + return true; + } else { + return false; + } + } + + template + _NODISCARD _InIt _Parse_time_field(_InIt _First, ios_base& _Iosbase, ios_base::iostate& _State, + const char _Flag, const char _Modifier, const unsigned int _Width, + const unsigned int _Subsecond_precision) { + using _CharT = typename _InIt::value_type; + + const auto& _Ctype_fac = _STD use_facet>(_Iosbase.getloc()); + const auto& _Time_fac = _STD use_facet>(_Iosbase.getloc()); + constexpr _InIt _Last{}; + + int _Val{0}; + switch (_Flag) { + case 'a': + case 'A': + { + tm _Tp; + _Tp.tm_wday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'a'); + if (!_Update(_Weekday, _Tp.tm_wday)) { + _State |= ios_base::failbit; + } + break; + } + + case 'b': + case 'B': + case 'h': + { + tm _Tp; + _Tp.tm_mon = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'b'); + if (_Tp.tm_mon == _Invalid_time_field || !_Update(_Month, ++_Tp.tm_mon)) { + _State |= ios_base::failbit; + } + break; + } + + case 'C': + if (_Modifier != 'E') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + _Val *= 100; + } else { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'C', 'E'); + _Val = _Tp.tm_year; + if (_Tp.tm_year != _Invalid_time_field) { + _Val += 1900; + } + } + + if (!_Update(_Century, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'd': + case 'e': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_mday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'd', 'O'); + _Val = _Tp.tm_mday; + } + + if (!_Update(_Day, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'D': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%m/%d/%y"); + break; + + case 'F': + { + // If modified with a width N, the width is applied to only %Y. + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + const auto _Year_parts = _Decompose_year(_Val); + if (_Update(_Century, _Year_parts.first) && _Update(_Two_dig_year, _Year_parts.second)) { + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "-%m-%d"); + } else { + _State |= ios_base::failbit; + } + break; + } + + case 'g': + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + if (!_Update(_Two_dig_iso_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'G': + { + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + const auto _Year_parts = _Decompose_year(_Val); + if (!_Update(_Iso_year, _Val) || !_Update(_Two_dig_iso_year, _Year_parts.second)) { + _State |= ios_base::failbit; + } + break; + } + + case 'H': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_hour = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'H', 'O'); + _Val = _Tp.tm_hour; + } + + if (!_Update(_Hour_24, _Val) + || (_Val < 24 + && (!_Update(_Ampm, _Val >= 12 ? 1 : 0) + || !_Update(_Hour_12, _CHRONO make12(hours{_Val}).count())))) { + _State |= ios_base::failbit; + } + break; + + case 'I': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_hour = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'I', 'O'); + _Val = (_Tp.tm_hour == 0) ? 12 : _Tp.tm_hour; + } + + if (!_Update(_Hour_12, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'j': + _State |= _Get_int(_First, _Width == 0 ? 3u : _Width, _Val, _Ctype_fac); + if (!_Update(_Day_of_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'M': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_min = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'M', 'O'); + _Val = _Tp.tm_min; + } + + if (!_Update(_Minute, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'm': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Initialize_time_point(_Tp); + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'm', 'O'); + _Val = _Tp.tm_mon; + if (_Tp.tm_mon != _Invalid_time_field) { + ++_Val; + } + } + + if (!_Update(_Month, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'p': + { + tm _Tp; + _Tp.tm_hour = 0; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'p'); + if (!_Update(_Ampm, _Tp.tm_hour == 0 ? 0 : 1)) { + _State |= ios_base::failbit; + } + break; + } + + case 'c': + case 'r': + case 'x': + case 'X': + { + tm _Tp; + _Initialize_time_point(_Tp); + const bool _Full_year = (_Flag == 'c'); // 'x' reads two-digit year, 'r' and 'X' read times + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, _Flag, _Modifier); + if (!_Update_if_valid(_Tp, _Full_year)) { + _State |= ios_base::failbit; + } + break; + } + + case 'R': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%H:%M"); + break; + + case 'T': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%H:%M:%S", _Subsecond_precision); + break; + + case 'S': + if (_Subsecond_precision == 0) { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + if (!_Update(_Second, _Val)) { + _State |= ios_base::failbit; + } + } else { + const auto& _Numpunct_fac = _STD use_facet>(_Iosbase.getloc()); + _State |= + _Get_fixed(_First, _Width == 0 ? 3 + _Subsecond_precision : _Width, _Ctype_fac, _Numpunct_fac); + } + break; + + case 'u': + case 'w': + if (_Flag == 'w' && _Modifier == 'O') { + tm _Tp; + _Tp.tm_wday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'w', 'O'); + _Val = _Tp.tm_wday; + } else { + _State |= _Get_int(_First, _Width == 0 ? 1u : _Width, _Val, _Ctype_fac); + if (_Flag == 'u') { + // ISO weekday: [1,7], 7 == Sunday + if (_Val == 7) { + _Val = 0; + } else if (_Val == 0) { + _Val = 7; // out of range + } + } + } + + if (!_Update(_Weekday, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'U': + case 'V': + case 'W': + { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + auto& _Field{(_Flag == 'U') ? _Week_u : (_Flag == 'W' ? _Week_w : _Iso_week)}; + if (!_Update(_Field, _Val)) { + _State |= ios_base::failbit; + } + break; + } + + case 'y': + if (_Modifier == '\0') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'y', _Modifier); + if (_Modifier == 'E') { + _Val = _Tp.tm_year + 1900; // offset from %EC base year + } else { + const auto _Year_parts = _Decompose_year(_Tp.tm_year); + _Val = _Year_parts.second; + } + } + + if (!_Update(_Two_dig_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'Y': + { + if (_Modifier == 'E') { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'Y', 'E'); + _Val = _Tp.tm_year + 1900; + } else { + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + } + + const auto _Year_parts = _Decompose_year(_Val); + if (!_Update(_Century, _Year_parts.first) || !_Update(_Two_dig_year, _Year_parts.second)) { + _State |= ios_base::failbit; + } + break; + } + + case 'z': + _State |= _Get_tz_offset(_First, _Ctype_fac, _Modifier == 'E' || _Modifier == 'O', _Val); + if (!_Update(_Utc_offset, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'Z': + { + string _Name; + _State |= _Get_tz_name(_First, _Ctype_fac, _Name); + if (!_Update(_Tz_name, _Name)) { + _State |= ios_base::failbit; + } + break; + } + + default: + // Invalid flag + _State |= ios_base::failbit; + break; + } + + return _First; + } + + template + _NODISCARD _InIt _Parse_time_field_restricted(_InIt _First, ios_base& _Iosbase, ios_base::iostate& _State, + const char* _Fmt, const unsigned int _Subsecond_precision = 0) { + using _Ctype = ctype; + // Parses a restricted format string. It generally doesn't handle anything parsed outside of + // _Parse_time_field: + // (a) any whitespace (' ', %n, %t) + // (b) %% literal (other literals are parsed, however) + // (c) E or O modifiers + // (d) width parameter + // It also assumes a valid format string, specifically that '%' is always followed by a flag. + const _Ctype& _Ctype_fac{_STD use_facet<_Ctype>(_Iosbase.getloc())}; + constexpr _InIt _Last{}; + + while (*_Fmt != '\0' && _First != _Last && _State == ios_base::goodbit) { + if (*_Fmt == '%') { + _First = _Parse_time_field(_First, _Iosbase, _State, *++_Fmt, '\0', 0, _Subsecond_precision); + } else if (_Ctype_fac.narrow(*_First++) != *_Fmt) { + _State |= ios_base::failbit; + } + ++_Fmt; + } + return _First; + } + + template + _NODISCARD ios_base::iostate _Get_fixed(_InIt& _First, unsigned int _Width, + const ctype& _Ctype_fac, + const numpunct& _Numpunct_fac) { + constexpr _InIt _Last{}; + + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First) && _Width > 0) { + ++_First; + --_Width; + } + + int _Second_ = 0; + int64_t _Subsecond_ = 0; + int64_t _Multiplier = _SubsecondType::period::den; + bool _Found_point = false; + bool _Found_digit = false; + + while (_First != _Last && _Width > 0 && _Multiplier >= 10) { + const auto _Ch = *_First; + if (_Ch == _Numpunct_fac.decimal_point() && !_Found_point) { + _Found_point = true; + } else if (_Ctype_fac.is(ctype_base::digit, _Ch)) { + _Found_digit = true; + const auto _Digit = _Ctype_fac.narrow(_Ch) - '0'; + if (_Found_point) { + _Multiplier /= 10; + _Subsecond_ += _Digit * _Multiplier; + } else { + if (_Second_ > ((numeric_limits::max)() - _Digit) / 10) { + return ios_base::failbit; + } + + _Second_ = _Second_ * 10 + _Digit; + } + } else { + break; + } + ++_First; + --_Width; + } + + ios_base::iostate _State = ios_base::goodbit; + if (_First == _Last) { + _State |= ios_base::eofbit; + } + + if (!(_Found_digit && _Update(_Second, _Second_) && _Update(_Subsecond, _Subsecond_))) { + _State |= ios_base::failbit; + } + + return _State; + } + + template + _NODISCARD ios_base::iostate _Get_int( + _InIt& _First, unsigned int _Width, int& _Val, const ctype& _Ctype_fac) { + constexpr _InIt _Last{}; + + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First) && _Width > 0) { + ++_First; + --_Width; + } + + char _Ac[_MAX_INT_DIG]; + char* _Ptr = _Ac; + if (_First != _Last && _Width > 0) { + const char _Ch = _Ctype_fac.narrow(*_First); + if (_Ch == '+' || _Ch == '-') { // copy sign + *_Ptr++ = _Ch; + ++_First; + --_Width; + } + } + + bool _Has_leading_zero = false; + while (_First != _Last && _Width > 0 && _Ctype_fac.narrow(*_First) == '0') { // strip leading zeros + ++_First; + --_Width; + _Has_leading_zero = true; + } + + if (_Has_leading_zero) { + *_Ptr++ = '0'; + } + + char _Ch; + while (_First != _Last && _Width > 0 && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) + && _Ch <= '9') { // copy digits + *_Ptr = _Ch; + if (_Ptr < _STD cend(_Ac)) { + ++_Ptr; // drop trailing digits if already too large + } + ++_First; + --_Width; + } + + *_Ptr = '\0'; + int _Errno = 0; + char* _Ep; + const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno); + ios_base::iostate _State = ios_base::goodbit; + + if (_First == _Last) { + _State |= ios_base::eofbit; + } + + if (_Ep == _Ac || _Errno != 0) { + _State |= ios_base::failbit; // bad conversion + } else { + _Val = _Ans; // store valid result + } + + return _State; + } + + template + _NODISCARD ios_base::iostate _Get_tz_offset( + _InIt& _First, const ctype& _Ctype_fac, const bool _Is_modified, int& _Offset) { + constexpr _InIt _Last{}; + if (_First == _Last) { + return ios_base::eofbit; + } + + bool _Negative = false; + switch (_Ctype_fac.narrow(*_First)) { + case '-': + _Negative = true; + [[fallthrough]]; + case '+': + ++_First; + break; + } + + // For a regular offset hh[mm], simply read four digits, with the option of an EOF or non-digit after + // reading two. The modified form h[h][:mm] is similar, except for the following points: + // (a) an EOF or non-digit is allowable after reading *one* digit, not two. + // (b) after reading one digit, another optional digit keeps us in the same state, except for decrementing + // the number of optional digits allowed. In this state, reading a ':' allows parsing to continue. + + int _Tz_hours = 0; + int _Tz_minutes = 0; + int _Optional_digits = 1; + for (int _Count = 0; _Count < 4; ++_Count) { + const bool _Allow_match_fail{_Count == (_Is_modified ? 1 : 2)}; + + if (_First == _Last) { + if (_Allow_match_fail) { + break; + } else { + return ios_base::eofbit | ios_base::failbit; + } + } + + const char _Ch = _Ctype_fac.narrow(*_First++); + const bool _Is_digit = ('0' <= _Ch && _Ch <= '9'); + if (_Is_modified && _Count == 1) { + if (_Ch == ':') { + continue; + } else if (_Is_digit && _Optional_digits > 0) { + _Tz_hours = 10 * _Tz_hours + (_Ch - '0'); + --_Optional_digits; + --_Count; + } else { + if (_Allow_match_fail) { + break; + } else { + return ios_base::failbit; + } + } + } else if (_Is_digit) { + auto& _Part = _Count < 2 ? _Tz_hours : _Tz_minutes; + _Part = 10 * _Part + (_Ch - '0'); + } else { + if (_Allow_match_fail) { + break; + } else { + return ios_base::failbit; + } + } + } + + _Offset = 60 * _Tz_hours + _Tz_minutes; + if (_Negative) { + _Offset = -_Offset; + } + return ios_base::goodbit; + } + + template + _NODISCARD ios_base::iostate _Get_tz_name( + _InIt& _First, const ctype& _Ctype_fac, string& _Tz_name) { + constexpr _InIt _Last{}; + _Tz_name.clear(); + while (_First != _Last) { + const char _Ch{_Ctype_fac.narrow(*_First)}; + if (_STD isalnum(static_cast(_Ch)) || _Ch == '_' || _Ch == '/' || _Ch == '-' + || _Ch == '+') { + _Tz_name.push_back(_Ch); + ++_First; + } else { + break; + } + } + return _First == _Last ? ios_base::eofbit : ios_base::goodbit; + } + + template > + _Time_parse_fields(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _FmtFirst, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr, + unsigned int _Subsecond_precision = 0) { + + using _Myis = basic_istream<_CharT, _Traits>; + + const auto& _Ctype_fac = _STD use_facet>(_Istr.getloc()); + ios_base::iostate _State = ios_base::goodbit; + const _CharT* const _FmtLast = _FmtFirst + _Traits::length(_FmtFirst); + const typename _Myis::sentry _Ok{_Istr, true}; + + istreambuf_iterator _First{_Istr}; + constexpr decltype(_First) _Last{}; + + if (_Ok) { + _TRY_IO_BEGIN + for (; _FmtFirst != _FmtLast && _State == ios_base::goodbit; ++_FmtFirst) { + if (_First == _Last) { + // EOF is not an error if the remaining flags can match zero characters. + for (; _FmtFirst != _FmtLast; ++_FmtFirst) { + char _Flag{}; + if (_Ctype_fac.is(ctype_base::space, *_FmtFirst)) { + _Flag = ' '; + } else { + if (_Ctype_fac.narrow(*_FmtFirst) == '%' && ++_FmtFirst != _FmtLast) { + _Flag = _Ctype_fac.narrow(*_FmtFirst); + } + } + + if (_Flag != ' ' && _Flag != 't') { + _State |= ios_base::failbit | ios_base::eofbit; + break; + } + } + break; + } else if (_Ctype_fac.narrow(*_FmtFirst) != '%') { // match literal element + if (_Ctype_fac.is(ctype_base::space, *_FmtFirst)) { + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First)) { + ++_First; + } + } else if (*_First == *_FmtFirst) { + ++_First; + } else { + _State |= ios_base::failbit; // bad literal match + } + } else if (++_FmtFirst == _FmtLast) { // not enough for a valid flag + _State |= ios_base::failbit; + } else { // get flag after % + char _Flag = _Ctype_fac.narrow(*_FmtFirst); + char _Modifier = '\0'; + unsigned int _Width = 0; + + if (_Flag == 'E' || _Flag == 'O') { + if (++_FmtFirst == _FmtLast) { + _State |= ios_base::failbit; + break; + } + _Modifier = _STD exchange(_Flag, _Ctype_fac.narrow(*_FmtFirst)); + } else if ('0' <= _Flag && _Flag <= '9') { + _Width = static_cast(_Flag - '0'); + while (++_FmtFirst != _FmtLast && _Ctype_fac.is(ctype_base::digit, *_FmtFirst)) { + const auto _Digit = static_cast(_Ctype_fac.narrow(*_FmtFirst) - '0'); + if (_Width > ((numeric_limits::max)() - _Digit) / 10) { + _State |= ios_base::failbit; + break; + } + _Width = 10 * _Width + _Digit; + } + if (_FmtFirst == _FmtLast) { + _State |= ios_base::failbit; + break; + } + _Flag = _Ctype_fac.narrow(*_FmtFirst); + } + + switch (_Flag) { + case 'n': // exactly one space + if (!_Ctype_fac.is(ctype_base::space, *_First++)) { + _State |= ios_base::failbit; + } + break; + + case 't': // zero or one space + if (_Ctype_fac.is(ctype_base::space, *_First)) { + ++_First; + } + break; + + case '%': + if (_Ctype_fac.narrow(*_First++) != '%') { + _State |= ios_base::failbit; + } + break; + + default: + _First = _Parse_time_field(_First, _Istr, _State, _Flag, _Modifier, _Width, + _Subsecond_precision); // convert a single field + break; + } + } + } + + _CATCH_IO_(_Myis, _Istr) + } + + if (!_Is_complete()) { + _State |= ios_base::failbit; + } + + if (!(_State & ios_base::failbit)) { + if (_Offset != nullptr && _Utc_offset) { + *_Offset = minutes{*_Utc_offset}; + } + + if (_Abbrev != nullptr && _Tz_name) { + if constexpr (is_same_v) { + *_Abbrev = _STD move(*_Tz_name); + } else { + _Abbrev->clear(); + for (const char& _Ch : *_Tz_name) { + _Abbrev->push_back(_Ctype_fac.widen(_Ch)); + } + } + } + } + + _Istr.setstate(_State); + } + }; + + // FUNCTION TEMPLATE from_stream + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + duration<_Rep, _Period>& _Duration, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset, hh_mm_ss>::fractional_width}; + if (_Istr && !_Time._Make_duration(_Duration)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, weekday& _Wd, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_weekday(_Wd)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, day& _Day, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_day(_Day)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + month& _Month, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_month(_Month)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + month_day& _Md, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_month_day(_Md)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, year& _Year, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year(_Year)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + year_month& _Ym, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year_month(_Ym)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + year_month_day& _Ymd, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year_month_day(_Ymd)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + utc_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_All)) { + _Tp = utc_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + sys_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_Negative)) { + _Tp = sys_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + tai_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = tai_time<_Duration>{_Dur + _CHRONO duration_cast<_Duration>(days{4383})}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + gps_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + chrono::minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = gps_time<_Duration>{_Dur - _CHRONO duration_cast<_Duration>(days{3657})}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + file_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + chrono::minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_File_time)) { + constexpr auto _File_clock_adj{_CHRONO duration_cast<_Duration>( + filesystem::_File_time_clock::duration{filesystem::__std_fs_file_time_epoch_adjustment})}; + _Tp = file_time<_Duration>{_Dur} + _File_clock_adj; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + local_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = local_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template + struct _Time_parse_iomanip { + _Time_parse_iomanip(const basic_string<_CharT, _Traits, _Alloc>& _Fmt_, _Parsable& _Tp_, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev_ = nullptr, minutes* _Offset_ = nullptr) + : _Fmt{_Fmt_}, _Tp{_Tp_}, _Abbrev{_Abbrev_}, _Offset{_Offset_} {} + + const basic_string<_CharT, _Traits, _Alloc>& _Fmt; + _Parsable& _Tp; + basic_string<_CharT, _Traits, _Alloc>* _Abbrev; + minutes* _Offset; + }; + + // FUNCTION TEMPLATE parse + template &>(), + _STD declval(), _STD declval<_Parsable&>()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp) { + return _Time_parse_iomanip{_Fmt, _Tp}; + } + + template &>(), _STD declval(), + _STD declval<_Parsable&>(), _STD declval*>()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, + basic_string<_CharT, _Traits, _Alloc>& _Abbrev) { + return _Time_parse_iomanip{_Fmt, _Tp, _STD addressof(_Abbrev)}; + } + + template &>(), + _STD declval(), _STD declval<_Parsable&>(), + _STD declval*>(), _STD declval()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, minutes& _Offset) { + return _Time_parse_iomanip{_Fmt, _Tp, static_cast*>(nullptr), &_Offset}; + } + + template &>(), + _STD declval(), _STD declval<_Parsable&>(), + _STD declval*>(), _STD declval()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, + basic_string<_CharT, _Traits, _Alloc>& _Abbrev, minutes& _Offset) { + return _Time_parse_iomanip{_Fmt, _Tp, _STD addressof(_Abbrev), &_Offset}; + } + + template + basic_istream<_CharT, _Traits>& operator>>( + basic_istream<_CharT, _Traits>& _Is, const _Time_parse_iomanip<_CharT, _Traits, _Alloc, _Parsable>& _Tpi) { + return _CHRONO from_stream(_Is, _Tpi._Fmt.c_str(), _Tpi._Tp, _Tpi._Abbrev, _Tpi._Offset); + } #endif // _HAS_CXX20 } // namespace chrono diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index fefbba6929..7b3c5fa40b 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -1,17 +1,24 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include #include +#include #include #include +#include +#include #include #include #include #include +#include +#include #include +#include using namespace std; -using chrono::duration; +using namespace std::chrono; template bool test_duration_basic_out(const duration& d, const CharT* expected) { @@ -119,7 +126,1010 @@ void test_duration_output() { assert(test_duration_locale_out()); } + +template +void test_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t*> abbrev = nullptr, + minutes* offset = nullptr) { + p = Parsable{}; + if (abbrev) { + if constexpr (is_same_v) { + *abbrev = _Time_parse_fields::_Invalid_time_string; + } else { + *abbrev = L"!"; + } + } + + basic_stringstream sstr{str}; + if (abbrev) { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *abbrev, *offset); + } else { + sstr >> parse(basic_string{fmt}, p, *abbrev); + } + } else { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *offset); + } else { + sstr >> parse(basic_string{fmt}, p); + } + } + + assert(sstr); +} + +template +void fail_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t*> abbrev = nullptr, + minutes* offset = nullptr) { + p = Parsable{}; + if (abbrev) { + if constexpr (is_same_v) { + *abbrev = _Time_parse_fields::_Invalid_time_string; + } else { + *abbrev = L"!"; + } + } + + if (offset) { + *offset = minutes::min(); + } + + basic_stringstream sstr{str}; + if (abbrev) { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *abbrev, *offset); + } else { + sstr >> parse(basic_string{fmt}, p, *abbrev); + } + } else { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *offset); + } else { + sstr >> parse(basic_string{fmt}, p); + } + } + + assert(!sstr); +} + +template +void test_limits(const char* flag, const IntType min, const IntType max) { + char buffer[24]; + TimeType value; + auto conv_result = to_chars(begin(buffer), end(buffer), static_cast>(min) - 1); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + fail_parse(buffer, flag, value); + conv_result = to_chars(begin(buffer), end(buffer), max + 1); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + fail_parse(buffer, flag, value); + + conv_result = to_chars(begin(buffer), end(buffer), min); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + test_parse(buffer, flag, value); + assert(value == TimeType{min}); + conv_result = to_chars(begin(buffer), end(buffer), max); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + test_parse(buffer, flag, value); + assert(value == TimeType{max}); +} + +void parse_seconds() { + seconds time; + test_parse("1", "%S", time); + assert(time == 1s); + test_parse("12", "%S", time); + assert(time == 12s); + test_parse("234", "%S4", time); + assert(time == 23s); + test_parse("0345", "%3S5", time); + assert(time == 34s); + test_parse(" 456", "%3S6", time); + assert(time == 45s); + test_parse("99", "%S", time); // not out-of-range for duration + assert(time == 99s); + + milliseconds time_ms; + test_parse("12.543", "%S", time_ms); + assert(time_ms == 12s + 543ms); + assert(time_ms == seconds{12} + milliseconds{543}); + test_parse("01.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse(" 1.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse("1.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse("1. 234", "%S 234", time_ms); // Flag should consume "1.". + assert(time_ms == 1s); + test_parse("1 .234", "%S .234", time_ms); // Flag should consume "1". + assert(time_ms == 1s); + test_parse("12..345", "%S.345", time_ms); // Flag should consume "12.". + assert(time_ms == 12s); + fail_parse("1.2345", "%6S", time_ms); // would truncate + test_parse("1.2340", "%6S", time_ms); + assert(time_ms == 1'234ms); + + duration time_atto; + test_parse("0.400000000000000002", "%S", time_atto); + assert((time_atto == duration{4} + duration{2})); + + fail_parse("1.2 1.3", "%S %S", time_ms); + fail_parse("1.2 2.2", "%S %S", time_ms); +} + +void parse_minutes() { + seconds time; + test_parse("1", "%M", time); + assert(time == 1min); + test_parse("12", "%M", time); + assert(time == 12min); + test_parse("234", "%M4", time); + assert(time == 23min); + test_parse("0345", "%3M5", time); + assert(time == 34min); + test_parse(" 456", "%3M6", time); + assert(time == 45min); + test_parse("99", "%M", time); // not out-of-range for duration + assert(time == 99min); +} + +void parse_hours() { + seconds time; + + fail_parse("0", "%I", time); + test_parse("1", "%I", time); + assert(time == 1h); + test_parse("11", "%I", time); + assert(time == 11h); + test_parse("12", "%I", time); // assumes a.m. + assert(time == 0h); + fail_parse("13", "%I", time); + + fail_parse("0", "%OI", time); + test_parse("1", "%OI", time); + assert(time == 1h); + test_parse("11", "%OI", time); + assert(time == 11h); + test_parse("12", "%OI", time); // assumes a.m. + assert(time == 0h); + fail_parse("13", "%OI", time); + + test_parse("110", "%I0", time); + assert(time == 11h); + test_parse("0011", "%3I1", time); + assert(time == 1h); + test_parse("010", "%4I", time); + assert(time == 10h); + test_parse(" 12", "%4I", time); + assert(time == 0h); // assumes A.M. with no %p flag + + test_parse("1", "%H", time); + assert(time == 1h); + test_parse("12", "%H", time); + assert(time == 12h); + test_parse("234", "%H4", time); + assert(time == 23h); + test_parse("0123", "%3H3", time); + assert(time == 12h); + test_parse(" 234", "%3H4", time); + assert(time == 23h); + test_parse("30", "%H", time); // not out-of-range for duration + assert(time == 30h); + + // any permutation of %I, %p, and %H should be valid, as long as they're consistent + test_parse("8 pm 20", "%I %p %H", time); + assert(time == 20h); + test_parse("8 20 pm", "%I %H %p", time); + assert(time == 20h); + test_parse("pm 8 20", "%p %I %H", time); + assert(time == 20h); + test_parse("pm 20 8", "%p %H %I", time); + assert(time == 20h); + test_parse("20 pm 8", "%H %p %I", time); + assert(time == 20h); + test_parse("20 8 pm", "%H %I %p", time); + assert(time == 20h); + + // inconsistent + fail_parse("8 am 20", "%I %p %H", time); + fail_parse("8 20 am", "%I %H %p", time); + fail_parse("am 8 20", "%p %I %H", time); + fail_parse("am 20 8", "%p %H %I", time); + fail_parse("20 am 8", "%H %p %I", time); + fail_parse("20 8 am", "%H %I %p", time); +} + +void parse_other_duration() { + seconds time; + test_parse("42", "%j", time); + assert(time == days{42}); + duration time_micro; // maximum ~35.8 minutes + fail_parse("1", "%j", time_micro); + test_parse("400", "%j", time); + assert(time == days{400}); // not out-of-range for duration + + test_parse(" 1:23:42", "%T", time); + assert(time == 1h + 23min + 42s); + test_parse("01:23:42", "%T", time); + assert(time == 1h + 23min + 42s); + + test_parse("11: 2:42", "%T", time); + assert(time == 11h + 2min + 42s); + test_parse("11:02:42", "%T", time); + assert(time == 11h + 2min + 42s); + + test_parse("12:34: 4", "%T", time); + assert(time == 12h + 34min + 4s); + test_parse("12:34:04", "%T", time); + assert(time == 12h + 34min + 4s); + test_parse("00:34:04", "%T", time); + assert(time == 34min + 4s); + + milliseconds time_milli; + test_parse("12:34:56.789", "%T", time_milli); + assert(time_milli == 12h + 34min + 56s + 789ms); + + // locale's time representation %X (== "%H : %M : %S") + test_parse("12:34:04", "%X", time); + assert(time == 12h + 34min + 4s); + + // floating-point representations, parsing precision controlled by duration::period + duration df; + test_parse("9.125", "%S", df); + assert(df.count() == 9125.0f); + + duration dd; + test_parse("1.875", "%S", dd); + assert(dd.count() == 1875.0); +} + +void parse_time_zone() { + seconds time; + minutes offset; + test_parse("-0430", "%z", time, nullptr, &offset); + assert(offset == -(4h + 30min)); + test_parse("+0430", "%z", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("0430", "%z", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("11", "%z", time, nullptr, &offset); + assert(offset == 11h); + + fail_parse("4", "%z", time, nullptr, &offset); + fail_parse("043", "%z", time, nullptr, &offset); + fail_parse("!0430", "%z", time, nullptr, &offset); + + test_parse("-04:30", "%Ez", time, nullptr, &offset); + assert(offset == -(4h + 30min)); + test_parse("+04:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("04:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("4:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("11", "%Ez", time, nullptr, &offset); + assert(offset == 11h); + test_parse("4", "%Ez", time, nullptr, &offset); + assert(offset == 4h); + + fail_parse("!4", "%Ez", time, nullptr, &offset); + // %Ez matches "04", leaving "30" in the stream. + fail_parse("0430 meow", "%Ez meow", time, nullptr, &offset); + fail_parse("04:", "%Ez", time, nullptr, &offset); + fail_parse("04:3", "%Ez", time, nullptr, &offset); + + string tz_name; + test_parse("UTC", "%Z", time, &tz_name); + assert(tz_name == "UTC"); + test_parse("Etc/GMT+11", "%Z", time, &tz_name); + assert(tz_name == "Etc/GMT+11"); + test_parse("Etc/GMT-4", "%Z", time, &tz_name); + assert(tz_name == "Etc/GMT-4"); + test_parse("Asia/Hong_Kong", "%Z", time, &tz_name); + assert(tz_name == "Asia/Hong_Kong"); + + fail_parse("Not_valid! 00", "%Z %H", time, &tz_name); + test_parse("Valid_Tz! 07", "%Z! %H", time, &tz_name); + assert(tz_name == "Valid_Tz" && time == 7h); +} + +void parse_calendar_types_basic() { + // basic day tests + day d; + test_parse("1", "%d", d); + assert(d == day{1}); + test_parse("23", "%d", d); + assert(d == day{23}); + test_parse("012", "%d2", d); + assert(d == day{1}); + test_parse(" 23", "%d3", d); + assert(d == day{2}); + + test_limits("%d", 1, 31); + + test_parse("1", "%e", d); + assert(d == day{1}); + test_parse("23", "%e", d); + assert(d == day{23}); + test_parse("012", "%e2", d); + assert(d == day{1}); + test_parse(" 23", "%e3", d); + assert(d == day{2}); + + // basic weekday tests + weekday wd; + + test_parse("Mon", "%a", wd); + assert(wd == Monday); + test_parse("wedNesday", "%A", wd); + assert(wd == Wednesday); + + test_parse("1", "%w", wd); // 0-based, Sunday=0 + assert(wd == Monday); + test_parse("1", "%u", wd); // ISO 1-based, Monday=1 + assert(wd == Monday); + test_parse("7", "%u", wd); + assert(wd == Sunday); + + test_limits("%w", 0, 6); + test_limits("%u", 1, 7); + + // basic month tests + month m; + test_parse("Apr", "%b", m); + assert(m == April); + test_parse("deCeMbeR", "%b", m); + assert(m == December); + test_parse("September", "%B", m); + assert(m == September); + test_parse("February", "%h", m); + assert(m == February); + + test_parse("3", "%m", m); + assert(m == March); + test_parse("11", "%m", m); + assert(m == November); + test_parse("110", "%m0", m); + assert(m == November); + test_parse("0011", "%3m1", m); + assert(m == January); + test_parse("010", "%4m", m); + assert(m == October); + test_parse(" 12", "%4m", m); + assert(m == December); + + test_limits("%m", 1, 12); + + // basic year tests + year y; + test_parse("1777", "%Y", y); + assert(y == 1777y); + test_parse("07 17", "%y %C", y); + assert(y == 1707y); + test_parse("18 077", "%C %3y", y); + assert(y == 1877y); + + // interpretation of two-digit year by itself + test_parse("00", "%y", y); + assert(y == 2000y); + test_parse("68", "%y", y); + assert(y == 2068y); + test_parse("69", "%y", y); + assert(y == 1969y); + test_parse("99", "%y", y); + assert(y == 1999y); + + // negative century + test_parse("-1 5", "%C %y", y); + assert(y == -95y); + + // check consistency, or lack thereof + test_parse("1887 18 87", "%Y %C %y", y); + assert(y == 1887y); + fail_parse("1888 18 87", "%Y %C %y", y); + fail_parse("1887 19 87", "%Y %C %y", y); + fail_parse("1887 18 88", "%Y %C %y", y); + + // basic month_day tests + month_day md; + test_parse("1 Jan 1", "%j %b %d", md); + assert(md == January / 1d); + test_parse("32 Feb 1", "%j %b %d", md); + assert(md == February / 1d); + test_parse("59 Feb 28", "%j %b %d", md); + assert(md == February / 28d); + fail_parse("0", "%j", md); + fail_parse("60", "%j", md); // could be Feb 29 or Mar 1 + + test_parse("January 1", "%b %d", md); + assert(md == January / 1d); + test_parse("January 31", "%b %d", md); + assert(md == January / 31d); + + test_parse("February 1", "%b %d", md); + assert(md == February / 1d); + test_parse("February 29", "%b %d", md); + assert(md == February / 29d); + + test_parse("March 1", "%b %d", md); + assert(md == March / 1d); + test_parse("March 31", "%b %d", md); + assert(md == March / 31d); + + test_parse("April 1", "%b %d", md); + assert(md == April / 1d); + test_parse("April 30", "%b %d", md); + assert(md == April / 30d); + + test_parse("May 1", "%b %d", md); + assert(md == May / 1d); + test_parse("May 31", "%b %d", md); + assert(md == May / 31d); + + test_parse("June 1", "%b %d", md); + assert(md == June / 1d); + test_parse("June 30", "%b %d", md); + assert(md == June / 30d); + + test_parse("July 1", "%b %d", md); + assert(md == July / 1d); + test_parse("July 31", "%b %d", md); + assert(md == July / 31d); + + test_parse("August 1", "%b %d", md); + assert(md == August / 1d); + test_parse("August 31", "%b %d", md); + assert(md == August / 31d); + + test_parse("September 1", "%b %d", md); + assert(md == September / 1d); + test_parse("September 30", "%b %d", md); + assert(md == September / 30d); + + test_parse("October 1", "%b %d", md); + assert(md == October / 1d); + test_parse("October 31", "%b %d", md); + assert(md == October / 31d); + + test_parse("November 1", "%b %d", md); + assert(md == November / 1d); + test_parse("November 30", "%b %d", md); + assert(md == November / 30d); + + test_parse("December 1", "%b %d", md); + assert(md == December / 1d); + test_parse("December 31", "%b %d", md); + assert(md == December / 31d); + + // not ambiguous with year + year_month_day ymd; + test_parse("60 2004-02-29", "%j %F", ymd); + assert(ymd == 2004y / February / 29); + + // basic year_month_day tests + // different ways of specifying year + test_parse("12-01-1997", "%d-%m-%Y", ymd); + assert(ymd == 12d / January / 1997y); + test_parse("12-01-19 97", "%d-%m-%C %y", ymd); + assert(ymd == 12d / January / 1997y); + test_parse("12-01-97", "%d-%m-%y", ymd); + assert(ymd == 12d / January / 1997y); + + // basic %D test + test_parse("07/04/76 17", "%D %C", ymd); + assert(ymd == July / 4d / 1776y); + // locale's date representation %x (== "%d / %m / %y") + test_parse("04/07/76 17", "%x %C", ymd); + assert(ymd == 4d / July / 1776y); + test_parse("10/12/15 18", "%x %C", ymd); + assert(ymd == 10d / December / 1815y); + + // day-of-year tests, leap and non-leap years + test_parse("60 2001-03-01", "%j %F", ymd); + assert(ymd == 2001y / March / 1d); + test_parse("61 2004-03-01", "%j %F", ymd); + assert(ymd == 2004y / March / 1d); + test_parse("90 2001-03-31", "%j %F", ymd); + assert(ymd == 2001y / March / last); + test_parse("91 2004-03-31", "%j %F", ymd); + assert(ymd == 2004y / March / last); + + test_parse("91 2001-04-01", "%j %F", ymd); + assert(ymd == 2001y / April / 1d); + test_parse("92 2004-04-01", "%j %F", ymd); + assert(ymd == 2004y / April / 1d); + test_parse("120 2001-04-30", "%j %F", ymd); + assert(ymd == 2001y / April / last); + test_parse("121 2004-04-30", "%j %F", ymd); + assert(ymd == 2004y / April / last); + + test_parse("121 2001-05-01", "%j %F", ymd); + assert(ymd == 2001y / May / 1d); + test_parse("122 2004-05-01", "%j %F", ymd); + assert(ymd == 2004y / May / 1d); + test_parse("151 2001-05-31", "%j %F", ymd); + assert(ymd == 2001y / May / last); + test_parse("152 2004-05-31", "%j %F", ymd); + assert(ymd == 2004y / May / last); + + test_parse("152 2001-06-01", "%j %F", ymd); + assert(ymd == 2001y / June / 1d); + test_parse("153 2004-06-01", "%j %F", ymd); + assert(ymd == 2004y / June / 1d); + test_parse("181 2001-06-30", "%j %F", ymd); + assert(ymd == 2001y / June / last); + test_parse("182 2004-06-30", "%j %F", ymd); + assert(ymd == 2004y / June / last); + + test_parse("182 2001-07-01", "%j %F", ymd); + assert(ymd == 2001y / July / 1d); + test_parse("183 2004-07-01", "%j %F", ymd); + assert(ymd == 2004y / July / 1d); + test_parse("212 2001-07-31", "%j %F", ymd); + assert(ymd == 2001y / July / last); + test_parse("213 2004-07-31", "%j %F", ymd); + assert(ymd == 2004y / July / last); + + test_parse("213 2001-08-01", "%j %F", ymd); + assert(ymd == 2001y / August / 1d); + test_parse("214 2004-08-01", "%j %F", ymd); + assert(ymd == 2004y / August / 1d); + test_parse("243 2001-08-31", "%j %F", ymd); + assert(ymd == 2001y / August / last); + test_parse("244 2004-08-31", "%j %F", ymd); + assert(ymd == 2004y / August / last); + + test_parse("244 2001-09-01", "%j %F", ymd); + assert(ymd == 2001y / September / 1d); + test_parse("245 2004-09-01", "%j %F", ymd); + assert(ymd == 2004y / September / 1d); + test_parse("273 2001-09-30", "%j %F", ymd); + assert(ymd == 2001y / September / last); + test_parse("274 2004-09-30", "%j %F", ymd); + assert(ymd == 2004y / September / last); + + test_parse("274 2001-10-01", "%j %F", ymd); + assert(ymd == 2001y / October / 1d); + test_parse("275 2004-10-01", "%j %F", ymd); + assert(ymd == 2004y / October / 1d); + test_parse("304 2001-10-31", "%j %F", ymd); + assert(ymd == 2001y / October / last); + test_parse("305 2004-10-31", "%j %F", ymd); + assert(ymd == 2004y / October / last); + + test_parse("305 2001-11-01", "%j %F", ymd); + assert(ymd == 2001y / November / 1d); + test_parse("306 2004-11-01", "%j %F", ymd); + assert(ymd == 2004y / November / 1d); + test_parse("334 2001-11-30", "%j %F", ymd); + assert(ymd == 2001y / November / last); + test_parse("335 2004-11-30", "%j %F", ymd); + assert(ymd == 2004y / November / last); + + test_parse("335 2001-12-01", "%j %F", ymd); + assert(ymd == 2001y / December / 1d); + test_parse("336 2004-12-01", "%j %F", ymd); + assert(ymd == 2004y / December / 1d); + test_parse("365 2001-12-31", "%j %F", ymd); + assert(ymd == 2001y / December / last); + test_parse("366 2004-12-31", "%j %F", ymd); + assert(ymd == 2004y / December / last); + + fail_parse("366 2001", "%j %Y", ymd); + fail_parse("367 2004", "%j %Y", ymd); + + // Check consistency between date and day-of-week + test_parse("Wed 2000-03-01", "%a %F", ymd); + fail_parse("Mon 2000-03-01", "%a %F", ymd); + + // For %F, width is applied only to the year + test_parse("12345-06-07", "%5F", ymd); + assert(ymd == 7d / June / 12345y); + fail_parse("12345-00006-07", "%5F", ymd); + fail_parse("12345-06-00007", "%5F", ymd); + fail_parse("12345-00006-00007", "%5F", ymd); +} + +void parse_iso_week_date() { + year_month_day ymd; + test_parse("2005-W52-6", "%G-W%V-%u", ymd); + assert(ymd == 2005y / December / 31d); + test_parse("2005-W52-7", "%G-W%V-%u", ymd); + assert(ymd == 2006y / January / 1d); + test_parse("2006-W01-1", "%G-W%V-%u", ymd); + assert(ymd == 2006y / January / 2d); + fail_parse("2006-W00-1", "%G-W%V-%u", ymd); + + test_parse("2007-W52-7", "%G-W%V-%u", ymd); + assert(ymd == 2007y / December / 30d); + test_parse("2008-W01-1", "%G-W%V-%u", ymd); + assert(ymd == 2007y / December / 31d); + test_parse("2008-W01-2", "%G-W%V-%u", ymd); + assert(ymd == 2008y / January / 1d); + + fail_parse("05-W52-6", "%g-W%V-%u", ymd); // no century + + year_month_day ref{2005y / December / 31d}; + fail_parse("2005-W52-6 19", "%G-W%V-%u %C", ymd); // inconsistent century + test_parse("2005-W52-6 20", "%G-W%V-%u %C", ymd); // consistent century + assert(ymd == ref); + test_parse("05-W52-6 20", "%g-W%V-%u %C", ymd); + assert(ymd == ref); + + fail_parse("2005-W52-6 2004", "%G-W%V-%u %Y", ymd); // inconsistent year + test_parse("2005-W52-6 2005", "%G-W%V-%u %Y", ymd); // consistent year + assert(ymd == ref); + test_parse("05-W52-6 2005", "%g-W%V-%u %Y", ymd); + assert(ymd == ref); + test_parse("2005-W52-6 05", "%G-W%V-%u %y", ymd); + assert(ymd == ref); + test_parse("05-W52-6 05", "%g-W%V-%u %y", ymd); + assert(ymd == ref); + + ref = 2007y / December / 31d; + fail_parse("2008-W01-1 2008", "%G-W%V-%u %Y", ymd); // inconsistent year (!) + test_parse("2008-W01-1 2007", "%G-W%V-%u %Y", ymd); // consistent year + assert(ymd == ref); + test_parse("08-W01-1 2007", "%g-W%V-%u %Y", ymd); + assert(ymd == ref); + test_parse("2008-W01-1 07", "%G-W%V-%u %y", ymd); + assert(ymd == ref); + test_parse("08-W01-1 07", "%g-W%V-%u %y", ymd); + assert(ymd == ref); + + // ISO and Gregorian years in different centuries + test_parse("1699-W53-5 1700", "%G-W%V-%u %Y", ymd); + assert(ymd == 1d / January / 1700y); + fail_parse("1699-W54-5 1700", "%G-W%V-%u %Y", ymd); + fail_parse("1699-W53-5 00", "%G-W%V-%u %y", ymd); // inconsistent %y (== 2000) + fail_parse("99-W53-5 16 00", "%g-W%V-%u %C %y", ymd); // inconsistent %C+%y year (== 1600) + fail_parse("99-W53-5 17 00", "%g-W%V-%u %C %y", ymd); // inconsistent %C+%g ISO year (== 1700) + + // This is expected to parse successfully. Even though %C+%g would give the wrong year, + // as above we don't try to use that for the ISO year when %G is present. + test_parse("1699 99-W53-5 17 00", "%G %g-W%V-%u %C %y", ymd); + assert(ymd == 1d / January / 1700y); + fail_parse("1699 98-W53-5 17 00", "%G %g-W%V-%u %C %y", ymd); +} + +void parse_other_week_date() { + year_month_day ymd; + // Year begins on Sunday. + test_parse("2017-01-0", "%Y-%U-%w", ymd); + assert(ymd == 2017y / January / 1d); + test_parse("2017-00-0", "%Y-%W-%w", ymd); + assert(ymd == 2017y / January / 1d); + test_parse("2017-53-0", "%Y-%U-%w", ymd); + assert(ymd == 2017y / December / 31d); + + fail_parse("2017/-1/0", "%Y/%W/%w", ymd); + fail_parse("2017/-1/0", "%Y/%U/%w", ymd); + fail_parse("2017-54-0", "%Y-%W-%w", ymd); + fail_parse("2017-54-0", "%Y-%U-%w", ymd); + fail_parse("2018-00-0", "%Y-%U-%w", ymd); // refers to 31 Dec. 2017 + fail_parse("2017-53-1", "%Y-%U-%w", ymd); // refers to 01 Jan. 2018 + + // Year begins on Monday. + test_parse("2018-00-1", "%Y-%U-%w", ymd); + assert(ymd == 2018y / January / 1d); + test_parse("2018-01-1", "%Y-%W-%w", ymd); + assert(ymd == 2018y / January / 1d); + test_parse("2018-53-1", "%Y-%W-%w", ymd); + assert(ymd == 2018y / December / 31d); + + // Year begins on Tuesday. + test_parse("2019-00-2", "%Y-%U-%w", ymd); + assert(ymd == 2019y / January / 1d); + test_parse("2019-00-2", "%Y-%W-%w", ymd); + assert(ymd == 2019y / January / 1d); + + // Year begins on Wednesday. + test_parse("2020-00-3", "%Y-%U-%w", ymd); + assert(ymd == 2020y / January / 1d); + test_parse("2020-00-3", "%Y-%W-%w", ymd); + assert(ymd == 2020y / January / 1d); + + // Year begins on Thursday. + test_parse("2015-00-4", "%Y-%U-%w", ymd); + assert(ymd == 2015y / January / 1d); + test_parse("2015-00-4", "%Y-%W-%w", ymd); + assert(ymd == 2015y / January / 1d); + + // Year begins on Friday. + test_parse("2016-00-5", "%Y-%U-%w", ymd); + assert(ymd == 2016y / January / 1d); + test_parse("2016-00-5", "%Y-%W-%w", ymd); + assert(ymd == 2016y / January / 1d); + + // Year begins on Saturday. + test_parse("2022-00-6", "%Y-%U-%w", ymd); + assert(ymd == 2022y / January / 1d); + test_parse("2022-00-6", "%Y-%W-%w", ymd); + assert(ymd == 2022y / January / 1d); +} + +void parse_whitespace() { + seconds time; + fail_parse("ab", "a%nb", time); + test_parse("a b", "a%nb", time); + fail_parse("a b", "a%nb", time); + fail_parse("a", "a%n", time); + test_parse("a ", "a%n", time); + + test_parse("ab", "a%tb", time); + test_parse("a b", "a%tb", time); + fail_parse("a b", "a%tb", time); + test_parse("a", "a%t", time); + test_parse("a ", "a%t", time); + + test_parse("a", "a ", time); + test_parse("", "", time); + test_parse("", " ", time); + test_parse("", "%t", time); + fail_parse("", "%n", time); +} + +void insert_leap_second(const sys_days& date, const seconds& value) { + const auto& my_tzdb = get_tzdb_list().front(); + vector zones; + vector links; + transform(my_tzdb.zones.begin(), my_tzdb.zones.end(), back_inserter(zones), + [](const auto& tz) { return time_zone{tz.name()}; }); + transform(my_tzdb.links.begin(), my_tzdb.links.end(), back_inserter(links), [](const auto& link) { + return time_zone_link{link.name(), link.target()}; + }); + + auto leap_vec = my_tzdb.leap_seconds; + leap_vec.emplace_back(date, value == 1s, leap_vec.back()._Elapsed()); + get_tzdb_list()._Emplace_front( + tzdb{my_tzdb.version, move(zones), move(links), move(leap_vec), my_tzdb._All_ls_positive && (value == 1s)}); +} + +void parse_timepoints() { + sys_seconds ref = sys_days{2020y / October / 29d} + 19h + 1min + 42s; + sys_seconds st; + utc_seconds ut; + file_time ft; + + test_parse("oct 29 19:01:42 2020", "%c", st); + test_parse("oct 29 19:01:42 2020", "%c", ut); + test_parse("oct 29 19:01:42 2020", "%c", ft); + + assert(st == ref); + assert(ut == utc_clock::from_sys(ref)); + assert(ft == clock_cast(ref)); + + test_parse("oct 29 19:01:42 2020 0430", "%c %z", st); + assert(st == ref - (4h + 30min)); + + // N4878 [time.clock.tai]/1: + // The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is offset 10s ahead of UTC at this date. + // That is, 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into + // TAI. Therefore every time a leap second is inserted into UTC, UTC shifts another second with respect to TAI. + // For example by 2000-01-01 there had been 22 positive and 0 negative leap seconds inserted so 2000-01-01 + // 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the initial 10s offset). + + ref = sys_days{1957y / December / 31d} + days{1} - 10s; + tai_seconds tt; + test_parse("jan 1 00:00:00 1958", "%c", tt); + assert(tt == clock_cast(ref)); + + ref = sys_days{2000y / January / 1d}; + test_parse("jan 1 00:00:32 2000", "%c", tt); + assert(tt == clock_cast(ref)); + + // N4878 [time.clock.gps]/1: + // The clock gps_clock measures seconds since the first Sunday of January, 1980 00:00:00 UTC. Leap seconds are + // not inserted into GPS. Therefore every time a leap second is inserted into UTC, UTC shifts another second + // with respect to GPS. Aside from the offset from 1958y/January/1 to 1980y/January/Sunday[1], GPS is behind TAI + // by 19s due to the 10s offset between 1958 and 1970 and the additional 9 leap seconds inserted between 1970 + // and 1980. + + gps_seconds gt; + ref = sys_days{1980y / January / 6d}; + test_parse("jan 6 00:00:00 1980", "%c", gt); + assert(gt == clock_cast(ref)); + test_parse("jan 6 00:00:19 1980", "%c", tt); + assert(gt == clock_cast(tt)); + + seconds time; + test_parse(" 1:23:42 am", "%r", time); + assert(time == 1h + 23min + 42s); + test_parse("2000-01-02 01:23:42 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (13h + 23min + 42s)); + + test_parse("11: 2:42 am", "%r", time); + assert(time == 11h + 2min + 42s); + test_parse("2000-01-02 11:02:42 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (23h + 2min + 42s)); + + test_parse("12:34: 4 am", "%r", time); + assert(time == 34min + 4s); + test_parse("2000-01-02 12:34:04 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (12h + 34min + 4s)); + + + test_parse(" 3:14 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (3h + 14min)); + test_parse("03:14 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (3h + 14min)); + + test_parse("11: 3 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (11h + 3min)); + test_parse("11:03 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (11h + 3min)); + + test_parse("00:42 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (42min)); + test_parse("12:42 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (12h + 42min)); + + // Historical leap seconds don't allow complete testing, because they've all been positive and there haven't been + // any since 2016 (as of 2021). + insert_leap_second(1d / January / 2020y, -1s); + insert_leap_second(1d / January / 2022y, 1s); + + utc_seconds ut_ref = utc_clock::from_sys(sys_days{1d / July / 1972y}) - 1s; // leap second insertion + test_parse("june 30 23:59:60 1972", "%c", ut); + assert(ut == ut_ref); + // not leap-second aware + fail_parse("june 30 23:59:60 1972", "%c", st); + fail_parse("june 30 23:59:60 1972", "%c", tt); + fail_parse("june 30 23:59:60 1972", "%c", gt); + fail_parse("june 30 23:59:60 1972", "%c", ft); + + fail_parse("june 30 23:59:60 1973", "%c", ut); // not a leap second insertion + + ref = sys_days{1d / January / 2020y} - 1s; // negative leap second, UTC time doesn't exist + fail_parse("dec 31 23:59:59 2019", "%c", ut); + fail_parse("dec 31 23:59:59 2019", "%c", st); + fail_parse("dec 31 23:59:59 2019", "%c", ft); + + local_seconds lt; + test_parse("dec 31 23:59:59 2019", "%c", lt); // Not UTC, might be valid depending on the time zone. + assert(lt.time_since_epoch() == ref.time_since_epoch()); + + // Initially, TAI - UTC == 37s. + test_parse("dec 31 23:59:59 2019", "%c", tt); + test_parse("dec 31 23:59:22 2019", "%c", ut); + assert(tt == clock_cast(ut)); + + test_parse("jan 01 00:00:35 2020", "%c", tt); + test_parse("dec 31 23:59:58 2019", "%c", ut); + assert(tt == clock_cast(ut)); + + test_parse("jan 01 00:00:36 2020", "%c", tt); + test_parse("jan 01 00:00:00 2020", "%c", ut); + assert(tt == clock_cast(ut)); + + // Initially, GPS - UTC == 18s + test_parse("dec 31 23:59:59 2019", "%c", gt); + test_parse("dec 31 23:59:41 2019", "%c", ut); + assert(gt == clock_cast(ut)); + + test_parse("jan 01 00:00:16 2020", "%c", gt); + test_parse("dec 31 23:59:58 2019", "%c", ut); + assert(gt == clock_cast(ut)); + + test_parse("jan 01 00:00:17 2020", "%c", gt); + test_parse("jan 01 00:00:00 2020", "%c", ut); + assert(gt == clock_cast(ut)); + + + ut_ref = utc_clock::from_sys(sys_days{1d / January / 2022y}) - 1s; // leap second insertion + test_parse("dec 31 23:59:60 2021", "%c", ut); + assert(ut == ut_ref); + test_parse("dec 31 23:59:60 2021", "%c", ft); + assert(ft == clock_cast(ut_ref)); + + + // GH-1606: reads too many leading zeros + test_parse("19700405T000006", "%Y%m%dT%H%M%S", st); + assert(st == sys_days{5d / April / 1970y} + 6s); + + // GH-1280 tests + year_month_day ymd; + test_parse("20200609", "%Y%m%d", ymd); + assert(ymd == 9d / June / 2020y); + + test_parse("20201213", "%Y%m%d", ymd); + assert(ymd == 13d / December / 2020y); + + test_parse("2020112", "%Y%m%d", ymd); + assert(ymd == 2d / November / 2020y); + + test_parse("2020061125", "%Y%m%d", ymd); + assert(ymd == 11d / June / 2020y); + + test_parse("2020120625119", "%Y%m%d", ymd); + assert(ymd == 6d / December / 2020y); + + test_parse("2020092Text", "%Y%m%d", ymd); + assert(ymd == 2d / September / 2020y); + + test_parse("20200609", "%Y%m%d", ymd); + assert(ymd == 9d / June / 2020y); + + test_parse("2020112", "%Y%m%d", ymd); + assert(ymd == 2d / November / 2020y); + + test_parse("2020061125", "%Y%m%d", ymd); + assert(ymd == 11d / June / 2020y); + + test_parse("2020124", "%Y%m%d", ymd); + assert(ymd == 4d / December / 2020y); + + test_parse("2020104Text", "%Y%m%d", ymd); + assert(ymd == 4d / October / 2020y); + + fail_parse("202000000000000923", "%Y%m%d", ymd); + fail_parse("202000000000000923", "%Y%m%d", ymd); + + // time_point out-of-range tests + fail_parse("1887-12-22 00:00:-1", "%F %T", st); + fail_parse("1887-12-22 00:00:60", "%F %T", st); + fail_parse("1887-12-22 00:-1:00", "%F %T", st); + fail_parse("1887-12-22 00:60:00", "%F %T", st); + fail_parse("1887-12-22 -1:00:00", "%F %T", st); + fail_parse("1887-12-22 24:00:00", "%F %T", st); + + test_parse("1912-06-23 00:00:00", "%F %T", st); + assert(st == sys_days{23d / June / 1912y}); + test_parse("1912-06-23 23:59:59", "%F %T", st); + assert(st == sys_days{23d / June / 1912y} + 23h + 59min + 59s); +} + +void parse_wchar() { + seconds time; + test_parse(L"12", L"%S", time); + assert(time == 12s); + test_parse(L"12", L"%M", time); + assert(time == 12min); + test_parse(L"30", L"%H", time); + assert(time == 30h); + test_parse(L" 1:23:42", L"%T", time); + assert(time == 1h + 23min + 42s); + wstring tz_name; + test_parse(L"Etc/GMT+11", L"%Z", time, &tz_name); + assert(tz_name == L"Etc/GMT+11"); + fail_parse(L"Not_valid! 00", L"%Z %H", time, &tz_name); + + weekday wd; + test_parse(L"wedNesday", L"%A", wd); + assert(wd == Wednesday); + + month m; + test_parse(L"deCeMbeR", L"%b", m); + assert(m == December); + + sys_seconds st; + test_parse(L"oct 29 19:01:42 2020", L"%c", st); + assert(st == sys_days{2020y / October / 29d} + 19h + 1min + 42s); + + fail_parse(L"ab", L"a%nb", time); + test_parse(L"a b", L"a%nb", time); + fail_parse(L"a b", L"a%nb", time); +} + +void test_parse() { + parse_seconds(); + parse_minutes(); + parse_hours(); + parse_other_duration(); + parse_time_zone(); + parse_calendar_types_basic(); + parse_iso_week_date(); + parse_other_week_date(); + parse_whitespace(); + parse_timepoints(); + parse_wchar(); +} + + int main() { test_duration_output(); + test_parse(); return 0; } From 3017b912169e6ac2d791f7970ce0d12826618986 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Mar 2021 16:14:06 -0700 Subject: [PATCH 07/22] chrono cleanups (#1779) --- stl/CMakeLists.txt | 2 +- stl/inc/{xtzdb.h => __msvc_tzdb.hpp} | 22 +-- stl/inc/chrono | 125 +++++++++--------- stl/inc/header-units.json | 2 +- stl/src/msvcp_atomic_wait.src | 4 +- stl/src/syncstream.cpp | 2 +- stl/src/tzdb.cpp | 14 +- tests/std/include/timezone_data.hpp | 1 - .../test.cpp | 5 +- .../test.cpp | 2 +- .../test.cpp | 4 +- 11 files changed, 94 insertions(+), 89 deletions(-) rename stl/inc/{xtzdb.h => __msvc_tzdb.hpp} (84%) diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index 64d282508c..0d460ef834 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -4,6 +4,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_all_public_headers.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_system_error_abi.hpp + ${CMAKE_CURRENT_LIST_DIR}/inc/__msvc_tzdb.hpp ${CMAKE_CURRENT_LIST_DIR}/inc/algorithm ${CMAKE_CURRENT_LIST_DIR}/inc/any ${CMAKE_CURRENT_LIST_DIR}/inc/array @@ -233,7 +234,6 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/xtimec.h ${CMAKE_CURRENT_LIST_DIR}/inc/xtr1common ${CMAKE_CURRENT_LIST_DIR}/inc/xtree - ${CMAKE_CURRENT_LIST_DIR}/inc/xtzdb.h ${CMAKE_CURRENT_LIST_DIR}/inc/xutility ${CMAKE_CURRENT_LIST_DIR}/inc/ymath.h ${CMAKE_CURRENT_LIST_DIR}/inc/yvals.h diff --git a/stl/inc/xtzdb.h b/stl/inc/__msvc_tzdb.hpp similarity index 84% rename from stl/inc/xtzdb.h rename to stl/inc/__msvc_tzdb.hpp index 73747054ce..d252f8a6d5 100644 --- a/stl/inc/xtzdb.h +++ b/stl/inc/__msvc_tzdb.hpp @@ -1,11 +1,11 @@ -// xtzdb.h internal header +// __msvc_tzdb.hpp internal header // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #pragma once -#ifndef _XTZDB_H -#define _XTZDB_H +#ifndef __MSVC_TZDB_HPP +#define __MSVC_TZDB_HPP #include #if _STL_COMPILER_PREPROCESSOR #include @@ -22,7 +22,7 @@ _STL_DISABLE_CLANG_WARNINGS using __std_tzdb_epoch_milli = double; -struct __std_tzdb_registry_leap_info { +struct __std_tzdb_leap_info { uint16_t _Year; uint16_t _Month; uint16_t _Day; @@ -76,9 +76,9 @@ _NODISCARD __std_tzdb_sys_info* __stdcall __std_tzdb_get_sys_info( const char* _Tz, size_t _Tz_len, __std_tzdb_epoch_milli _Local) noexcept; void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* _Info) noexcept; -__std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( - size_t _Prev_reg_ls_size, size_t* _Current_reg_ls_size) noexcept; -void __stdcall __std_tzdb_delete_reg_leap_seconds(__std_tzdb_registry_leap_info* _Rlsi) noexcept; +_NODISCARD __std_tzdb_leap_info* __stdcall __std_tzdb_get_leap_seconds( + size_t _Prev_ls_size, size_t* _Current_ls_size) noexcept; +void __stdcall __std_tzdb_delete_leap_seconds(__std_tzdb_leap_info* _Info) noexcept; _NODISCARD void* __stdcall __std_calloc_crt(size_t _Count, size_t _Size) noexcept; void __stdcall __std_free_crt(void* _Ptr) noexcept; @@ -112,9 +112,9 @@ struct _Tzdb_deleter<__std_tzdb_sys_info> { }; template <> -struct _Tzdb_deleter<__std_tzdb_registry_leap_info[]> { - void operator()(__std_tzdb_registry_leap_info* _Info) const noexcept { - __std_tzdb_delete_reg_leap_seconds(_Info); +struct _Tzdb_deleter<__std_tzdb_leap_info[]> { + void operator()(__std_tzdb_leap_info* _Info) const noexcept { + __std_tzdb_delete_leap_seconds(_Info); } }; @@ -151,4 +151,4 @@ _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) #pragma pack(pop) #endif // _STL_COMPILER_PREPROCESSOR -#endif // _XTZDB_H +#endif // __MSVC_TZDB_HPP diff --git a/stl/inc/chrono b/stl/inc/chrono index 351cfa5cbf..584b8941a5 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -16,6 +16,7 @@ #include #if _HAS_CXX20 +#include <__msvc_tzdb.hpp> #include #include #include @@ -27,7 +28,6 @@ #include #include #include -#include #endif // _HAS_CXX20 #pragma pack(push, _CRT_PACKING) @@ -2551,7 +2551,7 @@ namespace chrono { // [time.zone.db] - _NODISCARD inline string _Xtzdb_generate_current_zone() { + _NODISCARD inline string _Tzdb_generate_current_zone() { unique_ptr<__std_tzdb_current_zone_info, _Tzdb_deleter<__std_tzdb_current_zone_info>> _Info{ __std_tzdb_get_current_zone()}; if (_Info == nullptr) { @@ -2596,11 +2596,11 @@ namespace chrono { } _NODISCARD const time_zone* current_zone() const { - return locate_zone(_Xtzdb_generate_current_zone()); + return locate_zone(_Tzdb_generate_current_zone()); } }; - _NODISCARD inline tuple _Xtzdb_generate_time_zones() { + _NODISCARD inline tuple _Tzdb_generate_time_zones() { unique_ptr<__std_tzdb_time_zones_info, _Tzdb_deleter<__std_tzdb_time_zones_info>> _Info{ __std_tzdb_get_time_zones()}; if (_Info == nullptr) { @@ -2626,8 +2626,7 @@ namespace chrono { return {_Info->_Version, _STD move(_Time_zones), _STD move(_Time_zone_links)}; } - _NODISCARD inline pair _Xtzdb_generate_leap_seconds( - const size_t _Current_size) { + _NODISCARD inline pair _Tzdb_generate_leap_seconds(const size_t _Current_size) { // Returns empty vector if no new leap seconds are found. static constexpr leap_second _Known_leap_seconds[]{ {sys_seconds{seconds{78796800}}, true, seconds{0}}, @@ -2659,7 +2658,7 @@ namespace chrono { {sys_seconds{seconds{1483228800}}, true, seconds{26}}, }; - // __std_tzdb_get_reg_leap_seconds gets leap second (LS) data from the registry, but only if it contains more + // __std_tzdb_get_leap_seconds gets leap second (LS) data from the registry, but only if it contains more // LSs than we already know about. The registry only contains LSs after 2018, so we need to tell it how many of // *those* we already know about. The *total* number of LSs known at this point is a combination of what the // caller knows (_Current_size, 0 on first call) and the _Known_leap_seconds entries. @@ -2668,8 +2667,8 @@ namespace chrono { (_STD max)(_Current_size, _STD size(_Known_leap_seconds)) - _Pre_2018_count; size_t _Reg_post_2018_ls_size; // number of post-2018 LSs found in the registry - unique_ptr<__std_tzdb_registry_leap_info[], _Tzdb_deleter<__std_tzdb_registry_leap_info[]>> _Reg_ls_data{ - __std_tzdb_get_reg_leap_seconds(_Known_post_2018_ls_size, &_Reg_post_2018_ls_size)}; + unique_ptr<__std_tzdb_leap_info[], _Tzdb_deleter<__std_tzdb_leap_info[]>> _Reg_ls_data{ + __std_tzdb_get_leap_seconds(_Known_post_2018_ls_size, &_Reg_post_2018_ls_size)}; if (_Reg_post_2018_ls_size > _Known_post_2018_ls_size && !_Reg_ls_data) { _Xbad_alloc(); // registry has new data, but failed to allocate storage @@ -2683,8 +2682,7 @@ namespace chrono { if (_New_size > _Current_size) { _Leap_sec_info.reserve(_New_size); - _Leap_sec_info.insert( - _Leap_sec_info.begin(), _STD cbegin(_Known_leap_seconds), _STD cend(_Known_leap_seconds)); + _Leap_sec_info.assign(_STD cbegin(_Known_leap_seconds), _STD cend(_Known_leap_seconds)); for (size_t _Idx = 0; _Idx < _Reg_post_2018_ls_size; ++_Idx) { // Leap seconds occur at _Ls._Hour:59:59. We store the next second after, so we need to add an entire @@ -2701,12 +2699,11 @@ namespace chrono { return {_STD move(_Leap_sec_info), _All_ls_positive}; } - _NODISCARD inline string _Xtzdb_update_version(const string_view _Version, const size_t _Num_leap_seconds) { + _NODISCARD inline string _Tzdb_update_version(const string_view _Version, const size_t _Num_leap_seconds) { string _Icu_version{_Version.substr(0, _Version.find_last_of('.'))}; return _STD move(_Icu_version) + "." + _STD to_string(_Num_leap_seconds); } - // TRANSITION: work in progress // CLASS tzdb_list class tzdb_list { private: @@ -2719,8 +2716,8 @@ namespace chrono { tzdb_list& operator=(const tzdb_list&) = delete; tzdb_list() { - auto [_Icu_version, _Zones, _Links] = _Xtzdb_generate_time_zones(); - auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(0); + auto [_Icu_version, _Zones, _Links] = _Tzdb_generate_time_zones(); + auto [_Leap_sec, _All_ls_positive] = _Tzdb_generate_leap_seconds(0); auto _Version = _Icu_version + "." + _STD to_string(_Leap_sec.size()); _Tzdb_list.emplace_front(tzdb{ _STD move(_Version), _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); @@ -2732,23 +2729,26 @@ namespace chrono { } const_iterator erase_after(const_iterator _Where) noexcept /* strengthened */ { + _Unique_lock _Lk(_Tzdb_mutex); return _Tzdb_list.erase_after(_Where); } _NODISCARD const_iterator begin() const noexcept { + _Shared_lock _Lk(_Tzdb_mutex); return _Tzdb_list.begin(); } _NODISCARD const_iterator end() const noexcept { - return _Tzdb_list.end(); + return _Tzdb_list.end(); // no lock necessary for forward_list::end() } _NODISCARD const_iterator cbegin() const noexcept { + _Shared_lock _Lk(_Tzdb_mutex); return _Tzdb_list.cbegin(); } _NODISCARD const_iterator cend() const noexcept { - return _Tzdb_list.cend(); + return _Tzdb_list.cend(); // no lock necessary for forward_list::cend() } template @@ -2759,7 +2759,7 @@ namespace chrono { const tzdb& _Reload() { _Unique_lock _Lk(_Tzdb_mutex); - auto [_Leap_sec, _All_ls_positive] = _Xtzdb_generate_leap_seconds(_Tzdb_list.front().leap_seconds.size()); + auto [_Leap_sec, _All_ls_positive] = _Tzdb_generate_leap_seconds(_Tzdb_list.front().leap_seconds.size()); if (!_Leap_sec.empty()) { const auto& _Tzdb = _Tzdb_list.front(); vector _Zones; @@ -2770,7 +2770,7 @@ namespace chrono { _Tzdb.links.begin(), _Tzdb.links.end(), _STD back_inserter(_Links), [](const auto& _Link) { return time_zone_link{_Link.name(), _Link.target()}; }); - auto _Version = _Xtzdb_update_version(_Tzdb.version, _Leap_sec.size()); + auto _Version = _Tzdb_update_version(_Tzdb.version, _Leap_sec.size()); _Tzdb_list.emplace_front(tzdb{ _STD move(_Version), _STD move(_Zones), _STD move(_Links), _STD move(_Leap_sec), _All_ls_positive}); } @@ -2789,21 +2789,11 @@ namespace chrono { _Shared_lock(const _Shared_lock&) = delete; _Shared_lock& operator=(const _Shared_lock&) = delete; - void _Unlock() { - if (_Owns) { - _Smtx_unlock_shared(_Mtx); - } - _Owns = false; - } - ~_Shared_lock() { - if (_Owns) { - _Smtx_unlock_shared(_Mtx); - } + _Smtx_unlock_shared(_Mtx); } _Smtx_t* _Mtx; - bool _Owns = true; }; struct _NODISCARD _Unique_lock { @@ -2830,14 +2820,20 @@ namespace chrono { if (_Tzdb_ptr == nullptr) { auto _My_tzdb = static_cast(__std_calloc_crt(1, sizeof(tzdb_list))); if (_My_tzdb == nullptr) { - _Xruntime_error("bad allocation"); + _Xruntime_error("bad allocation"); // not bad_alloc, see N4878 [time.zone.db.access]/4 } - try { - _STD construct_at(_My_tzdb); - } catch (const exception& _Except) { - _Xruntime_error(_Except.what()); - } + _TRY_BEGIN + _STD construct_at(_My_tzdb); + _CATCH(const runtime_error&) + __std_free_crt(_My_tzdb); + _RERAISE; + _CATCH(const exception& _Except) +#if _HAS_EXCEPTIONS + __std_free_crt(_My_tzdb); + _Xruntime_error(_Except.what()); +#endif // _HAS_EXCEPTIONS + _CATCH_END if (_Global_tzdb_list.compare_exchange_strong(_Tzdb_ptr, _My_tzdb)) { _Tzdb_ptr = _My_tzdb; @@ -2867,19 +2863,23 @@ namespace chrono { // FUNCTION reload_tzdb inline const tzdb& reload_tzdb() { - try { - return _CHRONO get_tzdb_list()._Reload(); - } catch (const exception& _Except) { - _Xruntime_error(_Except.what()); - } + _TRY_BEGIN + return _CHRONO get_tzdb_list()._Reload(); + _CATCH(const runtime_error&) + _RERAISE; + _CATCH(const exception& _Except) +#if _HAS_EXCEPTIONS + _Xruntime_error(_Except.what()); +#endif // _HAS_EXCEPTIONS + _CATCH_END } // FUNCTION remote_version _NODISCARD inline string remote_version() { const auto& _Tzdb = _CHRONO get_tzdb(); const auto& _Version = _Tzdb.version; - const auto [_Leap_sec, _Ignored] = _Xtzdb_generate_leap_seconds(_Tzdb.leap_seconds.size()); - return _Leap_sec.empty() ? _Version : _Xtzdb_update_version(_Version, _Leap_sec.size()); + const auto [_Leap_sec, _Ignored] = _Tzdb_generate_leap_seconds(_Tzdb.leap_seconds.size()); + return _Leap_sec.empty() ? _Version : _Tzdb_update_version(_Version, _Leap_sec.size()); } // [time.zone.zonedtraits] @@ -2924,8 +2924,10 @@ namespace chrono { explicit zoned_time(_TimeZonePtr _Tz) noexcept /* strengthened */ : _Zone{_STD move(_Tz)} {} - template , int> = 0> + // clang-format on explicit zoned_time(string_view _Name) : _Zone{_Traits::locate_zone(_Name)} {} template , sys_time<_Duration>>, int> = 0> @@ -2934,10 +2936,11 @@ namespace chrono { zoned_time(_TimeZonePtr _Tz, const sys_time<_Duration>& _Sys) : _Zone{_STD move(_Tz)}, _Tp{_Sys} {} - template &>, - int> = 0> + // clang-format off + template &>, + int> = 0> + // clang-format on zoned_time(string_view _Name, type_identity_t&> _Sys) : zoned_time{_Traits::locate_zone(_Name), _Sys} {} @@ -2948,10 +2951,11 @@ namespace chrono { zoned_time(_TimeZonePtr _Tz, const local_time<_Duration>& _Local) : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local)} {} - template &>, - int> = 0> + // clang-format off + template &>, + int> = 0> + // clang-format on zoned_time(string_view _Name, type_identity_t>& _Local) : zoned_time{_Traits::locate_zone(_Name), _Local} {} @@ -2963,10 +2967,11 @@ namespace chrono { zoned_time(_TimeZonePtr _Tz, const local_time<_Duration>& _Local, choose _Choose) : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local, _Choose)} {} - template &, choose>, - int> = 0> + // clang-format off + template &, choose>, + int> = 0> + // clang-format on zoned_time(string_view _Name, type_identity_t&> _Local, choose _Choose) : zoned_time{_Traits::locate_zone(_Name), _Local, _Choose} {} @@ -3169,7 +3174,7 @@ namespace chrono { template _NODISCARD static sys_time> to_sys(const utc_time<_Duration>& _Utc_time) { using _CommonType = common_type_t<_Duration, seconds>; - const auto _Lsi{get_leap_second_info(_Utc_time)}; + const auto _Lsi{_CHRONO get_leap_second_info(_Utc_time)}; _CommonType _Ticks; if (_Lsi.is_leap_second) { const auto _Leap_sec_minus_one = _CHRONO floor(_Utc_time.time_since_epoch()) - _Lsi.elapsed; @@ -3313,8 +3318,8 @@ namespace filesystem { const chrono::file_time<_Duration>& _File_time) { using namespace chrono; using _CommonType = common_type_t<_Duration, seconds>; - const auto _Ticks = - _File_time.time_since_epoch() - duration_cast(duration{__std_fs_file_time_epoch_adjustment}); + const auto _Ticks = _File_time.time_since_epoch() + - _CHRONO duration_cast(duration{__std_fs_file_time_epoch_adjustment}); if (_Ticks < _Cutoff.time_since_epoch()) { return utc_clock::from_sys(sys_time<_CommonType>{_Ticks}); @@ -3328,7 +3333,7 @@ namespace filesystem { const chrono::utc_time<_Duration>& _Utc_time) { using namespace chrono; file_time> _File_time{ - duration_cast(duration{__std_fs_file_time_epoch_adjustment})}; + _CHRONO duration_cast(duration{__std_fs_file_time_epoch_adjustment})}; if (_Utc_time < utc_seconds{_Cutoff.time_since_epoch()} + _Skipped_filetime_leap_seconds) { _File_time += utc_clock::to_sys(_Utc_time).time_since_epoch(); diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index f27775fd22..cac92cab6d 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -6,6 +6,7 @@ "BuildAsHeaderUnits": [ // "__msvc_all_public_headers.hpp", // for testing, not production "__msvc_system_error_abi.hpp", + "__msvc_tzdb.hpp", "algorithm", "any", "array", @@ -143,7 +144,6 @@ "xtimec.h", "xtr1common", "xtree", - "xtzdb.h", "xutility", "ymath.h", "yvals.h", diff --git a/stl/src/msvcp_atomic_wait.src b/stl/src/msvcp_atomic_wait.src index cbcd3153c4..0ea7194ccc 100644 --- a/stl/src/msvcp_atomic_wait.src +++ b/stl/src/msvcp_atomic_wait.src @@ -30,11 +30,11 @@ EXPORTS __std_release_shared_mutex_for_instance __std_submit_threadpool_work __std_tzdb_delete_current_zone - __std_tzdb_delete_reg_leap_seconds + __std_tzdb_delete_leap_seconds __std_tzdb_delete_sys_info __std_tzdb_delete_time_zones __std_tzdb_get_current_zone - __std_tzdb_get_reg_leap_seconds + __std_tzdb_get_leap_seconds __std_tzdb_get_sys_info __std_tzdb_get_time_zones __std_wait_for_threadpool_work_callbacks diff --git a/stl/src/syncstream.cpp b/stl/src/syncstream.cpp index 6a3454a56b..2b16b31654 100644 --- a/stl/src/syncstream.cpp +++ b/stl/src/syncstream.cpp @@ -3,13 +3,13 @@ // initialize syncstream mutex map +#include <__msvc_tzdb.hpp> #include #include #include #include #include #include -#include #pragma warning(disable : 4074) #pragma init_seg(compiler) diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp index c68f6d61ff..9ba8541bc5 100644 --- a/stl/src/tzdb.cpp +++ b/stl/src/tzdb.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include <__msvc_tzdb.hpp> #include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include @@ -551,7 +551,7 @@ void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* const _Info) noex } } -__std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( +_NODISCARD __std_tzdb_leap_info* __stdcall __std_tzdb_get_leap_seconds( const size_t prev_reg_ls_size, size_t* const current_reg_ls_size) noexcept { // On exit--- // *current_reg_ls_size <= prev_reg_ls_size, reg_ls_data == nullptr --> no new data @@ -572,14 +572,14 @@ __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( DWORD byte_size = 0; status = RegQueryValueExW(leap_sec_key, reg_subkey_name, nullptr, nullptr, nullptr, &byte_size); - static_assert(sizeof(__std_tzdb_registry_leap_info) == 12); + static_assert(sizeof(__std_tzdb_leap_info) == 12); const auto ls_size = byte_size / 12; *current_reg_ls_size = ls_size; - __std_tzdb_registry_leap_info* reg_ls_data = nullptr; + __std_tzdb_leap_info* reg_ls_data = nullptr; if ((status == ERROR_SUCCESS || status == ERROR_MORE_DATA) && ls_size > prev_reg_ls_size) { try { - reg_ls_data = new __std_tzdb_registry_leap_info[ls_size]; + reg_ls_data = new __std_tzdb_leap_info[ls_size]; status = RegQueryValueExW( leap_sec_key, reg_subkey_name, nullptr, nullptr, reinterpret_cast(reg_ls_data), &byte_size); if (status != ERROR_SUCCESS) { @@ -597,8 +597,8 @@ __std_tzdb_registry_leap_info* __stdcall __std_tzdb_get_reg_leap_seconds( return reg_ls_data; } -void __stdcall __std_tzdb_delete_reg_leap_seconds(__std_tzdb_registry_leap_info* _Rlsi) noexcept { - delete[] _Rlsi; +void __stdcall __std_tzdb_delete_leap_seconds(__std_tzdb_leap_info* _Info) noexcept { + delete[] _Info; } _NODISCARD void* __stdcall __std_calloc_crt(const size_t count, const size_t size) noexcept { diff --git a/tests/std/include/timezone_data.hpp b/tests/std/include/timezone_data.hpp index 9f72666013..04f37a3055 100644 --- a/tests/std/include/timezone_data.hpp +++ b/tests/std/include/timezone_data.hpp @@ -4,7 +4,6 @@ #pragma once #include #include -#include #include #include diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp index e4f17ac061..17107d6edb 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include #include #include #include @@ -92,7 +93,7 @@ static_assert(!is_clock_v, "no_steady is a clock"); static_assert(!is_clock_v, "no_now is a clock"); void test_is_leap_second(const year_month_day& ymd) { - const auto ls = sys_days{ymd}; + const sys_days ls{ymd}; const auto& leap_seconds = get_tzdb().leap_seconds; assert(find(leap_seconds.begin(), leap_seconds.end(), ls + days{1}) != leap_seconds.end()); assert(get_leap_second_info(utc_clock::from_sys(ls) + days{1}).is_leap_second); @@ -383,7 +384,7 @@ tzdb copy_tzdb() { vector zones; vector links; transform(my_tzdb.zones.begin(), my_tzdb.zones.end(), back_inserter(zones), - [](const auto& _Tz) { return time_zone{_Tz.name()}; }); + [](const auto& tz) { return time_zone{tz.name()}; }); transform(my_tzdb.links.begin(), my_tzdb.links.end(), back_inserter(links), [](const auto& link) { return time_zone_link{link.name(), link.target()}; }); diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index 7b3c5fa40b..3d8d22367c 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -28,7 +28,7 @@ bool test_duration_basic_out(const duration& d, const CharT* expect return ss.str() == expected; } -#define WIDEN(TYPE, STR) get(make_pair(STR, L##STR)); +#define WIDEN(TYPE, STR) get(pair{STR, L##STR}); template bool test_duration_locale_out() { diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index a2fa09ee3d..78ca4ed52a 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -146,9 +146,9 @@ void timezone_names_test() { assert(false); } - // FIXME: add a link to an issue. These may change overtime and might have to be removed from tests. + // See GH-1786. These may change over time and might have to be removed from this test. - // these are some example in which the ICU.dll and IANA database diverge in what they consider a zone or a link + // these are some examples in which the ICU.dll and IANA database diverge in what they consider a zone or a link assert(_Locate_zone_impl(my_tzdb.links, "Atlantic/Faroe") != nullptr); // is a time_zone in IANA assert(_Locate_zone_impl(my_tzdb.zones, "Africa/Addis_Ababa") != nullptr); // is a time_zone_link in IANA assert(_Locate_zone_impl(my_tzdb.links, "PST") != nullptr); // time_zone_link does not exist in IANA From dde4623b98d3af3aeeff0371c130d028d4cb5d33 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 29 Mar 2021 14:26:32 -0700 Subject: [PATCH 08/22] ``: Avoid unnecessary use of concepts (#1787) --- stl/inc/chrono | 221 +++++++++++------- .../test.cpp | 7 +- 2 files changed, 137 insertions(+), 91 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 584b8941a5..e6593d479a 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -212,7 +212,8 @@ namespace chrono { using rep = typename _Duration::rep; using period = typename _Duration::period; - static_assert(_Is_duration_v<_Duration>, "duration must be an instance of std::duration"); + static_assert(_Is_duration_v<_Duration>, + "N4885 [time.point.general]/1 mandates Duration to be a specialization of chrono::duration."); constexpr time_point() = default; @@ -2128,8 +2129,11 @@ namespace chrono { } template - requires _Is_duration_v<_Duration> class hh_mm_ss { + class hh_mm_ss { public: + static_assert(_Is_duration_v<_Duration>, + "N4885 [time.hms.overview]/2 mandates Duration to be a specialization of chrono::duration."); + static constexpr unsigned int fractional_width = [] { auto _Num = _Duration::period::num; constexpr auto _Den = _Duration::period::den; @@ -2907,19 +2911,19 @@ namespace chrono { class zoned_time { private: static_assert(_Is_duration_v<_Duration>, - "N4878 [time.zone.zonedtime.overview]/2 requires Duration to be a specialization of chrono::duration."); + "N4885 [time.zone.zonedtime.overview]/2 mandates Duration to be a specialization of chrono::duration."); using _Traits = zoned_traits<_TimeZonePtr>; public: using duration = common_type_t<_Duration, seconds>; - template + template > zoned_time() : _Zone{_Traits::default_zone()} {} zoned_time(const zoned_time&) = default; zoned_time& operator=(const zoned_time&) = default; - template + template > zoned_time(const sys_time<_Duration>& _Sys) : _Zone{_Traits::default_zone()}, _Tp{_Sys} {} explicit zoned_time(_TimeZonePtr _Tz) noexcept /* strengthened */ : _Zone{_STD move(_Tz)} {} @@ -3405,131 +3409,174 @@ namespace chrono { // [time.clock.cast.sys] - // TRANSITION, GH-395 workaround, is_same_v -> same_as - template - concept _Is_time_point = requires { - typename _Ty::duration; - requires is_same_v, _Ty>; - }; + template + inline constexpr bool _Is_time_point_for_clock = false; template - concept _Convertible_to_sys_time = - is_same_v<_Clock, system_clock> || requires(const time_point<_Clock, _Duration>& _Time) { - { _Clock::to_sys(_Time) } - ->_Is_time_point; - }; - - template - concept _Convertible_from_sys_time = is_same_v<_Clock, system_clock> || requires(const sys_time<_Duration>& _Time) { - { _Clock::from_sys(_Time) } - ->_Is_time_point<_Clock>; - }; + inline constexpr bool _Is_time_point_for_clock, _Clock> = true; template struct clock_time_conversion { - template + template &>()))>> _NODISCARD auto operator()(const time_point<_SourceClock, _Duration>& _Time) const - noexcept(noexcept(_SourceClock::to_sys(_Time))) /* strengthened */ - requires _Convertible_to_sys_time<_SourceClock, _Duration> { + noexcept(noexcept(_SourceClock::to_sys(_Time))) /* strengthened */ { + static_assert(_Is_time_point_for_clock, + "N4885 [time.clock.cast.sys]/2: Mandates: SourceClock::to_sys(t) returns a sys_time"); return _SourceClock::to_sys(_Time); } }; template struct clock_time_conversion<_DestClock, system_clock> { - template + template &>()))>> _NODISCARD auto operator()(const sys_time<_Duration>& _Time) const - noexcept(noexcept(_DestClock::from_sys(_Time))) /* strengthened */ - requires _Convertible_from_sys_time<_DestClock, _Duration> { + noexcept(noexcept(_DestClock::from_sys(_Time))) /* strengthened */ { + static_assert(_Is_time_point_for_clock, + "N4885 [time.clock.cast.sys]/5: Mandates: DestClock::from_sys(t) returns a " + "time_point"); return _DestClock::from_sys(_Time); } }; // [time.clock.cast.utc] - template - concept _Convertible_to_utc_time = - is_same_v<_Clock, - utc_clock> || is_same_v<_Clock, system_clock> || requires(const time_point<_Clock, _Duration>& _Time) { - { _Clock::to_utc(_Time) } - ->_Is_time_point; - }; - - template - concept _Convertible_from_utc_time = - is_same_v<_Clock, utc_clock> || is_same_v<_Clock, system_clock> || requires(const utc_time<_Duration>& _Time) { - { _Clock::from_utc(_Time) } - ->_Is_time_point<_Clock>; - }; - template struct clock_time_conversion { - template + template &>()))>> _NODISCARD auto operator()(const time_point<_SourceClock, _Duration>& _Time) const - noexcept(noexcept(_SourceClock::to_utc(_Time))) /* strengthened */ - requires _Convertible_to_utc_time<_SourceClock, _Duration> { + noexcept(noexcept(_SourceClock::to_utc(_Time))) /* strengthened */ { + static_assert(_Is_time_point_for_clock, + "N4885 [time.clock.cast.utc]/2: Mandates: SourceClock::to_utc(t) returns a utc_time"); return _SourceClock::to_utc(_Time); } }; template struct clock_time_conversion<_DestClock, utc_clock> { - template + template &>()))>> _NODISCARD auto operator()(const utc_time<_Duration>& _Time) const - noexcept(noexcept(_DestClock::from_utc(_Time))) /* strengthened */ - requires _Convertible_from_utc_time<_DestClock, _Duration> { + noexcept(noexcept(_DestClock::from_utc(_Time))) /* strengthened */ { + static_assert(_Is_time_point_for_clock, + "N4885 [time.clock.cast.utc]/5: Mandates: DestClock::from_utc(t) returns a " + "time_point"); return _DestClock::from_utc(_Time); } }; // [time.clock.cast.fn] - // FUNCTION TEMPLATE clock_cast + enum class _Clock_cast_strategy { + _Direct, + _Via_sys, + _Via_utc, + _Via_utc_from_sys, + _Via_sys_from_utc, + _Two_step_ambiguous, + _Three_step_ambiguous, + _None, + }; + + template + inline constexpr bool _Has_two_step_conversion = false; + + template + inline constexpr bool + _Has_two_step_conversion<_Conv1, _Conv2, _Tp, void_t())))>> = true; + + template + inline constexpr bool _Has_three_step_conversion = false; + + template + inline constexpr bool _Has_three_step_conversion<_Conv1, _Conv2, _Conv3, _Tp, + void_t()))))>> = true; + + template + _NODISCARD _CONSTEVAL _Clock_cast_strategy _Choose_clock_cast() noexcept { + using _Tp = const time_point<_SourceClock, _Duration>&; + + if constexpr (is_invocable_v, _Tp>) { + return _Clock_cast_strategy::_Direct; + } else { + constexpr bool _Has_sys = _Has_two_step_conversion< // + clock_time_conversion<_DestClock, system_clock>, // + clock_time_conversion, _Tp>; + + constexpr bool _Has_utc = _Has_two_step_conversion< // + clock_time_conversion<_DestClock, utc_clock>, // + clock_time_conversion, _Tp>; + + if constexpr (_Has_sys && _Has_utc) { + return _Clock_cast_strategy::_Two_step_ambiguous; + } else if constexpr (_Has_sys) { + return _Clock_cast_strategy::_Via_sys; + } else if constexpr (_Has_utc) { + return _Clock_cast_strategy::_Via_utc; + } else { + constexpr bool _Has_utc_from_sys = _Has_three_step_conversion< // + clock_time_conversion<_DestClock, utc_clock>, // + clock_time_conversion, // + clock_time_conversion, _Tp>; + + constexpr bool _Has_sys_from_utc = _Has_three_step_conversion< // + clock_time_conversion<_DestClock, system_clock>, // + clock_time_conversion, // + clock_time_conversion, _Tp>; + + if constexpr (_Has_utc_from_sys && _Has_sys_from_utc) { + return _Clock_cast_strategy::_Three_step_ambiguous; + } else if constexpr (_Has_utc_from_sys) { + return _Clock_cast_strategy::_Via_utc_from_sys; + } else if constexpr (_Has_sys_from_utc) { + return _Clock_cast_strategy::_Via_sys_from_utc; + } else { + return _Clock_cast_strategy::_None; + } + } + } + } + template + inline constexpr auto _Clock_cast_choice = _Choose_clock_cast<_DestClock, _SourceClock, _Duration>(); + + // FUNCTION TEMPLATE clock_cast + template != _Clock_cast_strategy::_None, int> = 0> _NODISCARD auto clock_cast(const time_point<_SourceClock, _Duration>& _Time) { - constexpr bool _Has_direct_conversion = - is_invocable_v, decltype(_Time)>; - - constexpr bool _Utc_from_src = _Convertible_to_utc_time<_SourceClock, _Duration>; - constexpr bool _Sys_from_src = _Convertible_to_sys_time<_SourceClock, _Duration>; - constexpr bool _Dest_from_utc = _Convertible_from_utc_time<_DestClock, _Duration>; - constexpr bool _Dest_from_sys = _Convertible_from_sys_time<_DestClock, _Duration>; - - constexpr bool _Has_utc_conversion = _Dest_from_utc && _Utc_from_src; - constexpr bool _Has_sys_conversion = _Dest_from_sys && _Sys_from_src; - static_assert(_Has_direct_conversion || !(_Has_utc_conversion && _Has_sys_conversion), - "A two-step clock time conversion is required to be unique, either through utc_clock or system_clock, but " - "not both (N4878 [time.clock.cast.fn]/2.)"); - - constexpr bool _Has_sys_utc_conversion = _Dest_from_sys && _Utc_from_src; - constexpr bool _Has_utc_sys_conversion = _Dest_from_utc && _Sys_from_src; - static_assert(_Has_direct_conversion || _Has_utc_conversion || _Has_sys_conversion - || !(_Has_utc_sys_conversion && _Has_sys_utc_conversion), - "A three-step clock time conversion is required to be unique, either utc-to-system or system-to-utc, but " - "not both (N4878 [time.clock.cast.fn]/2)."); + constexpr auto _Strat = _Clock_cast_choice<_DestClock, _SourceClock, _Duration>; - // clang-format off - if constexpr (_Has_direct_conversion) { + if constexpr (_Strat == _Clock_cast_strategy::_Direct) { return clock_time_conversion<_DestClock, _SourceClock>{}(_Time); - } else if constexpr (_Has_utc_conversion) { - return clock_time_conversion<_DestClock, utc_clock>{}( - clock_time_conversion{}(_Time)); - } else if constexpr (_Has_sys_conversion) { + } else if constexpr (_Strat == _Clock_cast_strategy::_Via_sys) { return clock_time_conversion<_DestClock, system_clock>{}( - clock_time_conversion{}(_Time)); - } else if constexpr (_Has_sys_utc_conversion) { - return clock_time_conversion<_DestClock, system_clock>{}( - clock_time_conversion{}( - clock_time_conversion{}(_Time))); - } else if constexpr (_Has_utc_sys_conversion) { + clock_time_conversion{}(_Time)); + } else if constexpr (_Strat == _Clock_cast_strategy::_Via_utc) { return clock_time_conversion<_DestClock, utc_clock>{}( - clock_time_conversion{}( - clock_time_conversion{}(_Time))); + clock_time_conversion{}(_Time)); + } else if constexpr (_Strat == _Clock_cast_strategy::_Via_utc_from_sys) { + return clock_time_conversion<_DestClock, utc_clock>{}( // + clock_time_conversion{}( + clock_time_conversion{}(_Time))); + } else if constexpr (_Strat == _Clock_cast_strategy::_Via_sys_from_utc) { + return clock_time_conversion<_DestClock, system_clock>{}( // + clock_time_conversion{}( + clock_time_conversion{}(_Time))); + } else if constexpr (_Strat == _Clock_cast_strategy::_Two_step_ambiguous) { + static_assert(_Always_false<_Duration>, + "A two-step clock time conversion is required to be unique, " + "either through utc_clock or system_clock, but not both (N4878 [time.clock.cast.fn]/2)."); + } else if constexpr (_Strat == _Clock_cast_strategy::_Three_step_ambiguous) { + static_assert(_Always_false<_Duration>, + "A three-step clock time conversion is required to be unique, " + "either utc-to-system or system-to-utc, but not both (N4878 [time.clock.cast.fn]/2)."); } else { - static_assert(_Always_false<_Duration>, "No clock time conversion exists from source clock type to " - "destination clock type (N4878 [time.clock.cast.fn]/1)."); + static_assert(_Always_false<_Duration>, "should be unreachable"); } - // clang-format on } // [time.parse] diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index 78ca4ed52a..288a798f89 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -147,14 +147,13 @@ void timezone_names_test() { } // See GH-1786. These may change over time and might have to be removed from this test. - - // these are some examples in which the ICU.dll and IANA database diverge in what they consider a zone or a link + // These are some examples in which the ICU.dll and IANA database diverge in what they consider a zone or a link. assert(_Locate_zone_impl(my_tzdb.links, "Atlantic/Faroe") != nullptr); // is a time_zone in IANA assert(_Locate_zone_impl(my_tzdb.zones, "Africa/Addis_Ababa") != nullptr); // is a time_zone_link in IANA assert(_Locate_zone_impl(my_tzdb.links, "PST") != nullptr); // time_zone_link does not exist in IANA - assert(_Locate_zone_impl(my_tzdb.links, "Africa/Asmara") != nullptr); // matches IANA but target is wrong + assert(_Locate_zone_impl(my_tzdb.links, "Africa/Asmara") != nullptr); // matches IANA but target is different assert(_Locate_zone_impl(my_tzdb.links, "Africa/Asmara")->target() == "Africa/Asmera"); // target == Africa/Nairobi - assert(_Locate_zone_impl(my_tzdb.zones, "America/Nuuk") == nullptr); // does not exist in ICU (very rare) + assert(_Locate_zone_impl(my_tzdb.zones, "America/Nuuk") == nullptr); // added in ICU 68, update test when it arrives } void validate_timezone_transitions(const time_zone* tz, const Transition& transition) { From 6bcb4ebf077f4152b82cbe6ff932cfa404d50010 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:14 -0700 Subject: [PATCH 09/22] stl/msbuild: Link advapi32.lib. --- stl/msbuild/stl_atomic_wait/msvcp_atomic_wait.settings.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/stl/msbuild/stl_atomic_wait/msvcp_atomic_wait.settings.targets b/stl/msbuild/stl_atomic_wait/msvcp_atomic_wait.settings.targets index f85149981f..06aee69269 100644 --- a/stl/msbuild/stl_atomic_wait/msvcp_atomic_wait.settings.targets +++ b/stl/msbuild/stl_atomic_wait/msvcp_atomic_wait.settings.targets @@ -69,6 +69,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + From 0c34af6e713b81318eaf7484b94c68eabd9d3325 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:20 -0700 Subject: [PATCH 10/22] static_cast from int to bool to avoid warning C4800. --- stl/inc/chrono | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index e6593d479a..4a80131700 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -3913,7 +3913,8 @@ namespace chrono { _NODISCARD bool _Calculate_hour24() { if (_Hour_12) { - return _Update(_Hour_24, _CHRONO make24(hours{*_Hour_12}, _Ampm.value_or(0)).count()); + return _Update( + _Hour_24, _CHRONO make24(hours{*_Hour_12}, static_cast(_Ampm.value_or(0))).count()); } else { return true; } From 37c43eda0de278cb45672d73b1f52764f0e41593 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:21 -0700 Subject: [PATCH 11/22] Add run_tz_test() to report exceptions and handle old OSes. --- tests/std/include/timezone_data.hpp | 36 +++++++++++++++++++ .../test.cpp | 8 +++-- .../test.cpp | 11 +++--- .../test.cpp | 26 +++++--------- .../test.cpp | 19 ++++------ 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/tests/std/include/timezone_data.hpp b/tests/std/include/timezone_data.hpp index 04f37a3055..d1a69835b1 100644 --- a/tests/std/include/timezone_data.hpp +++ b/tests/std/include/timezone_data.hpp @@ -4,7 +4,12 @@ #pragma once #include #include +#include +#include +#include +#include #include +#include #include using namespace std; @@ -138,3 +143,34 @@ namespace LA { inline constexpr pair Std_to_Day{Std_1, Day_2}; } // namespace LA + +template +void run_tz_test(TestFunction test_function) { + try { +#ifdef MSVC_INTERNAL_TESTING + try { + (void) get_tzdb(); + } catch (const system_error& ex) { + if (ex.code() == error_code{126 /* ERROR_MOD_NOT_FOUND */, system_category()}) { + // Skip testing when we can't load icu.dll on an internal test machine running an older OS. + exit(EXIT_SUCCESS); + } + + throw; // Report any other errors. + } +#endif // MSVC_INTERNAL_TESTING + + test_function(); + + } catch (const system_error& ex) { + cerr << "Test threw system_error: " << ex.what() << "\n"; + cerr << "With error_code: " << ex.code() << "\n"; + assert(false); + } catch (const runtime_error& ex) { + cerr << "Test threw runtime_error: " << ex.what() << "\n"; + assert(false); + } catch (const exception& ex) { + cerr << "Test threw exception: " << ex.what() << "\n"; + assert(false); + } +} diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp index 17107d6edb..8d0e80203e 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp @@ -11,6 +11,8 @@ #include #include +#include + using namespace std; using namespace std::chrono; @@ -392,7 +394,7 @@ tzdb copy_tzdb() { return {my_tzdb.version, move(zones), move(links), my_tzdb.leap_seconds, my_tzdb._All_ls_positive}; } -int main() { +void test() { assert(test_leap_second()); static_assert(test_leap_second()); @@ -472,6 +474,8 @@ int main() { offset += leap.value(); assert(leap._Elapsed() == offset); } +} - return 0; +int main() { + run_tz_test([] { test(); }); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index 3d8d22367c..5fedcd8088 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -12,11 +12,12 @@ #include #include #include -#include #include #include #include +#include + using namespace std; using namespace std::chrono; @@ -1127,9 +1128,11 @@ void test_parse() { parse_wchar(); } - -int main() { +void test() { test_duration_output(); test_parse(); - return 0; +} + +int main() { + run_tz_test([] { test(); }); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index 288a798f89..a84824e67d 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -404,23 +403,16 @@ void timezone_precision_test() { } } -bool test() { - try { - timezone_tzdb_list_test(); - timezone_version_test(); - timezone_names_test(); - timezone_sys_info_test(); - timezone_to_local_test(); - timezone_local_info_test(); - timezone_precision_test(); - } catch (const exception& ex) { - cerr << "Test threw exception: " << ex.what() << "\n"; - assert(false); - } - - return true; +void test() { + timezone_tzdb_list_test(); + timezone_version_test(); + timezone_names_test(); + timezone_sys_info_test(); + timezone_to_local_test(); + timezone_local_info_test(); + timezone_precision_test(); } int main() { - test(); + run_tz_test([] { test(); }); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp index 6d18ade717..6bd031a5e2 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_zoned_time/test.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include @@ -338,18 +336,13 @@ constexpr void zonedtime_constraints_test() { } void test() { - try { - zonedtime_constructor_test(); - zonedtime_operator_test(); - zonedtime_exception_tests(); - zonedtime_traits_test(); - zonedtime_constraints_test(); - } catch (const exception& ex) { - cerr << "Test threw exception: " << ex.what() << "\n"; - assert(false); - } + zonedtime_constructor_test(); + zonedtime_operator_test(); + zonedtime_exception_tests(); + zonedtime_traits_test(); + zonedtime_constraints_test(); } int main() { - test(); + run_tz_test([] { test(); }); } From b0f132d6edd2571a08bb4f174d80e128e8c62e50 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:21 -0700 Subject: [PATCH 12/22] Remove _Invalid_time_string to fix the objsize test. --- stl/inc/chrono | 7 +------ .../std/tests/P0355R7_calendars_and_time_zones_io/test.cpp | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 4a80131700..991cfefda3 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -3655,7 +3655,6 @@ namespace chrono { template static constexpr _Ty _Invalid_time_field{numeric_limits<_Ty>::lowest()}; - static constexpr const char* _Invalid_time_string = "!"; static void _Initialize_time_point(tm& _Tp) { _Tp.tm_sec = _Invalid_time_field; @@ -3673,11 +3672,7 @@ namespace chrono { // Update a field. Ignores invalid values. If _Val already has a value, returns true or false according to // whether the new value matches the current one or not, so that inconsistencies can be detected. - if constexpr (is_same_v<_Ty, string>) { - if (_New == _Invalid_time_string) { - return true; - } - } else { + if constexpr (!is_same_v<_Ty, string>) { if (_New == _Invalid_time_field<_Ty>) { return true; } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index 5fedcd8088..f1bb7aecfe 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -134,7 +134,7 @@ void test_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t p = Parsable{}; if (abbrev) { if constexpr (is_same_v) { - *abbrev = _Time_parse_fields::_Invalid_time_string; + *abbrev = "!"; } else { *abbrev = L"!"; } @@ -164,7 +164,7 @@ void fail_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t p = Parsable{}; if (abbrev) { if constexpr (is_same_v) { - *abbrev = _Time_parse_fields::_Invalid_time_string; + *abbrev = "!"; } else { *abbrev = L"!"; } From fd6ecdb44b72488861ee9d14a01a5aa70553182e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:21 -0700 Subject: [PATCH 13/22] Work around VSO-1303556, an is_constructible_v ICE. --- stl/inc/chrono | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 991cfefda3..61faa392aa 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2929,8 +2929,9 @@ namespace chrono { explicit zoned_time(_TimeZonePtr _Tz) noexcept /* strengthened */ : _Zone{_STD move(_Tz)} {} // clang-format off - template , int> = 0> + template ::value, + int> = 0> // clang-format on explicit zoned_time(string_view _Name) : _Zone{_Traits::locate_zone(_Name)} {} @@ -2941,8 +2942,8 @@ namespace chrono { zoned_time(_TimeZonePtr _Tz, const sys_time<_Duration>& _Sys) : _Zone{_STD move(_Tz)}, _Tp{_Sys} {} // clang-format off - template &>, + template &>::value, int> = 0> // clang-format on zoned_time(string_view _Name, type_identity_t&> _Sys) @@ -2956,8 +2957,8 @@ namespace chrono { : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local)} {} // clang-format off - template &>, + template &>::value, int> = 0> // clang-format on zoned_time(string_view _Name, type_identity_t>& _Local) @@ -2972,8 +2973,8 @@ namespace chrono { : _Zone{_STD move(_Tz)}, _Tp{_Zone->to_sys(_Local, _Choose)} {} // clang-format off - template &, choose>, + template &, choose>::value, int> = 0> // clang-format on zoned_time(string_view _Name, type_identity_t&> _Local, choose _Choose) @@ -2990,17 +2991,23 @@ namespace chrono { _TimeZonePtr _Tz, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt, choose) noexcept /* strengthened */ : zoned_time{_Tz, _Zt} {} - template &>, - int> = 0> + // clang-format off + template &>::value, + int> = 0> + // clang-format on zoned_time(string_view _Name, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt) : zoned_time{_Traits::locate_zone(_Name), _Zt} {} - template &, choose>, - int> = 0> + // clang-format off + template &, choose>::value, + int> = 0> + // clang-format on zoned_time(string_view _Name, const zoned_time<_Duration2, _TimeZonePtr2>& _Zt, choose _Choose) : zoned_time{_Traits::locate_zone(_Name), _Zt, _Choose} {} From aa54dc4f36ff06c1ec13f5cc291bb10ccb0553be Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:22 -0700 Subject: [PATCH 14/22] Fix memory leaks. --- stl/src/tzdb.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/stl/src/tzdb.cpp b/stl/src/tzdb.cpp index 9ba8541bc5..f18642fa9c 100644 --- a/stl/src/tzdb.cpp +++ b/stl/src/tzdb.cpp @@ -421,7 +421,6 @@ void __stdcall __std_tzdb_delete_time_zones(__std_tzdb_time_zones_info* const _I } delete[] _Info->_Names; - _Info->_Names = nullptr; } if (_Info->_Links != nullptr) { @@ -430,8 +429,9 @@ void __stdcall __std_tzdb_delete_time_zones(__std_tzdb_time_zones_info* const _I } delete[] _Info->_Links; - _Info->_Links = nullptr; } + + delete _Info; } } @@ -467,7 +467,8 @@ _NODISCARD __std_tzdb_current_zone_info* __stdcall __std_tzdb_get_current_zone() void __stdcall __std_tzdb_delete_current_zone(__std_tzdb_current_zone_info* const _Info) noexcept { if (_Info) { delete[] _Info->_Tz_name; - _Info->_Tz_name = nullptr; + + delete _Info; } } @@ -547,7 +548,8 @@ _NODISCARD __std_tzdb_sys_info* __stdcall __std_tzdb_get_sys_info( void __stdcall __std_tzdb_delete_sys_info(__std_tzdb_sys_info* const _Info) noexcept { if (_Info) { delete[] _Info->_Abbrev; - _Info->_Abbrev = nullptr; + + delete _Info; } } From 620d71af23fe7f09a81c9c0da9e025a72f816b17 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 30 Mar 2021 22:21:22 -0700 Subject: [PATCH 15/22] Code review feedback. * Remove extra space in comment. * Consistently _CHRONO qualify "near-shadowing". * Remove now() strengthening; utc_clock::now() isn't strengthened. * Cite WP number. --- stl/inc/chrono | 18 +++++++++--------- .../test.cpp | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 61faa392aa..d19f6086d1 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2575,7 +2575,7 @@ namespace chrono { return _Result == _Vec.end() ? nullptr : &*_Result; } - // STRUCT tzdb + // STRUCT tzdb struct tzdb { string version; vector zones; @@ -3174,8 +3174,8 @@ namespace chrono { public: using rep = system_clock::rep; using period = system_clock::period; - using duration = duration; - using time_point = time_point; + using duration = _CHRONO duration; + using time_point = _CHRONO time_point; static constexpr bool is_steady = system_clock::is_steady; _NODISCARD static time_point now() { @@ -3225,13 +3225,13 @@ namespace chrono { public: using rep = system_clock::rep; using period = system_clock::period; - using duration = duration; - using time_point = time_point; + using duration = _CHRONO duration; + using time_point = _CHRONO time_point; static constexpr bool is_steady = system_clock::is_steady; static constexpr seconds _Tai_epoch_adjust{378691210}; - _NODISCARD static time_point now() noexcept(noexcept(utc_clock::now())) { + _NODISCARD static time_point now() { return from_utc(utc_clock::now()); } @@ -3261,13 +3261,13 @@ namespace chrono { public: using rep = system_clock::rep; using period = system_clock::period; - using duration = chrono::duration; - using time_point = chrono::time_point; + using duration = _CHRONO duration; + using time_point = _CHRONO time_point; static constexpr bool is_steady = system_clock::is_steady; static constexpr seconds _Gps_epoch_adjust{-315964809}; - _NODISCARD static time_point now() noexcept(noexcept(utc_clock::now())) { + _NODISCARD static time_point now() { return from_utc(utc_clock::now()); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp index 8d0e80203e..f1db6f7286 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_clocks/test.cpp @@ -254,7 +254,7 @@ void test_file_clock_from_utc(const leap_second& leap) { } void test_utc_clock_from_sys(const leap_second& leap, seconds offset) { - // Generalized from [time.clock.utc.members]/3 Example 1. + // Generalized from N4885 [time.clock.utc.members]/3 Example 1. auto t = leap.date() - 2ns; auto u = utc_clock::from_sys(t); assert(u.time_since_epoch() - t.time_since_epoch() == offset); @@ -371,7 +371,7 @@ void test_clock_cast() { assert(clock_cast(gt) == ft); assert(clock_cast(ft) == ft); - // [time.clock.utc.overview]/1 Example 1 + // N4885 [time.clock.utc.overview]/1 Example 1 assert(clock_cast(sys_seconds{sys_days{1970y / January / 1}}).time_since_epoch() == 0s); assert(clock_cast(sys_seconds{sys_days{2000y / January / 1}}).time_since_epoch() == 946'684'822s); } From 213fced684d0ee97c595a58f18deb06648382701 Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 1 Apr 2021 11:33:20 -0700 Subject: [PATCH 16/22] extract time_since_epoch() --- stl/inc/chrono | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index e6593d479a..03a1d65e80 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2285,9 +2285,10 @@ namespace chrono { template _NODISCARD local_info get_info(const local_time<_Duration>& _Local) const { local_info _Info{}; - _Info.first = _Get_info(_Local.time_since_epoch()); + const auto _Time_since_ep = _Local.time_since_epoch(); + _Info.first = _Get_info(_Time_since_ep); - const sys_seconds _Local_sys{_CHRONO duration_cast(_Local.time_since_epoch())}; + const sys_seconds _Local_sys{_CHRONO duration_cast(_Time_since_ep)}; const auto _Curr_sys = _Local_sys - _Info.first.offset; if (_Info.first.begin != _Min_seconds && _Curr_sys < _Info.first.begin + days{1}) { // get previous transition information From e885fd02f3f60c397d4994e2006ec0ba9b34adfb Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 1 Apr 2021 12:04:30 -0700 Subject: [PATCH 17/22] early returns --- stl/inc/chrono | 54 +++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 03a1d65e80..f868152eab 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2586,11 +2586,13 @@ namespace chrono { _NODISCARD const time_zone* locate_zone(string_view _Tz_name) const { auto _Tz = _Locate_zone_impl(zones, _Tz_name); - if (_Tz == nullptr) { - const auto _Link = _Locate_zone_impl(links, _Tz_name); - if (_Link != nullptr) { - _Tz = _Locate_zone_impl(zones, _Link->target()); - } + if (_Tz != nullptr) { + return _Tz; + } + + const auto _Link = _Locate_zone_impl(links, _Tz_name); + if (_Link != nullptr) { + _Tz = _Locate_zone_impl(zones, _Link->target()); } if (_Tz == nullptr) { @@ -2822,30 +2824,32 @@ namespace chrono { // FUNCTION get_tzdb_list _NODISCARD inline tzdb_list& get_tzdb_list() { auto* _Tzdb_ptr = _Global_tzdb_list.load(); - if (_Tzdb_ptr == nullptr) { - auto _My_tzdb = static_cast(__std_calloc_crt(1, sizeof(tzdb_list))); - if (_My_tzdb == nullptr) { - _Xruntime_error("bad allocation"); // not bad_alloc, see N4878 [time.zone.db.access]/4 - } + if (_Tzdb_ptr != nullptr) { + return *_Tzdb_ptr; + } - _TRY_BEGIN - _STD construct_at(_My_tzdb); - _CATCH(const runtime_error&) - __std_free_crt(_My_tzdb); - _RERAISE; - _CATCH(const exception& _Except) + auto _My_tzdb = static_cast(__std_calloc_crt(1, sizeof(tzdb_list))); + if (_My_tzdb == nullptr) { + _Xruntime_error("bad allocation"); // not bad_alloc, see N4878 [time.zone.db.access]/4 + } + + _TRY_BEGIN + _STD construct_at(_My_tzdb); + _CATCH(const runtime_error&) + __std_free_crt(_My_tzdb); + _RERAISE; + _CATCH(const exception& _Except) #if _HAS_EXCEPTIONS - __std_free_crt(_My_tzdb); - _Xruntime_error(_Except.what()); + __std_free_crt(_My_tzdb); + _Xruntime_error(_Except.what()); #endif // _HAS_EXCEPTIONS - _CATCH_END + _CATCH_END - if (_Global_tzdb_list.compare_exchange_strong(_Tzdb_ptr, _My_tzdb)) { - _Tzdb_ptr = _My_tzdb; - } else { - _STD destroy_at(_My_tzdb); - __std_free_crt(_My_tzdb); - } + if (_Global_tzdb_list.compare_exchange_strong(_Tzdb_ptr, _My_tzdb)) { + _Tzdb_ptr = _My_tzdb; + } else { + _STD destroy_at(_My_tzdb); + __std_free_crt(_My_tzdb); } return *_Tzdb_ptr; From cbadcdf022b166c1893ed83122f84675cbdf800c Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 1 Apr 2021 13:20:44 -0700 Subject: [PATCH 18/22] small restructuring --- stl/inc/chrono | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index f868152eab..6846c234ce 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -4089,9 +4089,10 @@ namespace chrono { const bool _Consistent = _Calculate_hour24(); if (_Consistent) { _Duration_result = duration<_Rep, _Period>::zero(); // TRANSITION: LWG-3536 & GH-1740 + return _Apply_duration_fields(_Duration_result, false); } - return _Consistent && _Apply_duration_fields(_Duration_result, false); + return false; } enum class _LeapSecondRep : unsigned int { From a6b6a2c09f8bcc6a22707548f62a6c71df763377 Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 1 Apr 2021 13:43:08 -0700 Subject: [PATCH 19/22] pull _For_time into an enum template parameter --- stl/inc/chrono | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 6846c234ce..05718e3aa7 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -4011,12 +4011,15 @@ namespace chrono { } } - template - _NODISCARD bool _Apply_duration_fields(_DurationType& _Result, const bool _For_time_point) { - constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); - constexpr bool _Can_rep_min = _Can_represent<_DurationType, minutes>(); - constexpr bool _Can_rep_hr = _Can_represent<_DurationType, hours>(); - constexpr bool _Can_rep_day = _Can_represent<_DurationType, days>(); + enum class _Parse_tp_or_duration { _Time_point, _Duration }; + + template + _NODISCARD bool _Apply_duration_fields(_DurationType& _Result) { + constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); + constexpr bool _Can_rep_min = _Can_represent<_DurationType, minutes>(); + constexpr bool _Can_rep_hr = _Can_represent<_DurationType, hours>(); + constexpr bool _Can_rep_day = _Can_represent<_DurationType, days>(); + constexpr bool _For_time_point = _Parse_type == _Parse_tp_or_duration::_Time_point; const auto _Required{(_For_time_point ? _F_day | _F_mon | _F_year : 0)}; const auto _Optional{(_For_time_point ? _F_wkday : 0) | (_Can_rep_sec ? _F_sec : 0) @@ -4089,7 +4092,8 @@ namespace chrono { const bool _Consistent = _Calculate_hour24(); if (_Consistent) { _Duration_result = duration<_Rep, _Period>::zero(); // TRANSITION: LWG-3536 & GH-1740 - return _Apply_duration_fields(_Duration_result, false); + return _Apply_duration_fields, _Parse_tp_or_duration::_Duration>( + _Duration_result); } return false; @@ -4106,7 +4110,7 @@ namespace chrono { _NODISCARD bool _Make_time_point(_DurationType& _Dur, _LeapSecondRep _Leap) { const bool _Consistent{_Calculate_hour24() && _Calculate_year_fields()}; - if (!_Consistent || !_Apply_duration_fields(_Dur, true)) { + if (!_Consistent || !_Apply_duration_fields<_DurationType, _Parse_tp_or_duration::_Time_point>(_Dur)) { return false; } From e83116e00d294959adedca71fe18f6d566313b62 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 1 Apr 2021 17:58:59 -0700 Subject: [PATCH 20/22] Refactor another early-ish return. --- stl/inc/chrono | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 9f5b7c10bc..f4c0b8b5c9 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2593,13 +2593,12 @@ namespace chrono { const auto _Link = _Locate_zone_impl(links, _Tz_name); if (_Link != nullptr) { _Tz = _Locate_zone_impl(zones, _Link->target()); + if (_Tz != nullptr) { + return _Tz; + } } - if (_Tz == nullptr) { - _Xruntime_error("unable to locate time_zone with given name"); - } - - return _Tz; + _Xruntime_error("unable to locate time_zone with given name"); } _NODISCARD const time_zone* current_zone() const { From c5a46dabb149461a7b662681dd00df24c39e2b92 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 1 Apr 2021 17:59:35 -0700 Subject: [PATCH 21/22] Reorder _Parse_tp_or_duration, deducing _DurationType. --- stl/inc/chrono | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index f4c0b8b5c9..997b3a4136 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -4015,7 +4015,7 @@ namespace chrono { enum class _Parse_tp_or_duration { _Time_point, _Duration }; - template + template <_Parse_tp_or_duration _Parse_type, class _DurationType> _NODISCARD bool _Apply_duration_fields(_DurationType& _Result) { constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); constexpr bool _Can_rep_min = _Can_represent<_DurationType, minutes>(); @@ -4094,8 +4094,7 @@ namespace chrono { const bool _Consistent = _Calculate_hour24(); if (_Consistent) { _Duration_result = duration<_Rep, _Period>::zero(); // TRANSITION: LWG-3536 & GH-1740 - return _Apply_duration_fields, _Parse_tp_or_duration::_Duration>( - _Duration_result); + return _Apply_duration_fields<_Parse_tp_or_duration::_Duration>(_Duration_result); } return false; @@ -4112,7 +4111,7 @@ namespace chrono { _NODISCARD bool _Make_time_point(_DurationType& _Dur, _LeapSecondRep _Leap) { const bool _Consistent{_Calculate_hour24() && _Calculate_year_fields()}; - if (!_Consistent || !_Apply_duration_fields<_DurationType, _Parse_tp_or_duration::_Time_point>(_Dur)) { + if (!_Consistent || !_Apply_duration_fields<_Parse_tp_or_duration::_Time_point>(_Dur)) { return false; } From 20fea0ad2c885fb392861143e2953ad9bcda92ee Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 1 Apr 2021 18:21:24 -0700 Subject: [PATCH 22/22] Test a later leap second. Co-authored-by: MattStephanson <68978048+MattStephanson@users.noreply.github.com> --- .../std/tests/P0355R7_calendars_and_time_zones_io/test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index f1bb7aecfe..daf8868690 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -975,6 +975,12 @@ void parse_timepoints() { utc_seconds ut_ref = utc_clock::from_sys(sys_days{1d / July / 1972y}) - 1s; // leap second insertion test_parse("june 30 23:59:60 1972", "%c", ut); assert(ut == ut_ref); + + // Test a later leap second, where the accumulated offset is greater than 1s. + ut_ref = utc_clock::from_sys(sys_days{1d / July / 1992y}) - 1s; + test_parse("june 30 23:59:60 1992", "%c", ut); + assert(ut == ut_ref); + // not leap-second aware fail_parse("june 30 23:59:60 1972", "%c", st); fail_parse("june 30 23:59:60 1972", "%c", tt);