diff --git a/stl/inc/chrono b/stl/inc/chrono index d62210e67ac..92d380c3d61 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 abd6aeaeb82..a0f1ffb4d8e 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 cd8f13dcaf1..cbcd3153c49 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 ab065abdeec..0e13a44636c 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 04ced25c095..ba6b4bd14aa 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 00000000000..9f726660135 --- /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