diff --git a/azure-devops/create-vmss.ps1 b/azure-devops/create-vmss.ps1 index 64fa971fdc8..a927b1cc164 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 8e8832a925f..9c0fa71b56e 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 de7457d2631..d62210e67ac 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 734d050ecf4..abd6aeaeb82 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 2c7b1b76b33..cd8f13dcaf1 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 f49ff2ff792..ab065abdeec 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 e5866c93af1..3b7270fd45a 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 cc8b5a8096c..e4f17ac061d 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 00000000000..642f530ffad --- /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 00000000000..04ced25c095 --- /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(); +}