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; +}