diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv index d73088687975c..db6491419a5cc 100644 --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -180,7 +180,7 @@ "`P1973R1 `__","LWG","Rename ""_default_init"" Functions, Rev1","Prague","|Complete|","16.0" "`P1976R2 `__","LWG","Fixed-size span construction from dynamic range","Prague","|Complete|","11.0","|ranges|" "`P1981R0 `__","LWG","Rename leap to leap_second","Prague","* *","" -"`P1982R0 `__","LWG","Rename link to time_zone_link","Prague","* *","" +"`P1982R0 `__","LWG","Rename link to time_zone_link","Prague","|Complete|","19.0","|chrono|" "`P1983R0 `__","LWG","Wording for GB301, US296, US292, US291, and US283","Prague","|Complete|","15.0","|ranges|" "`P1994R1 `__","LWG","elements_view needs its own sentinel","Prague","|Complete|","16.0","|ranges|" "`P2002R1 `__","CWG","Defaulted comparison specification cleanups","Prague","* *","" diff --git a/libcxx/docs/Status/SpaceshipProjects.csv b/libcxx/docs/Status/SpaceshipProjects.csv index aaa9278a50c1e..c8221078e9a8d 100644 --- a/libcxx/docs/Status/SpaceshipProjects.csv +++ b/libcxx/docs/Status/SpaceshipProjects.csv @@ -171,10 +171,10 @@ Section,Description,Dependencies,Assignee,Complete | `month_weekday_last `_ | `year_month_weekday `_ | `year_month_weekday_last `_",None,Hristo Hristov,|Complete| -`[time.zone.nonmembers] `_,"`chrono::time_zone`",A ```` implementation,Mark de Wever,|In Progress| +`[time.zone.nonmembers] `_,"`chrono::time_zone`",A ```` implementation,Mark de Wever,|Complete| `[time.zone.zonedtime.nonmembers] `_,"`chrono::zoned_time`",A ```` implementation,Mark de Wever,|In Progress| `[time.zone.leap.nonmembers] `_,"`chrono::time_leap_seconds`",A ```` implementation,Mark de Wever,|In Progress| -`[time.zone.link.nonmembers] `_,"`chrono::time_zone_link`",A ```` implementation,Mark de Wever,|In Progress| +`[time.zone.link.nonmembers] `_,"`chrono::time_zone_link`",A ```` implementation,Mark de Wever,|Complete| - `5.13 Clause 28: Localization library `_,,,, "| `[locale] `_ | `[locale.operators] `_",| remove ops `locale `_,None,Hristo Hristov,|Complete| diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 6ded426640f0d..925fc84e7412d 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -290,6 +290,8 @@ set(files __chrono/steady_clock.h __chrono/system_clock.h __chrono/time_point.h + __chrono/time_zone.h + __chrono/time_zone_link.h __chrono/tzdb.h __chrono/tzdb_list.h __chrono/weekday.h diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h new file mode 100644 index 0000000000000..7d97327a6c8e9 --- /dev/null +++ b/libcxx/include/__chrono/time_zone.h @@ -0,0 +1,86 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP___CHRONO_TIME_ZONE_H +#define _LIBCPP___CHRONO_TIME_ZONE_H + +#include +// Enable the contents of the header only when libc++ was built with experimental features enabled. +#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +# include <__compare/strong_order.h> +# include <__config> +# include <__memory/unique_ptr.h> +# include + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +# if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \ + !defined(_LIBCPP_HAS_NO_LOCALIZATION) + +namespace chrono { + +class _LIBCPP_AVAILABILITY_TZDB time_zone { + _LIBCPP_HIDE_FROM_ABI time_zone() = default; + +public: + class __impl; // public so it can be used by make_unique. + + // The "constructor". + // + // The default constructor is private to avoid the constructor from being + // part of the ABI. Instead use an __ugly_named function as an ABI interface, + // since that gives us the ability to change it in the future. + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI static time_zone __create(unique_ptr<__impl>&& __p); + + _LIBCPP_EXPORTED_FROM_ABI ~time_zone(); + + _LIBCPP_HIDE_FROM_ABI time_zone(time_zone&&) = default; + _LIBCPP_HIDE_FROM_ABI time_zone& operator=(time_zone&&) = default; + + _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI string_view name() const noexcept { return __name(); } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; } + +private: + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view __name() const noexcept; + unique_ptr<__impl> __impl_; +}; + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline bool +operator==(const time_zone& __x, const time_zone& __y) noexcept { + return __x.name() == __y.name(); +} + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline strong_ordering +operator<=>(const time_zone& __x, const time_zone& __y) noexcept { + return __x.name() <=> __y.name(); +} + +} // namespace chrono + +# endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) + // && !defined(_LIBCPP_HAS_NO_LOCALIZATION) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +#endif // _LIBCPP___CHRONO_TIME_ZONE_H diff --git a/libcxx/include/__chrono/time_zone_link.h b/libcxx/include/__chrono/time_zone_link.h new file mode 100644 index 0000000000000..17e915d2677a8 --- /dev/null +++ b/libcxx/include/__chrono/time_zone_link.h @@ -0,0 +1,79 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP___CHRONO_TIME_ZONE_LINK_H +#define _LIBCPP___CHRONO_TIME_ZONE_LINK_H + +#include +// Enable the contents of the header only when libc++ was built with experimental features enabled. +#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +# include <__compare/strong_order.h> +# include <__config> +# include +# include + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +# if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \ + !defined(_LIBCPP_HAS_NO_LOCALIZATION) + +namespace chrono { + +class time_zone_link { +public: + struct __constructor_tag; + _LIBCPP_NODISCARD_EXT + _LIBCPP_HIDE_FROM_ABI explicit time_zone_link(__constructor_tag&&, string_view __name, string_view __target) + : __name_{__name}, __target_{__target} {} + + _LIBCPP_HIDE_FROM_ABI time_zone_link(time_zone_link&&) = default; + _LIBCPP_HIDE_FROM_ABI time_zone_link& operator=(time_zone_link&&) = default; + + _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI string_view name() const noexcept { return __name_; } + _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI string_view target() const noexcept { return __target_; } + +private: + string __name_; + // TODO TZDB instead of the name we can store the pointer to a zone. These + // pointers are immutable. This makes it possible to directly return a + // pointer in the time_zone in the 'locate_zone' function. + string __target_; +}; + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline bool +operator==(const time_zone_link& __x, const time_zone_link& __y) noexcept { + return __x.name() == __y.name(); +} + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline strong_ordering +operator<=>(const time_zone_link& __x, const time_zone_link& __y) noexcept { + return __x.name() <=> __y.name(); +} + +} // namespace chrono + +# endif //_LIBCPP_STD_VER >= 20 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +#endif // _LIBCPP___CHRONO_TIME_ZONE_LINK_H diff --git a/libcxx/include/__chrono/tzdb.h b/libcxx/include/__chrono/tzdb.h index bd7b05d478e50..582172e5df9dd 100644 --- a/libcxx/include/__chrono/tzdb.h +++ b/libcxx/include/__chrono/tzdb.h @@ -16,12 +16,19 @@ // Enable the contents of the header only when libc++ was built with experimental features enabled. #if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) +# include <__chrono/time_zone.h> +# include <__chrono/time_zone_link.h> +# include <__config> # include +# include # if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header # endif +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + _LIBCPP_BEGIN_NAMESPACE_STD # if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \ @@ -29,8 +36,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace chrono { -struct _LIBCPP_AVAILABILITY_TZDB tzdb { +struct tzdb { string version; + vector zones; + vector links; }; } // namespace chrono @@ -40,6 +49,8 @@ struct _LIBCPP_AVAILABILITY_TZDB tzdb { _LIBCPP_END_NAMESPACE_STD +_LIBCPP_POP_MACROS + #endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) #endif // _LIBCPP___CHRONO_TZDB_H diff --git a/libcxx/include/__chrono/tzdb_list.h b/libcxx/include/__chrono/tzdb_list.h index 0494826c01a33..112e04ff2ee6a 100644 --- a/libcxx/include/__chrono/tzdb_list.h +++ b/libcxx/include/__chrono/tzdb_list.h @@ -18,8 +18,9 @@ # include <__availability> # include <__chrono/tzdb.h> +# include <__config> +# include <__fwd/string.h> # include -# include # if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -32,9 +33,18 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace chrono { +// TODO TZDB +// Libc++ recently switched to only export __ugly_names from the dylib. +// Since the library is still experimental the functions in this header +// should be adapted to this new style. The other tzdb headers should be +// evaluated too. + class _LIBCPP_AVAILABILITY_TZDB tzdb_list { public: - _LIBCPP_EXPORTED_FROM_ABI explicit tzdb_list(tzdb&& __tzdb); + class __impl; // public to allow construction in dylib + _LIBCPP_HIDE_FROM_ABI explicit tzdb_list(__impl* __p) : __impl_(__p) { + _LIBCPP_ASSERT_NON_NULL(__impl_ != nullptr, "initialized time_zone without a valid pimpl object"); + } _LIBCPP_EXPORTED_FROM_ABI ~tzdb_list(); tzdb_list(const tzdb_list&) = delete; @@ -46,16 +56,15 @@ class _LIBCPP_AVAILABILITY_TZDB tzdb_list { _LIBCPP_EXPORTED_FROM_ABI const_iterator erase_after(const_iterator __p); - _LIBCPP_EXPORTED_FROM_ABI tzdb& __emplace_front(tzdb&& __tzdb); - _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const_iterator begin() const noexcept; _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const_iterator end() const noexcept; _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const_iterator cbegin() const noexcept; _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const_iterator cend() const noexcept; + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __impl& __implementation() { return *__impl_; } + private: - class __impl; __impl* __impl_; }; diff --git a/libcxx/include/chrono b/libcxx/include/chrono index f8407419c9544..fe73f7c772b99 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -686,6 +686,8 @@ constexpr hours make24(const hours& h, bool is_pm) noexcept; // [time.zone.db], time zone database struct tzdb { // C++20 string version; + vector zones; + vector links; }; class tzdb_list { // C++20 @@ -716,15 +718,34 @@ tzdb_list& get_tzdb_list(); const tzdb& reload_tzdb(); // C++20 string remote_version(); // C++20 -// 25.10.5, class time_zone // C++20 +// 25.10.5, class time_zone // C++20 enum class choose {earliest, latest}; -class time_zone; -bool operator==(const time_zone& x, const time_zone& y) noexcept; -bool operator!=(const time_zone& x, const time_zone& y) noexcept; -bool operator<(const time_zone& x, const time_zone& y) noexcept; -bool operator>(const time_zone& x, const time_zone& y) noexcept; -bool operator<=(const time_zone& x, const time_zone& y) noexcept; -bool operator>=(const time_zone& x, const time_zone& y) noexcept; +class time_zone { + time_zone(time_zone&&) = default; + time_zone& operator=(time_zone&&) = default; + + // unspecified additional constructors + + string_view name() const noexcept; +}; +bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20 +strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20 + +// [time.zone.link], class time_zone_link +class time_zone_link { // C++20 +public: + time_zone_link(time_zone_link&&) = default; + time_zone_link& operator=(time_zone_link&&) = default; + + // unspecified additional constructors + + string_view name() const noexcept; + string_view target() const noexcept; +}; + +bool operator==(const time_zone_link& x, const time_zone_link& y); // C++20 +strong_ordering operator<=>(const time_zone_link& x, const time_zone_link& y); // C++20 + } // chrono namespace std { @@ -842,6 +863,8 @@ constexpr chrono::year operator ""y(unsigned lo #if !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \ !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <__chrono/time_zone.h> +# include <__chrono/time_zone_link.h> # include <__chrono/tzdb.h> # include <__chrono/tzdb_list.h> #endif diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp index 69de4705f3788..ee4b4e29fb750 100644 --- a/libcxx/include/libcxx.imp +++ b/libcxx/include/libcxx.imp @@ -287,6 +287,8 @@ { include: [ "<__chrono/steady_clock.h>", "private", "", "public" ] }, { include: [ "<__chrono/system_clock.h>", "private", "", "public" ] }, { include: [ "<__chrono/time_point.h>", "private", "", "public" ] }, + { include: [ "<__chrono/time_zone.h>", "private", "", "public" ] }, + { include: [ "<__chrono/time_zone_link.h>", "private", "", "public" ] }, { include: [ "<__chrono/tzdb.h>", "private", "", "public" ] }, { include: [ "<__chrono/tzdb_list.h>", "private", "", "public" ] }, { include: [ "<__chrono/weekday.h>", "private", "", "public" ] }, diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 9828c48e0587b..d4b6847a28840 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1155,6 +1155,12 @@ module std_private_chrono_steady_clock [system] { header "__chrono/steady_clock.h" export std_private_chrono_time_point } +module std_private_chrono_time_zone [system] { + header "__chrono/time_zone.h" +} +module std_private_chrono_time_zone_link [system] { + header "__chrono/time_zone_link.h" +} module std_private_chrono_system_clock [system] { header "__chrono/system_clock.h" export std_private_chrono_time_point diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc index 65dc973936c47..109023a3abc25 100644 --- a/libcxx/modules/std/chrono.inc +++ b/libcxx/modules/std/chrono.inc @@ -221,7 +221,11 @@ export namespace std { // [time.zone.timezone], class time_zone using std::chrono::choose; +# endif +# ifdef _LIBCPP_ENABLE_EXPERIMENTAL using std::chrono::time_zone; +# endif +# if 0 // [time.zone.zonedtraits], class template zoned_traits using std::chrono::zoned_traits; @@ -233,10 +237,14 @@ export namespace std { // [time.zone.leap], leap second support using std::chrono::leap_second; +# endif +# ifdef _LIBCPP_ENABLE_EXPERIMENTAL // [time.zone.link], class time_zone_link using std::chrono::time_zone_link; +# endif +# if 0 // [time.format], formatting using std::chrono::local_time_format; # endif diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 44a088663463c..cc6954a7bac37 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -336,7 +336,13 @@ endif() if (LIBCXX_ENABLE_LOCALIZATION AND LIBCXX_ENABLE_FILESYSTEM AND LIBCXX_ENABLE_TIME_ZONE_DATABASE) list(APPEND LIBCXX_EXPERIMENTAL_SOURCES - tz.cpp + include/tzdb/time_zone_link_private.h + include/tzdb/time_zone_private.h + include/tzdb/types_private.h + include/tzdb/tzdb_list_private.h + include/tzdb/tzdb_private.h + time_zone.cpp + tzdb.cpp tzdb_list.cpp ) endif() diff --git a/libcxx/src/include/tzdb/time_zone_link_private.h b/libcxx/src/include/tzdb/time_zone_link_private.h new file mode 100644 index 0000000000000..139237625274d --- /dev/null +++ b/libcxx/src/include/tzdb/time_zone_link_private.h @@ -0,0 +1,27 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H + +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +struct time_zone_link::__constructor_tag {}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_LINK_PRIVATE_H diff --git a/libcxx/src/include/tzdb/time_zone_private.h b/libcxx/src/include/tzdb/time_zone_private.h new file mode 100644 index 0000000000000..039a3b0ffeb7c --- /dev/null +++ b/libcxx/src/include/tzdb/time_zone_private.h @@ -0,0 +1,48 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H + +#include +#include +#include + +#include "types_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +class time_zone::__impl { +public: + explicit _LIBCPP_HIDE_FROM_ABI __impl(string&& __name) : __name_(std::move(__name)) {} + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI string_view __name() const noexcept { return __name_; } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI vector<__tz::__continuation>& __continuations() { return __continuations_; } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const vector<__tz::__continuation>& __continuations() const { + return __continuations_; + } + +private: + string __name_; + // Note the first line has a name + __continuation, the other lines + // are just __continuations. So there is always at least one item in + // the vector. + vector<__tz::__continuation> __continuations_; +}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TIME_ZONE_PRIVATE_H diff --git a/libcxx/src/include/tzdb/types_private.h b/libcxx/src/include/tzdb/types_private.h new file mode 100644 index 0000000000000..4604b9fc88114 --- /dev/null +++ b/libcxx/src/include/tzdb/types_private.h @@ -0,0 +1,106 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef __LIBCPP_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H +#define __LIBCPP_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H + +#include +#include +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +// TODO TZDB +// The helper classes in this header have no constructor but are loaded with +// dedicated parse functions. In the original design this header was public and +// the parsing was done in the dylib. In that design having constructors would +// expand the ABI interface. Since this header is now in the dylib that design +// should be reconsidered. (For now the design is kept as is, in case this +// header needs to be public for unforseen reasons.) + +namespace chrono::__tz { + +// Sun>=8 first Sunday on or after the eighth +// Sun<=25 last Sunday on or before the 25th +struct __constrained_weekday { + /* year_month_day operator()(year __year, month __month);*/ // needed but not implemented + + weekday __weekday; + enum __comparison_t { __le, __ge } __comparison; + day __day; +}; + +// The on field has a few alternative presentations +// 5 the fifth of the month +// lastSun the last Sunday in the month +// lastMon the last Monday in the month +// Sun>=8 first Sunday on or after the eighth +// Sun<=25 last Sunday on or before the 25th +using __on = variant; + +enum class __clock { __local, __standard, __universal }; + +struct __at { + seconds __time{0}; + __tz::__clock __clock{__tz::__clock::__local}; +}; + +struct __save { + seconds __time; + bool __is_dst; +}; + +// The names of the fields match the fields of a Rule. +struct __rule { + year __from; + year __to; + month __in; + __tz::__on __on; + __tz::__at __at; + __tz::__save __save; + string __letters; +}; + +using __rules_storage_type = std::vector>>; // TODO TZDB use flat_map; + +struct __continuation { + // Non-owning link to the RULE entries. + __tz::__rules_storage_type* __rule_database_; + + seconds __stdoff; + + // The RULES is either a SAVE or a NAME. + // The size_t is used as cache. After loading the rules they are + // sorted and remain stable, then an index in the vector can be + // used. + // If this field contains - then standard time always + // applies. This is indicated by the monostate. + using __rules_t = variant; + + __rules_t __rules; + + string __format; + // TODO TZDB the until field can contain more than just a year. + // Parts of the UNTIL, the optional parts are default initialized + // optional __until_; + year __year = chrono::year::min(); + month __in{January}; + __tz::__on __on{chrono::day{1}}; + __tz::__at __at{chrono::seconds{0}, __tz::__clock::__local}; +}; + +} // namespace chrono::__tz + +_LIBCPP_END_NAMESPACE_STD + +#endif // __LIBCPP_SRC_INCLUDE_TZDB_TYPES_PRIVATE_H diff --git a/libcxx/src/include/tzdb/tzdb_list_private.h b/libcxx/src/include/tzdb/tzdb_list_private.h new file mode 100644 index 0000000000000..f43d7d8ea772b --- /dev/null +++ b/libcxx/src/include/tzdb/tzdb_list_private.h @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H + +#include <__mutex/unique_lock.h> +#include + +// When threads are not available the locking is not required. +#ifndef _LIBCPP_HAS_NO_THREADS +# include +#endif + +#include "types_private.h" +#include "tzdb_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +//===----------------------------------------------------------------------===// +// Private API +//===----------------------------------------------------------------------===// + +// The tzdb_list stores a list of "tzdb" entries. +// +// The public tzdb database does not store the RULE entries of the IANA +// database. These entries are considered an implementation detail. Since most +// of the tzdb_list interface is exposed as "a list of tzdb entries" it's not +// possible to use a helper struct that stores a tzdb and the RULE database. +// Instead this class stores these in parallel forward lists. +// +// Since the nodes of a forward_list are stable it's possible to store pointers +// and references to these nodes. +class tzdb_list::__impl { +public: + __impl() { __load_no_lock(); } + + [[nodiscard]] const tzdb& __load() { +#ifndef _LIBCPP_HAS_NO_THREADS + unique_lock __lock{__mutex_}; +#endif + __load_no_lock(); + return __tzdb_.front(); + } + + using const_iterator = tzdb_list::const_iterator; + + const tzdb& front() const noexcept { +#ifndef _LIBCPP_HAS_NO_THREADS + shared_lock __lock{__mutex_}; +#endif + return __tzdb_.front(); + } + + const_iterator erase_after(const_iterator __p) { +#ifndef _LIBCPP_HAS_NO_THREADS + unique_lock __lock{__mutex_}; +#endif + + __rules_.erase_after(std::next(__rules_.cbegin(), std::distance(__tzdb_.cbegin(), __p))); + return __tzdb_.erase_after(__p); + } + + const_iterator begin() const noexcept { +#ifndef _LIBCPP_HAS_NO_THREADS + shared_lock __lock{__mutex_}; +#endif + return __tzdb_.begin(); + } + const_iterator end() const noexcept { + // forward_list::end does not access the list, so no need to take a lock. + return __tzdb_.end(); + } + + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + +private: + // Loads the tzdbs + // pre: The caller ensures the locking, if needed, is done. + void __load_no_lock() { chrono::__init_tzdb(__tzdb_.emplace_front(), __rules_.emplace_front()); } + +#ifndef _LIBCPP_HAS_NO_THREADS + mutable shared_mutex __mutex_; +#endif + forward_list __tzdb_; + + forward_list<__tz::__rules_storage_type> __rules_; +}; + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TZDB_LIST_PRIVATE_H diff --git a/libcxx/src/include/tzdb/tzdb_private.h b/libcxx/src/include/tzdb/tzdb_private.h new file mode 100644 index 0000000000000..8ec3f890ef65c --- /dev/null +++ b/libcxx/src/include/tzdb/tzdb_private.h @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#ifndef _LIBCPP_SRC_INCLUDE_TZDB_TZ_PRIVATE_H +#define _LIBCPP_SRC_INCLUDE_TZDB_TZ_PRIVATE_H + +#include + +#include "types_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules); + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SRC_INCLUDE_TZDB_TZ_PRIVATE_H diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp new file mode 100644 index 0000000000000..b6bf06a116f68 --- /dev/null +++ b/libcxx/src/time_zone.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#include + +#include "include/tzdb/time_zone_private.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI time_zone time_zone::__create(unique_ptr&& __p) { + _LIBCPP_ASSERT_NON_NULL(__p != nullptr, "initialized time_zone without a valid pimpl object"); + time_zone result; + result.__impl_ = std::move(__p); + return result; +} + +_LIBCPP_EXPORTED_FROM_ABI time_zone::~time_zone() = default; + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view time_zone::__name() const noexcept { return __impl_->__name(); } + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tz.cpp b/libcxx/src/tz.cpp deleted file mode 100644 index 4425f0e6b91bd..0000000000000 --- a/libcxx/src/tz.cpp +++ /dev/null @@ -1,146 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html - -#include -#include -#include -#include -#include - -// Contains a parser for the IANA time zone data files. -// -// These files can be found at https://data.iana.org/time-zones/ and are in the -// public domain. Information regarding the input can be found at -// https://data.iana.org/time-zones/tz-how-to.html and -// https://man7.org/linux/man-pages/man8/zic.8.html. -// -// As indicated at https://howardhinnant.github.io/date/tz.html#Installation -// For Windows another file seems to be required -// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml -// This file seems to contain the mapping of Windows time zone name to IANA -// time zone names. -// -// However this article mentions another way to do the mapping on Windows -// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 -// This requires Windows 10 Version 1903, which was released in May of 2019 -// and considered end of life in December 2020 -// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing -// -// TODO TZDB Implement the Windows mapping in tzdb::current_zone - -_LIBCPP_BEGIN_NAMESPACE_STD - -namespace chrono { - -// This function is weak so it can be overriden in the tests. The -// declaration is in the test header test/support/test_tzdb.h -_LIBCPP_WEAK string_view __libcpp_tzdb_directory() { -#if defined(__linux__) - return "/usr/share/zoneinfo/"; -#else -# error "unknown path to the IANA Time Zone Database" -#endif -} - -[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } - -static void __skip_optional_whitespace(istream& __input) { - while (chrono::__is_whitespace(__input.peek())) - __input.get(); -} - -static void __skip_mandatory_whitespace(istream& __input) { - if (!chrono::__is_whitespace(__input.get())) - std::__throw_runtime_error("corrupt tzdb: expected whitespace"); - - chrono::__skip_optional_whitespace(__input); -} - -static void __matches(istream& __input, char __expected) { - if (std::tolower(__input.get()) != __expected) - std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); -} - -static void __matches(istream& __input, string_view __expected) { - for (auto __c : __expected) - if (std::tolower(__input.get()) != __c) - std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); -} - -[[nodiscard]] static string __parse_string(istream& __input) { - string __result; - while (true) { - int __c = __input.get(); - switch (__c) { - case ' ': - case '\t': - case '\n': - __input.unget(); - [[fallthrough]]; - case istream::traits_type::eof(): - if (__result.empty()) - std::__throw_runtime_error("corrupt tzdb: expected a string"); - - return __result; - - default: - __result.push_back(__c); - } - } -} - -static string __parse_version(istream& __input) { - // The first line in tzdata.zi contains - // # version YYYYw - // The parser expects this pattern - // #\s*version\s*\(.*) - // This part is not documented. - chrono::__matches(__input, '#'); - chrono::__skip_optional_whitespace(__input); - chrono::__matches(__input, "version"); - chrono::__skip_mandatory_whitespace(__input); - return chrono::__parse_string(__input); -} - -static tzdb __make_tzdb() { - tzdb __result; - - filesystem::path __root = chrono::__libcpp_tzdb_directory(); - ifstream __tzdata{__root / "tzdata.zi"}; - - __result.version = chrono::__parse_version(__tzdata); - return __result; -} - -//===----------------------------------------------------------------------===// -// Public API -//===----------------------------------------------------------------------===// - -_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { - static tzdb_list __result{chrono::__make_tzdb()}; - return __result; -} - -_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { - if (chrono::remote_version() == chrono::get_tzdb().version) - return chrono::get_tzdb(); - - return chrono::get_tzdb_list().__emplace_front(chrono::__make_tzdb()); -} - -_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { - filesystem::path __root = chrono::__libcpp_tzdb_directory(); - ifstream __tzdata{__root / "tzdata.zi"}; - return chrono::__parse_version(__tzdata); -} - -} // namespace chrono - -_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb.cpp b/libcxx/src/tzdb.cpp new file mode 100644 index 0000000000000..2bb801e48694e --- /dev/null +++ b/libcxx/src/tzdb.cpp @@ -0,0 +1,641 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html + +#include +#include +#include +#include +#include +#include + +#include "include/tzdb/time_zone_link_private.h" +#include "include/tzdb/time_zone_private.h" +#include "include/tzdb/types_private.h" +#include "include/tzdb/tzdb_list_private.h" +#include "include/tzdb/tzdb_private.h" + +// Contains a parser for the IANA time zone data files. +// +// These files can be found at https://data.iana.org/time-zones/ and are in the +// public domain. Information regarding the input can be found at +// https://data.iana.org/time-zones/tz-how-to.html and +// https://man7.org/linux/man-pages/man8/zic.8.html. +// +// As indicated at https://howardhinnant.github.io/date/tz.html#Installation +// For Windows another file seems to be required +// https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml +// This file seems to contain the mapping of Windows time zone name to IANA +// time zone names. +// +// However this article mentions another way to do the mapping on Windows +// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 +// This requires Windows 10 Version 1903, which was released in May of 2019 +// and considered end of life in December 2020 +// https://learn.microsoft.com/en-us/lifecycle/announcements/windows-10-1903-end-of-servicing +// +// TODO TZDB Implement the Windows mapping in tzdb::current_zone + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace chrono { + +// This function is weak so it can be overriden in the tests. The +// declaration is in the test header test/support/test_tzdb.h +_LIBCPP_WEAK string_view __libcpp_tzdb_directory() { +#if defined(__linux__) + return "/usr/share/zoneinfo/"; +#else +# error "unknown path to the IANA Time Zone Database" +#endif +} + +//===----------------------------------------------------------------------===// +// Details +//===----------------------------------------------------------------------===// + +[[nodiscard]] static bool __is_whitespace(int __c) { return __c == ' ' || __c == '\t'; } + +static void __skip_optional_whitespace(istream& __input) { + while (chrono::__is_whitespace(__input.peek())) + __input.get(); +} + +static void __skip_mandatory_whitespace(istream& __input) { + if (!chrono::__is_whitespace(__input.get())) + std::__throw_runtime_error("corrupt tzdb: expected whitespace"); + + chrono::__skip_optional_whitespace(__input); +} + +[[nodiscard]] static bool __is_eol(int __c) { return __c == '\n' || __c == std::char_traits::eof(); } + +static void __skip_line(istream& __input) { + while (!chrono::__is_eol(__input.peek())) { + __input.get(); + } + __input.get(); +} + +static void __skip(istream& __input, char __suffix) { + if (std::tolower(__input.peek()) == __suffix) + __input.get(); +} + +static void __skip(istream& __input, string_view __suffix) { + for (auto __c : __suffix) + if (std::tolower(__input.peek()) == __c) + __input.get(); +} + +static void __matches(istream& __input, char __expected) { + if (std::tolower(__input.get()) != __expected) + std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str()); +} + +static void __matches(istream& __input, string_view __expected) { + for (auto __c : __expected) + if (std::tolower(__input.get()) != __c) + std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str()); +} + +[[nodiscard]] static string __parse_string(istream& __input) { + string __result; + while (true) { + int __c = __input.get(); + switch (__c) { + case ' ': + case '\t': + case '\n': + __input.unget(); + [[fallthrough]]; + case istream::traits_type::eof(): + if (__result.empty()) + std::__throw_runtime_error("corrupt tzdb: expected a string"); + + return __result; + + default: + __result.push_back(__c); + } + } +} + +[[nodiscard]] static int64_t __parse_integral(istream& __input, bool __leading_zero_allowed) { + int64_t __result = __input.get(); + if (__leading_zero_allowed) { + if (__result < '0' || __result > '9') + std::__throw_runtime_error("corrupt tzdb: expected a digit"); + } else { + if (__result < '1' || __result > '9') + std::__throw_runtime_error("corrupt tzdb: expected a non-zero digit"); + } + __result -= '0'; + while (true) { + if (__input.peek() < '0' || __input.peek() > '9') + return __result; + + // In order to avoid possible overflows we limit the accepted range. + // Most values parsed are expected to be very small: + // - 8784 hours in a year + // - 31 days in a month + // - year no real maximum, these values are expected to be less than + // the range of the year type. + // + // However the leapseconds use a seconds after epoch value. Using an + // int would run into an overflow in 2038. By using a 64-bit value + // the range is large enough for the bilions of years. Limiting that + // range slightly to make the code easier is not an issue. + if (__result > (std::numeric_limits::max() / 16)) + std::__throw_runtime_error("corrupt tzdb: integral too large"); + + __result *= 10; + __result += __input.get() - '0'; + } +} + +//===----------------------------------------------------------------------===// +// Calendar +//===----------------------------------------------------------------------===// + +[[nodiscard]] static day __parse_day(istream& __input) { + unsigned __result = chrono::__parse_integral(__input, false); + if (__result > 31) + std::__throw_runtime_error("corrupt tzdb day: value too large"); + return day{__result}; +} + +[[nodiscard]] static weekday __parse_weekday(istream& __input) { + // TZDB allows the shortest unique name. + switch (std::tolower(__input.get())) { + case 'f': + chrono::__skip(__input, "riday"); + return Friday; + + case 'm': + chrono::__skip(__input, "onday"); + return Monday; + + case 's': + switch (std::tolower(__input.get())) { + case 'a': + chrono::__skip(__input, "turday"); + return Saturday; + + case 'u': + chrono::__skip(__input, "nday"); + return Sunday; + } + break; + + case 't': + switch (std::tolower(__input.get())) { + case 'h': + chrono::__skip(__input, "ursday"); + return Thursday; + + case 'u': + chrono::__skip(__input, "esday"); + return Tuesday; + } + break; + case 'w': + chrono::__skip(__input, "ednesday"); + return Wednesday; + } + + std::__throw_runtime_error("corrupt tzdb weekday: invalid name"); +} + +[[nodiscard]] static month __parse_month(istream& __input) { + // TZDB allows the shortest unique name. + switch (std::tolower(__input.get())) { + case 'a': + switch (std::tolower(__input.get())) { + case 'p': + chrono::__skip(__input, "ril"); + return April; + + case 'u': + chrono::__skip(__input, "gust"); + return August; + } + break; + + case 'd': + chrono::__skip(__input, "ecember"); + return December; + + case 'f': + chrono::__skip(__input, "ebruary"); + return February; + + case 'j': + switch (std::tolower(__input.get())) { + case 'a': + chrono::__skip(__input, "nuary"); + return January; + + case 'u': + switch (std::tolower(__input.get())) { + case 'n': + chrono::__skip(__input, 'e'); + return June; + + case 'l': + chrono::__skip(__input, 'y'); + return July; + } + } + break; + + case 'm': + if (std::tolower(__input.get()) == 'a') + switch (std::tolower(__input.get())) { + case 'y': + return May; + + case 'r': + chrono::__skip(__input, "ch"); + return March; + } + break; + + case 'n': + chrono::__skip(__input, "ovember"); + return November; + + case 'o': + chrono::__skip(__input, "ctober"); + return October; + + case 's': + chrono::__skip(__input, "eptember"); + return September; + } + std::__throw_runtime_error("corrupt tzdb month: invalid name"); +} + +[[nodiscard]] static year __parse_year_value(istream& __input) { + bool __negative = __input.peek() == '-'; + if (__negative) [[unlikely]] + __input.get(); + + int64_t __result = __parse_integral(__input, true); + if (__result > static_cast(year::max())) { + if (__negative) + std::__throw_runtime_error("corrupt tzdb year: year is less than the minimum"); + + std::__throw_runtime_error("corrupt tzdb year: year is greater than the maximum"); + } + + return year{static_cast(__negative ? -__result : __result)}; +} + +[[nodiscard]] static year __parse_year(istream& __input) { + if (std::tolower(__input.peek()) != 'm') [[likely]] + return chrono::__parse_year_value(__input); + + __input.get(); + switch (std::tolower(__input.peek())) { + case 'i': + __input.get(); + chrono::__skip(__input, 'n'); + [[fallthrough]]; + + case ' ': + // The m is minimum, even when that is ambiguous. + return year::min(); + + case 'a': + __input.get(); + chrono::__skip(__input, 'x'); + return year::max(); + } + + std::__throw_runtime_error("corrupt tzdb year: expected 'min' or 'max'"); +} + +//===----------------------------------------------------------------------===// +// TZDB fields +//===----------------------------------------------------------------------===// + +[[nodiscard]] static year __parse_to(istream& __input, year __only) { + if (std::tolower(__input.peek()) != 'o') + return chrono::__parse_year(__input); + + __input.get(); + chrono::__skip(__input, "nly"); + return __only; +} + +[[nodiscard]] static __tz::__constrained_weekday::__comparison_t __parse_comparison(istream& __input) { + switch (__input.get()) { + case '>': + chrono::__matches(__input, '='); + return __tz::__constrained_weekday::__ge; + + case '<': + chrono::__matches(__input, '='); + return __tz::__constrained_weekday::__le; + } + std::__throw_runtime_error("corrupt tzdb on: expected '>=' or '<='"); +} + +[[nodiscard]] static __tz::__on __parse_on(istream& __input) { + if (std::isdigit(__input.peek())) + return chrono::__parse_day(__input); + + if (std::tolower(__input.peek()) == 'l') { + chrono::__matches(__input, "last"); + return weekday_last(chrono::__parse_weekday(__input)); + } + + return __tz::__constrained_weekday{ + chrono::__parse_weekday(__input), chrono::__parse_comparison(__input), chrono::__parse_day(__input)}; +} + +[[nodiscard]] static seconds __parse_duration(istream& __input) { + seconds __result{0}; + int __c = __input.peek(); + bool __negative = __c == '-'; + if (__negative) { + __input.get(); + // Negative is either a negative value or a single -. + // The latter means 0 and the parsing is complete. + if (!std::isdigit(__input.peek())) + return __result; + } + + __result += hours(__parse_integral(__input, true)); + if (__input.peek() != ':') + return __negative ? -__result : __result; + + __input.get(); + __result += minutes(__parse_integral(__input, true)); + if (__input.peek() != ':') + return __negative ? -__result : __result; + + __input.get(); + __result += seconds(__parse_integral(__input, true)); + if (__input.peek() != '.') + return __negative ? -__result : __result; + + __input.get(); + (void)__parse_integral(__input, true); // Truncate the digits. + + return __negative ? -__result : __result; +} + +[[nodiscard]] static __tz::__clock __parse_clock(istream& __input) { + switch (__input.get()) { // case sensitive + case 'w': + return __tz::__clock::__local; + case 's': + return __tz::__clock::__standard; + + case 'u': + case 'g': + case 'z': + return __tz::__clock::__universal; + } + + __input.unget(); + return __tz::__clock::__local; +} + +[[nodiscard]] static bool __parse_dst(istream& __input, seconds __offset) { + switch (__input.get()) { // case sensitive + case 's': + return false; + + case 'd': + return true; + } + + __input.unget(); + return __offset != 0s; +} + +[[nodiscard]] static __tz::__at __parse_at(istream& __input) { + return {__parse_duration(__input), __parse_clock(__input)}; +} + +[[nodiscard]] static __tz::__save __parse_save(istream& __input) { + seconds __time = chrono::__parse_duration(__input); + return {__time, chrono::__parse_dst(__input, __time)}; +} + +[[nodiscard]] static string __parse_letters(istream& __input) { + string __result = __parse_string(__input); + // Canonicalize "-" to "" since they are equivalent in the specification. + return __result != "-" ? __result : ""; +} + +[[nodiscard]] static __tz::__continuation::__rules_t __parse_rules(istream& __input) { + int __c = __input.peek(); + // A single - is not a SAVE but a special case. + if (__c == '-') { + __input.get(); + if (chrono::__is_whitespace(__input.peek())) + return monostate{}; + __input.unget(); + return chrono::__parse_save(__input); + } + + if (std::isdigit(__c) || __c == '+') + return chrono::__parse_save(__input); + + return chrono::__parse_string(__input); +} + +[[nodiscard]] static __tz::__continuation __parse_continuation(__tz::__rules_storage_type& __rules, istream& __input) { + __tz::__continuation __result; + + __result.__rule_database_ = std::addressof(__rules); + + // Note STDOFF is specified as + // This field has the same format as the AT and SAVE fields of rule lines; + // These fields have different suffix letters, these letters seem + // not to be used so do not allow any of them. + + __result.__stdoff = chrono::__parse_duration(__input); + chrono::__skip_mandatory_whitespace(__input); + __result.__rules = chrono::__parse_rules(__input); + chrono::__skip_mandatory_whitespace(__input); + __result.__format = chrono::__parse_string(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__year = chrono::__parse_year(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__in = chrono::__parse_month(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__on = chrono::__parse_on(__input); + chrono::__skip_optional_whitespace(__input); + + if (chrono::__is_eol(__input.peek())) + return __result; + __result.__at = __parse_at(__input); + + return __result; +} + +//===----------------------------------------------------------------------===// +// Time Zone Database entries +//===----------------------------------------------------------------------===// + +static string __parse_version(istream& __input) { + // The first line in tzdata.zi contains + // # version YYYYw + // The parser expects this pattern + // #\s*version\s*\(.*) + // This part is not documented. + chrono::__matches(__input, '#'); + chrono::__skip_optional_whitespace(__input); + chrono::__matches(__input, "version"); + chrono::__skip_mandatory_whitespace(__input); + return chrono::__parse_string(__input); +} + +static void __parse_rule(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + string __name = chrono::__parse_string(__input); + + if (__rules.empty() || __rules.back().first != __name) + __rules.emplace_back(__name, vector<__tz::__rule>{}); + + __tz::__rule& __rule = __rules.back().second.emplace_back(); + + chrono::__skip_mandatory_whitespace(__input); + __rule.__from = chrono::__parse_year(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__to = chrono::__parse_to(__input, __rule.__from); + chrono::__skip_mandatory_whitespace(__input); + chrono::__matches(__input, '-'); + chrono::__skip_mandatory_whitespace(__input); + __rule.__in = chrono::__parse_month(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__on = chrono::__parse_on(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__at = __parse_at(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__save = __parse_save(__input); + chrono::__skip_mandatory_whitespace(__input); + __rule.__letters = chrono::__parse_letters(__input); + chrono::__skip_line(__input); +} + +static void __parse_zone(tzdb& __tzdb, __tz::__rules_storage_type& __rules, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + auto __p = std::make_unique(chrono::__parse_string(__input)); + vector<__tz::__continuation>& __continuations = __p->__continuations(); + chrono::__skip_mandatory_whitespace(__input); + + do { + // The first line must be valid, continuations are optional. + __continuations.emplace_back(__parse_continuation(__rules, __input)); + chrono::__skip_line(__input); + chrono::__skip_optional_whitespace(__input); + } while (std::isdigit(__input.peek()) || __input.peek() == '-'); + + __tzdb.zones.emplace_back(time_zone::__create(std::move(__p))); +} + +static void __parse_link(tzdb& __tzdb, istream& __input) { + chrono::__skip_mandatory_whitespace(__input); + string __target = chrono::__parse_string(__input); + chrono::__skip_mandatory_whitespace(__input); + string __name = chrono::__parse_string(__input); + chrono::__skip_line(__input); + + __tzdb.links.emplace_back(time_zone_link::__constructor_tag{}, std::move(__name), std::move(__target)); +} + +static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istream& __input) { + while (true) { + int __c = std::tolower(__input.get()); + + switch (__c) { + case istream::traits_type::eof(): + return; + + case ' ': + case '\t': + case '\n': + break; + + case '#': + chrono::__skip_line(__input); + break; + + case 'r': + chrono::__skip(__input, "ule"); + chrono::__parse_rule(__db, __rules, __input); + break; + + case 'z': + chrono::__skip(__input, "one"); + chrono::__parse_zone(__db, __rules, __input); + break; + + case 'l': + chrono::__skip(__input, "ink"); + chrono::__parse_link(__db, __input); + break; + + default: + std::__throw_runtime_error("corrupt tzdb: unexpected input"); + } + } +} + +void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) { + filesystem::path __root = chrono::__libcpp_tzdb_directory(); + ifstream __tzdata{__root / "tzdata.zi"}; + + __tzdb.version = chrono::__parse_version(__tzdata); + chrono::__parse_tzdata(__tzdb, __rules, __tzdata); + std::ranges::sort(__tzdb.zones); + std::ranges::sort(__tzdb.links); + std::ranges::sort(__rules, {}, [](const auto& p) { return p.first; }); +} + +//===----------------------------------------------------------------------===// +// Public API +//===----------------------------------------------------------------------===// + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_list& get_tzdb_list() { + static tzdb_list __result{new tzdb_list::__impl()}; + return __result; +} + +_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() { + if (chrono::remote_version() == chrono::get_tzdb().version) + return chrono::get_tzdb(); + + return chrono::get_tzdb_list().__implementation().__load(); +} + +_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version() { + filesystem::path __root = chrono::__libcpp_tzdb_directory(); + ifstream __tzdata{__root / "tzdata.zi"}; + return chrono::__parse_version(__tzdata); +} + +} // namespace chrono + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/tzdb_list.cpp b/libcxx/src/tzdb_list.cpp index 7eaaedc6eda79..d3ee8b58f98bf 100644 --- a/libcxx/src/tzdb_list.cpp +++ b/libcxx/src/tzdb_list.cpp @@ -10,76 +10,12 @@ #include -#include <__mutex/unique_lock.h> -#include - -// When threads are not available the locking is not required. -#ifndef _LIBCPP_HAS_NO_THREADS -# include -#endif +#include "include/tzdb/tzdb_list_private.h" _LIBCPP_BEGIN_NAMESPACE_STD namespace chrono { -//===----------------------------------------------------------------------===// -// Private API -//===----------------------------------------------------------------------===// - -class tzdb_list::__impl { -public: - explicit __impl(tzdb&& __tzdb) { __tzdb_.push_front(std::move(__tzdb)); } - - using const_iterator = tzdb_list::const_iterator; - - const tzdb& front() const noexcept { -#ifndef _LIBCPP_HAS_NO_THREADS - shared_lock __lock{__mutex_}; -#endif - return __tzdb_.front(); - } - - const_iterator erase_after(const_iterator __p) { -#ifndef _LIBCPP_HAS_NO_THREADS - unique_lock __lock{__mutex_}; -#endif - return __tzdb_.erase_after(__p); - } - - tzdb& __emplace_front(tzdb&& __tzdb) { -#ifndef _LIBCPP_HAS_NO_THREADS - unique_lock __lock{__mutex_}; -#endif - return __tzdb_.emplace_front(std::move(__tzdb)); - } - - const_iterator begin() const noexcept { -#ifndef _LIBCPP_HAS_NO_THREADS - shared_lock __lock{__mutex_}; -#endif - return __tzdb_.begin(); - } - const_iterator end() const noexcept { - // forward_list::end does not access the list, so no need to take a lock. - return __tzdb_.end(); - } - - const_iterator cbegin() const noexcept { return begin(); } - const_iterator cend() const noexcept { return end(); } - -private: -#ifndef _LIBCPP_HAS_NO_THREADS - mutable shared_mutex __mutex_; -#endif - forward_list __tzdb_; -}; - -//===----------------------------------------------------------------------===// -// Public API -//===----------------------------------------------------------------------===// - -_LIBCPP_EXPORTED_FROM_ABI tzdb_list::tzdb_list(tzdb&& __tzdb) : __impl_{new __impl(std::move(__tzdb))} {} - _LIBCPP_EXPORTED_FROM_ABI tzdb_list::~tzdb_list() { delete __impl_; } _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI const tzdb& tzdb_list::front() const noexcept { @@ -90,10 +26,6 @@ _LIBCPP_EXPORTED_FROM_ABI tzdb_list::const_iterator tzdb_list::erase_after(const return __impl_->erase_after(__p); } -_LIBCPP_EXPORTED_FROM_ABI tzdb& tzdb_list::__emplace_front(tzdb&& __tzdb) { - return __impl_->__emplace_front(std::move(__tzdb)); -} - _LIBCPP_NODISCARD_EXT _LIBCPP_EXPORTED_FROM_ABI tzdb_list::const_iterator tzdb_list::begin() const noexcept { return __impl_->begin(); } diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp index 1f30af06fedd8..8019824046c47 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp @@ -23,6 +23,10 @@ #include "test_macros.h" +// These types have "private" constructors. +extern std::chrono::time_zone tz; +extern std::chrono::time_zone_link link; + void test() { std::chrono::tzdb_list& list = std::chrono::get_tzdb_list(); list.front(); @@ -34,4 +38,17 @@ void test() { std::chrono::get_tzdb_list(); std::chrono::get_tzdb(); std::chrono::remote_version(); + + { + tz.name(); + operator==(tz, tz); + operator<=>(tz, tz); + } + + { + link.name(); + link.target(); + operator==(link, link); + operator<=>(link, link); + } } diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp index ab68b1fb44f96..e9b27559497b7 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp @@ -20,6 +20,10 @@ #include "test_macros.h" +// These types have "private" constructors. +extern std::chrono::time_zone tz; +extern std::chrono::time_zone_link link; + void test() { std::chrono::tzdb_list& list = std::chrono::get_tzdb_list(); list.front(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} @@ -32,4 +36,19 @@ void test() { crno::get_tzdb_list(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} crno::get_tzdb(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} crno::remote_version(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + + { + tz.name(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + operator==(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + operator<=>(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + } + + { + link.name(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + link.target(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + operator==(link, link); + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + operator<=>(link, link); + } } diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/links.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/links.pass.cpp new file mode 100644 index 0000000000000..92d761d46bcce --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/links.pass.cpp @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// Tests the IANA database rules links and operations. +// This is not part of the public tzdb interface. + +#include + +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +static const std::chrono::tzdb& parse(std::string_view input) { + write(input); + return std::chrono::reload_tzdb(); +} + +static void test_exception(std::string_view input, [[maybe_unused]] std::string_view what) { + write(input); + + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD std::chrono::reload_tzdb()); +} + +static void test_invalid() { + test_exception("L", "corrupt tzdb: expected whitespace"); + + test_exception("L ", "corrupt tzdb: expected a string"); + + test_exception("L n", "corrupt tzdb: expected whitespace"); + + test_exception("L n ", "corrupt tzdb: expected a string"); +} + +static void test_link() { + const std::chrono::tzdb& result = parse( + R"( +L z d +l b a +lInK b b +)"); + assert(result.links.size() == 3); + + assert(result.links[0].name() == "a"); + assert(result.links[0].target() == "b"); + + assert(result.links[1].name() == "b"); + assert(result.links[1].target() == "b"); + + assert(result.links[2].name() == "d"); + assert(result.links[2].target() == "z"); +} + +int main(int, const char**) { + test_invalid(); + test_link(); + + return 0; +} diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp new file mode 100644 index 0000000000000..5ae2ed1e91ebe --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp @@ -0,0 +1,565 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// Tests the IANA database rules parsing and operations. +// This is not part of the public tzdb interface. +// The test uses private implementation headers. +// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../../src/include + +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +// headers in the dylib +#include "tzdb/types_private.h" +#include "tzdb/tzdb_private.h" + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +static void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +struct parse_result { + explicit parse_result(std::string_view input) { + write(input); + std::chrono::tzdb tzdb; // result not needed for the tests. + std::chrono::__init_tzdb(tzdb, rules); + } + std::chrono::__tz::__rules_storage_type rules; +}; + +static void test_exception(std::string_view input, [[maybe_unused]] std::string_view what) { + write(input); + + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD std::chrono::reload_tzdb()); +} + +static void test_invalid() { + test_exception("R", "corrupt tzdb: expected whitespace"); + + test_exception("R ", "corrupt tzdb: expected a string"); + + test_exception("R r", "corrupt tzdb: expected whitespace"); + + test_exception("R r x", "corrupt tzdb: expected a digit"); + test_exception("R r +", "corrupt tzdb: expected a digit"); + test_exception("R r mx", "corrupt tzdb year: expected 'min' or 'max'"); + test_exception("R r -32768", "corrupt tzdb year: year is less than the minimum"); + test_exception("R r 32768", "corrupt tzdb year: year is greater than the maximum"); + + test_exception("R r mix", "corrupt tzdb: expected whitespace"); + test_exception("R r 0", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 x", "corrupt tzdb: expected a digit"); + test_exception("R r 0 +", "corrupt tzdb: expected a digit"); + test_exception("R r 0 mx", "corrupt tzdb year: expected 'min' or 'max'"); + + test_exception("R r 0 mix", "corrupt tzdb: expected whitespace"); + test_exception("R r 0 1", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 X", "corrupt tzdb: expected character '-'"); + + test_exception("R r 0 1 -", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 - j", "corrupt tzdb month: invalid name"); + + test_exception("R r 0 1 - Ja", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 - Ja +", "corrupt tzdb weekday: invalid name"); + test_exception("R r 0 1 - Ja 32", "corrupt tzdb day: value too large"); + test_exception("R r 0 1 - Ja l", "corrupt tzdb: expected string 'last'"); + test_exception("R r 0 1 - Ja last", "corrupt tzdb weekday: invalid name"); + test_exception("R r 0 1 - Ja lastS", "corrupt tzdb weekday: invalid name"); + test_exception("R r 0 1 - Ja S", "corrupt tzdb weekday: invalid name"); + test_exception("R r 0 1 - Ja Su", "corrupt tzdb on: expected '>=' or '<='"); + test_exception("R r 0 1 - Ja Su>", "corrupt tzdb: expected character '='"); + test_exception("R r 0 1 - Ja Su<", "corrupt tzdb: expected character '='"); + test_exception("R r 0 1 - Ja Su>=+", "corrupt tzdb: expected a non-zero digit"); + test_exception("R r 0 1 - Ja Su>=0", "corrupt tzdb: expected a non-zero digit"); + test_exception("R r 0 1 - Ja Su>=32", "corrupt tzdb day: value too large"); + + test_exception("R r 0 1 - Ja Su>=31", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 - Ja Su>=31 ", "corrupt tzdb: expected a digit"); + test_exception("R r 0 1 - Ja Su>=31 +", "corrupt tzdb: expected a digit"); + + test_exception("R r 0 1 - Ja Su>=31 1", "corrupt tzdb: expected whitespace"); + test_exception("R r 0 1 - Ja Su>=31 1a", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 - Ja Su>=31 1w 2", "corrupt tzdb: expected whitespace"); + test_exception("R r 0 1 - Ja Su>=31 1w 2a", "corrupt tzdb: expected whitespace"); + + test_exception("R r 0 1 - Ja Su>=31 1w 2s", "corrupt tzdb: expected whitespace"); + test_exception("R r 0 1 - Ja Su>=31 1w 2s ", "corrupt tzdb: expected a string"); +} + +static void test_name() { + parse_result result{ + R"( +R z 0 1 - Ja Su>=31 1w 2s - +rULE z 0 1 - Ja Su>=31 1w 2s - +RuLe z 0 1 - Ja Su>=31 1w 2s - +R a 0 1 - Ja Su>=31 1w 2s - +R a 0 1 - Ja Su>=31 1w 2s - +)"}; + + assert(result.rules.size() == 2); + assert(result.rules[0].first == "a"); + assert(result.rules[0].second.size() == 2); + assert(result.rules[1].first == "z"); + assert(result.rules[1].second.size() == 3); +} + +static void test_from() { + parse_result result{ + R"( +# min abbreviations +R a M 1 - Ja Su>=31 1w 2s - +R a mI 1 - Ja Su>=31 1w 2s - +R a mIN 1 - Ja Su>=31 1w 2s - + +# max abbrviations +R a MA 1 - Ja Su>=31 1w 2s - +R a mAx 1 - Ja Su>=31 1w 2s - + +R a -1000 1 - Ja Su>=31 1w 2s - +R a -100 1 - Ja Su>=31 1w 2s - +R a 0000 1 - Ja Su>=31 1w 2s - +R a 100 1 - Ja Su>=31 1w 2s - +R a 1000 1 - Ja Su>=31 1w 2s - +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 10); + + assert(result.rules[0].second[0].__from == std::chrono::year::min()); + assert(result.rules[0].second[1].__from == std::chrono::year::min()); + assert(result.rules[0].second[2].__from == std::chrono::year::min()); + + assert(result.rules[0].second[3].__from == std::chrono::year::max()); + assert(result.rules[0].second[4].__from == std::chrono::year::max()); + + assert(result.rules[0].second[5].__from == std::chrono::year(-1000)); + assert(result.rules[0].second[6].__from == std::chrono::year(-100)); + assert(result.rules[0].second[7].__from == std::chrono::year(0)); + assert(result.rules[0].second[8].__from == std::chrono::year(100)); + assert(result.rules[0].second[9].__from == std::chrono::year(1000)); +} + +static void test_to() { + parse_result result{ + R"( +# min abbreviations +R a 0 m - Ja Su>=31 1w 2s - +R a 0 mi - Ja Su>=31 1w 2s - +R a 0 min - Ja Su>=31 1w 2s - + +# max abbrviations +R a 0 ma - Ja Su>=31 1w 2s - +R a 0 max - Ja Su>=31 1w 2s - + +R a 0 -1000 - Ja Su>=31 1w 2s - +R a 0 -100 - Ja Su>=31 1w 2s - +R a 0 0000 - Ja Su>=31 1w 2s - +R a 0 100 - Ja Su>=31 1w 2s - +R a 0 1000 - Ja Su>=31 1w 2s - + +# only abbreviations +R a m O - Ja Su>=31 1w 2s - +R a ma oN - Ja Su>=31 1w 2s - +R a -100 onL - Ja Su>=31 1w 2s - +R a 100 oNlY - Ja Su>=31 1w 2s - +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 14); + + assert(result.rules[0].second[0].__to == std::chrono::year::min()); + assert(result.rules[0].second[1].__to == std::chrono::year::min()); + assert(result.rules[0].second[2].__to == std::chrono::year::min()); + + assert(result.rules[0].second[3].__to == std::chrono::year::max()); + assert(result.rules[0].second[4].__to == std::chrono::year::max()); + + assert(result.rules[0].second[5].__to == std::chrono::year(-1000)); + assert(result.rules[0].second[6].__to == std::chrono::year(-100)); + assert(result.rules[0].second[7].__to == std::chrono::year(0)); + assert(result.rules[0].second[8].__to == std::chrono::year(100)); + assert(result.rules[0].second[9].__to == std::chrono::year(1000)); + + assert(result.rules[0].second[10].__to == std::chrono::year::min()); + assert(result.rules[0].second[11].__to == std::chrono::year::max()); + assert(result.rules[0].second[12].__to == std::chrono::year(-100)); + assert(result.rules[0].second[13].__to == std::chrono::year(100)); +} + +static void test_in() { + parse_result result{ + R"( +# All tests in alphabetic order to validate shortest unique abbreviation + +# Shortest abbreviation valid +R s 0 1 - ap Su>=31 1w 2s - +R s 0 1 - au Su>=31 1w 2s - +R s 0 1 - d Su>=31 1w 2s - +R s 0 1 - f Su>=31 1w 2s - +R s 0 1 - ja Su>=31 1w 2s - +R s 0 1 - jul Su>=31 1w 2s - +R s 0 1 - jun Su>=31 1w 2s - +R s 0 1 - May Su>=31 1w 2s - +R s 0 1 - mar Su>=31 1w 2s - +R s 0 1 - n Su>=31 1w 2s - +R s 0 1 - o Su>=31 1w 2s - +R s 0 1 - s Su>=31 1w 2s - + +# 3 letter abbreviation +R a 0 1 - APR Su>=31 1w 2s - +R a 0 1 - AUG Su>=31 1w 2s - +R a 0 1 - DEC Su>=31 1w 2s - +R a 0 1 - FEB Su>=31 1w 2s - +R a 0 1 - JAN Su>=31 1w 2s - +R a 0 1 - JUL Su>=31 1w 2s - +R a 0 1 - JUN Su>=31 1w 2s - +R a 0 1 - MAY Su>=31 1w 2s - +R a 0 1 - MAR Su>=31 1w 2s - +R a 0 1 - NOV Su>=31 1w 2s - +R a 0 1 - OCT Su>=31 1w 2s - +R a 0 1 - SEP Su>=31 1w 2s - + +# Full names +R f 0 1 - ApRiL Su>=31 1w 2s - +R f 0 1 - AuGuSt Su>=31 1w 2s - +R f 0 1 - DeCeMber Su>=31 1w 2s - +R f 0 1 - FeBrUary Su>=31 1w 2s - +R f 0 1 - JaNuAry Su>=31 1w 2s - +R f 0 1 - JuLy Su>=31 1w 2s - +R f 0 1 - JuNe Su>=31 1w 2s - +R f 0 1 - MaY Su>=31 1w 2s - +R f 0 1 - MaRch Su>=31 1w 2s - +R f 0 1 - NoVemBeR Su>=31 1w 2s - +R f 0 1 - OcTobEr Su>=31 1w 2s - +R f 0 1 - SePteMbEr Su>=31 1w 2s - +)"}; + + assert(result.rules.size() == 3); + for (std::size_t i = 0; i < result.rules.size(); ++i) { + assert(result.rules[i].second.size() == 12); + + assert(result.rules[i].second[0].__in == std::chrono::April); + assert(result.rules[i].second[1].__in == std::chrono::August); + assert(result.rules[i].second[2].__in == std::chrono::December); + assert(result.rules[i].second[3].__in == std::chrono::February); + assert(result.rules[i].second[4].__in == std::chrono::January); + assert(result.rules[i].second[5].__in == std::chrono::July); + assert(result.rules[i].second[6].__in == std::chrono::June); + assert(result.rules[i].second[7].__in == std::chrono::May); + assert(result.rules[i].second[8].__in == std::chrono::March); + assert(result.rules[i].second[9].__in == std::chrono::November); + assert(result.rules[i].second[10].__in == std::chrono::October); + assert(result.rules[i].second[11].__in == std::chrono::September); + } +}; + +static void test_on_day() { + parse_result result{ + R"( +# The parser does not validate the day as valid day of month +R a 0 1 - Fe 1 1w 2s - +R a 0 1 - Fe 10 1w 2s - +R a 0 1 - Fe 20 1w 2s - +R a 0 1 - Fe 30 1w 2s - +R a 0 1 - Fe 31 1w 2s - +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 5); + assert(std::get(result.rules[0].second[0].__on) == std::chrono::day(1)); + assert(std::get(result.rules[0].second[1].__on) == std::chrono::day(10)); + assert(std::get(result.rules[0].second[2].__on) == std::chrono::day(20)); + assert(std::get(result.rules[0].second[3].__on) == std::chrono::day(30)); + assert(std::get(result.rules[0].second[4].__on) == std::chrono::day(31)); +} + +static void test_on_last() { + parse_result result{ + R"( +# All tests in alphabetic order to validate shortest unique abbreviation + +# Shortest abbreviation valid +R s 0 1 - Ja lastF 1w 2s - +R s 0 1 - Ja lastM 1w 2s - +R s 0 1 - Ja lastSa 1w 2s - +R s 0 1 - Ja lastSu 1w 2s - +R s 0 1 - Ja lastTh 1w 2s - +R s 0 1 - Ja lastTu 1w 2s - +R s 0 1 - Ja lastW 1w 2s - + +# 3 letter abbreviation +R a 0 1 - Ja lastFri 1w 2s - +R a 0 1 - Ja lastMon 1w 2s - +R a 0 1 - Ja lastSat 1w 2s - +R a 0 1 - Ja lastSun 1w 2s - +R a 0 1 - Ja lastThu 1w 2s - +R a 0 1 - Ja lastTue 1w 2s - +R a 0 1 - Ja lastWed 1w 2s - + +# Full names +R f 0 1 - Ja lastFriday 1w 2s - +R f 0 1 - Ja lastMonday 1w 2s - +R f 0 1 - Ja lastSaturday 1w 2s - +R f 0 1 - Ja lastSunday 1w 2s - +R f 0 1 - Ja lastThursday 1w 2s - +R f 0 1 - Ja lastTuesday 1w 2s - +R f 0 1 - Ja lastWednesday 1w 2s - +)"}; + + assert(result.rules.size() == 3); + for (std::size_t i = 0; i < result.rules.size(); ++i) { + assert(result.rules[i].second.size() == 7); + + assert(std::get(result.rules[i].second[0].__on) == + std::chrono::weekday_last(std::chrono::Friday)); + assert(std::get(result.rules[i].second[1].__on) == + std::chrono::weekday_last(std::chrono::Monday)); + assert(std::get(result.rules[i].second[2].__on) == + std::chrono::weekday_last(std::chrono::Saturday)); + assert(std::get(result.rules[i].second[3].__on) == + std::chrono::weekday_last(std::chrono::Sunday)); + assert(std::get(result.rules[i].second[4].__on) == + std::chrono::weekday_last(std::chrono::Thursday)); + assert(std::get(result.rules[i].second[5].__on) == + std::chrono::weekday_last(std::chrono::Tuesday)); + assert(std::get(result.rules[i].second[6].__on) == + std::chrono::weekday_last(std::chrono::Wednesday)); + } +} + +static void test_on_constrain() { + parse_result result{ + R"( +# Shortest abbreviation valid +R s 0 1 - Ja F>=1 1w 2s - +R s 0 1 - Ja M<=1 1w 2s - +R s 0 1 - Ja Sa>=31 1w 2s - +R s 0 1 - Ja Su<=31 1w 2s - +R s 0 1 - Ja Th>=10 1w 2s - +R s 0 1 - Ja Tu<=20 1w 2s - +R s 0 1 - Ja W>=30 1w 2s - + +# 3 letter abbreviation +R a 0 1 - Ja Fri>=1 1w 2s - +R a 0 1 - Ja Mon<=1 1w 2s - +R a 0 1 - Ja Sat>=31 1w 2s - +R a 0 1 - Ja Sun<=31 1w 2s - +R a 0 1 - Ja Thu>=10 1w 2s - +R a 0 1 - Ja Tue<=20 1w 2s - +R a 0 1 - Ja Wed>=30 1w 2s - + +# Full names +R f 0 1 - Ja Friday>=1 1w 2s - +R f 0 1 - Ja Monday<=1 1w 2s - +R f 0 1 - Ja Saturday>=31 1w 2s - +R f 0 1 - Ja Sunday<=31 1w 2s - +R f 0 1 - Ja Thursday>=10 1w 2s - +R f 0 1 - Ja Tuesday<=20 1w 2s - +R f 0 1 - Ja Wednesday>=30 1w 2s - + +)"}; + + std::chrono::__tz::__constrained_weekday r; + assert(result.rules.size() == 3); + for (std::size_t i = 0; i < result.rules.size(); ++i) { + assert(result.rules[i].second.size() == 7); + + r = std::get(result.rules[i].second[0].__on); + assert(r.__weekday == std::chrono::Friday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(1)); + + r = std::get(result.rules[i].second[1].__on); + assert(r.__weekday == std::chrono::Monday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le); + assert(r.__day == std::chrono::day(1)); + + r = std::get(result.rules[i].second[2].__on); + assert(r.__weekday == std::chrono::Saturday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(31)); + + r = std::get(result.rules[i].second[3].__on); + assert(r.__weekday == std::chrono::Sunday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le); + assert(r.__day == std::chrono::day(31)); + + r = std::get(result.rules[i].second[4].__on); + assert(r.__weekday == std::chrono::Thursday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(10)); + + r = std::get(result.rules[i].second[5].__on); + assert(r.__weekday == std::chrono::Tuesday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le); + assert(r.__day == std::chrono::day(20)); + + r = std::get(result.rules[i].second[6].__on); + assert(r.__weekday == std::chrono::Wednesday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(30)); + } +} + +static void test_on() { + test_on_day(); + test_on_last(); + test_on_constrain(); +} + +static void test_at() { + parse_result result{ + R"( +# Based on the examples in the man page. +# Note the input is not expected to have fractional seconds, they are truncated. +R a 0 1 - Ja Su>=31 2w 2s - +R a 0 1 - Ja Su>=31 2:00s 2s - +R a 0 1 - Ja Su>=31 01:28:14u 2s - +R a 0 1 - Ja Su>=31 00:19:32.10g 2s - +R a 0 1 - Ja Su>=31 12:00z 2s - +R a 0 1 - Ja Su>=31 15:00 2s - +R a 0 1 - Ja Su>=31 24:00 2s - +R a 0 1 - Ja Su>=31 260:00 2s - +R a 0 1 - Ja Su>=31 -2:30 2s - +R a 0 1 - Ja Su>=31 - 2s - +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 10); + + assert(result.rules[0].second[0].__at.__time == std::chrono::hours(2)); + assert(result.rules[0].second[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(result.rules[0].second[1].__at.__time == std::chrono::hours(2)); + assert(result.rules[0].second[1].__at.__clock == std::chrono::__tz::__clock::__standard); + + assert(result.rules[0].second[2].__at.__time == + std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14)); + assert(result.rules[0].second[2].__at.__clock == std::chrono::__tz::__clock::__universal); + + assert(result.rules[0].second[3].__at.__time == std::chrono::minutes(19) + std::chrono::seconds(32)); + assert(result.rules[0].second[3].__at.__clock == std::chrono::__tz::__clock::__universal); + + assert(result.rules[0].second[4].__at.__time == std::chrono::hours(12)); + assert(result.rules[0].second[4].__at.__clock == std::chrono::__tz::__clock::__universal); + + assert(result.rules[0].second[5].__at.__time == std::chrono::hours(15)); + assert(result.rules[0].second[5].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(result.rules[0].second[6].__at.__time == std::chrono::hours(24)); + assert(result.rules[0].second[6].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(result.rules[0].second[7].__at.__time == std::chrono::hours(260)); + assert(result.rules[0].second[7].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(result.rules[0].second[8].__at.__time == -(std::chrono::hours(2) + std::chrono::minutes(30))); + assert(result.rules[0].second[8].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(result.rules[0].second[9].__at.__time == std::chrono::hours(0)); // The man page expresses it in hours + assert(result.rules[0].second[9].__at.__clock == std::chrono::__tz::__clock::__local); +} + +static void test_save() { + parse_result result{ + R"( +R a 0 1 - Ja Su>=31 1w 2d - +R a 0 1 - Ja Su>=31 1w 2:00s - +R a 0 1 - Ja Su>=31 1w 0 - +R a 0 1 - Ja Su>=31 1w 0:00:01 - +R a 0 1 - Ja Su>=31 1w -0:00:01 - +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 5); + + assert(result.rules[0].second[0].__save.__time == std::chrono::hours(2)); + assert(result.rules[0].second[0].__save.__is_dst == true); + + assert(result.rules[0].second[1].__save.__time == std::chrono::hours(2)); + assert(result.rules[0].second[1].__save.__is_dst == false); + + assert(result.rules[0].second[2].__save.__time == std::chrono::hours(0)); + assert(result.rules[0].second[2].__save.__is_dst == false); + + assert(result.rules[0].second[3].__save.__time == std::chrono::seconds(1)); + assert(result.rules[0].second[3].__save.__is_dst == true); + + assert(result.rules[0].second[4].__save.__time == -std::chrono::seconds(1)); + assert(result.rules[0].second[4].__save.__is_dst == true); +} + +static void test_letter() { + parse_result result{ + R"( +R a 0 1 - Ja Su>=31 1w 2s - +R a 0 1 - Ja Su>=31 1w 2s a +R a 0 1 - Ja Su>=31 1w 2s abc +)"}; + + assert(result.rules.size() == 1); + assert(result.rules[0].second.size() == 3); + + assert(result.rules[0].second[0].__letters == ""); + assert(result.rules[0].second[1].__letters == "a"); + assert(result.rules[0].second[2].__letters == "abc"); +} + +int main(int, const char**) { + test_invalid(); + test_name(); + test_from(); + test_to(); + test_in(); + test_on(); + test_at(); + test_save(); + test_letter(); + + return 0; +} diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/zones.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/zones.pass.cpp new file mode 100644 index 0000000000000..8571e0d05ebbd --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/zones.pass.cpp @@ -0,0 +1,380 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// Tests the IANA database zones parsing and operations. +// This is not part of the public tzdb interface. +// The test uses private implementation headers. +// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../../src/include + +#include +#include +#include +#include +#include +#include + +#include "assert_macros.h" +#include "concat_macros.h" +#include "filesystem_test_helper.h" +#include "test_tzdb.h" + +// headers in the dylib +#include "tzdb/types_private.h" +#include "tzdb/tzdb_private.h" +#include "tzdb/time_zone_private.h" + +scoped_test_env env; +[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo"); +const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi"); + +std::string_view std::chrono::__libcpp_tzdb_directory() { + static std::string result = dir.string(); + return result; +} + +static void write(std::string_view input) { + static int version = 0; + + std::ofstream f{file}; + f << "# version " << version++ << '\n'; + f.write(input.data(), input.size()); +} + +static const std::chrono::tzdb& parse(std::string_view input) { + write(input); + return std::chrono::reload_tzdb(); +} + +static const std::vector& continuations(const std::chrono::time_zone& time_zone) { + return time_zone.__implementation().__continuations(); +} + +static void test_exception(std::string_view input, [[maybe_unused]] std::string_view what) { + write(input); + + TEST_VALIDATE_EXCEPTION( + std::runtime_error, + [&]([[maybe_unused]] const std::runtime_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + }, + TEST_IGNORE_NODISCARD std::chrono::reload_tzdb()); +} + +static void test_invalid() { + test_exception("Z", "corrupt tzdb: expected whitespace"); + + test_exception("Z ", "corrupt tzdb: expected a string"); + + test_exception("Z n", "corrupt tzdb: expected whitespace"); + + test_exception("Z n ", "corrupt tzdb: expected a digit"); + test_exception("Z n x", "corrupt tzdb: expected a digit"); + test_exception("Z n +", "corrupt tzdb: expected a digit"); + + test_exception("Z n 0", "corrupt tzdb: expected whitespace"); + + test_exception("Z n 0 ", "corrupt tzdb: expected a string"); + + test_exception("Z n 0 r", "corrupt tzdb: expected whitespace"); + + test_exception("Z n 0 r ", "corrupt tzdb: expected a string"); +} + +static void test_name() { + const std::chrono::tzdb& result = parse( + R"( +Z n 0 r f +)"); + assert(result.zones.size() == 1); + assert(result.zones[0].name() == "n"); +} + +static void test_stdoff() { + const std::chrono::tzdb& result = parse( + R"( +# Based on the examples in the man page. +# Note the input is not expected to have fractional seconds, they are truncated. +Zo na 2 r f +Zon nb 2:00 r f +Zone nc 01:28:14 r f +zONE nd 00:19:32.10 r f +zoNe ne 12:00 r f +Z nf 15:00 r f +Z ng 24:00 r f +Z nh 260:00 r f +Z ni -2:30 r f +Z nj - r f +)"); + + assert(result.zones.size() == 10); + for (std::size_t i = 0; i < result.zones.size(); ++i) + assert(continuations(result.zones[0]).size() == 1); + + assert(continuations(result.zones[0])[0].__stdoff == std::chrono::hours(2)); + assert(continuations(result.zones[1])[0].__stdoff == std::chrono::hours(2)); + assert(continuations(result.zones[2])[0].__stdoff == + std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14)); + assert(continuations(result.zones[3])[0].__stdoff == std::chrono::minutes(19) + std::chrono::seconds(32)); + assert(continuations(result.zones[4])[0].__stdoff == std::chrono::hours(12)); + assert(continuations(result.zones[5])[0].__stdoff == std::chrono::hours(15)); + assert(continuations(result.zones[6])[0].__stdoff == std::chrono::hours(24)); + assert(continuations(result.zones[7])[0].__stdoff == std::chrono::hours(260)); + assert(continuations(result.zones[8])[0].__stdoff == -(std::chrono::hours(2) + std::chrono::minutes(30))); + assert(continuations(result.zones[9])[0].__stdoff == std::chrono::hours(0)); // The man page expresses it in hours +} + +static void test_rules() { + const std::chrono::tzdb& result = parse( + R"( +Z na 0 - f +Z nb 0 r f +Z nc 0 2d f +Z nd 0 2:00s f +Z ne 0 0 f +Z nf 0 0:00:01 f +Z ng 0 -0:00:01 f +)"); + + assert(result.zones.size() == 7); + for (std::size_t i = 0; i < result.zones.size(); ++i) + assert(continuations(result.zones[0]).size() == 1); + + assert(std::holds_alternative(continuations(result.zones[0])[0].__rules)); + assert(std::get(continuations(result.zones[1])[0].__rules) == "r"); + + assert(std::get(continuations(result.zones[2])[0].__rules).__time == + std::chrono::hours(2)); + assert(std::get(continuations(result.zones[2])[0].__rules).__is_dst == true); + + assert(std::get(continuations(result.zones[3])[0].__rules).__time == + std::chrono::hours(2)); + assert(std::get(continuations(result.zones[3])[0].__rules).__is_dst == false); + + assert(std::get(continuations(result.zones[4])[0].__rules).__time == + std::chrono::hours(0)); + assert(std::get(continuations(result.zones[4])[0].__rules).__is_dst == false); + + assert(std::get(continuations(result.zones[5])[0].__rules).__time == + std::chrono::seconds(1)); + assert(std::get(continuations(result.zones[5])[0].__rules).__is_dst == true); + + assert(std::get(continuations(result.zones[6])[0].__rules).__time == + -std::chrono::seconds(1)); + assert(std::get(continuations(result.zones[6])[0].__rules).__is_dst == true); +} + +static void test_format() { + const std::chrono::tzdb& result = parse( + R"( +Z n 0 r f +)"); + assert(result.zones.size() == 1); + assert(continuations(result.zones[0]).size() == 1); + assert(continuations(result.zones[0])[0].__format == "f"); +} + +static void test_until() { + const std::chrono::tzdb& result = parse( + R"( +Z na 0 r f +Z nb 0 r f 1000 +Z nc 0 r f -1000 N +Z nd 0 r f ma S 31 +Z ne 0 r f 0 jA LASTw +Z nf 0 r f -42 jUN m<=1 +Z ng 0 r f 42 jul Su>=12 +Z nh 0 r f 42 JUl 1 2w +Z ni 0 r f 42 July 1 01:28:14u +Z nj 0 r f 42 Jul 1 - +)"); + assert(result.zones.size() == 10); + for (std::size_t i = 0; i < result.zones.size(); ++i) + assert(continuations(result.zones[0]).size() == 1); + + std::chrono::__tz::__constrained_weekday r; + + assert(continuations(result.zones[0])[0].__year == std::chrono::year::min()); + assert(continuations(result.zones[0])[0].__in == std::chrono::January); + assert(std::get(continuations(result.zones[0])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[1])[0].__year == std::chrono::year(1000)); + assert(continuations(result.zones[1])[0].__in == std::chrono::January); + assert(std::get(continuations(result.zones[1])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[1])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[1])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[2])[0].__year == std::chrono::year(-1000)); + assert(continuations(result.zones[2])[0].__in == std::chrono::November); + assert(std::get(continuations(result.zones[2])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[2])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[2])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[3])[0].__year == std::chrono::year::max()); + assert(continuations(result.zones[3])[0].__in == std::chrono::September); + assert(std::get(continuations(result.zones[3])[0].__on) == std::chrono::day(31)); + assert(continuations(result.zones[3])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[3])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[4])[0].__year == std::chrono::year(0)); + assert(continuations(result.zones[4])[0].__in == std::chrono::January); + assert(std::get(continuations(result.zones[4])[0].__on) == + std::chrono::weekday_last{std::chrono::Wednesday}); + assert(continuations(result.zones[4])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[4])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[5])[0].__year == std::chrono::year(-42)); + assert(continuations(result.zones[5])[0].__in == std::chrono::June); + r = std::get(continuations(result.zones[5])[0].__on); + assert(r.__weekday == std::chrono::Monday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le); + assert(r.__day == std::chrono::day(1)); + assert(continuations(result.zones[5])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[5])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[6])[0].__year == std::chrono::year(42)); + assert(continuations(result.zones[6])[0].__in == std::chrono::July); + r = std::get(continuations(result.zones[6])[0].__on); + assert(r.__weekday == std::chrono::Sunday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(12)); + assert(continuations(result.zones[6])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[6])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[7])[0].__year == std::chrono::year(42)); + assert(continuations(result.zones[7])[0].__in == std::chrono::July); + assert(std::get(continuations(result.zones[7])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[7])[0].__at.__time == std::chrono::hours(2)); + assert(continuations(result.zones[7])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[8])[0].__year == std::chrono::year(42)); + assert(continuations(result.zones[8])[0].__in == std::chrono::July); + assert(std::get(continuations(result.zones[8])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[8])[0].__at.__time == + std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14)); + assert(continuations(result.zones[8])[0].__at.__clock == std::chrono::__tz::__clock::__universal); + + assert(continuations(result.zones[9])[0].__year == std::chrono::year(42)); + assert(continuations(result.zones[9])[0].__in == std::chrono::July); + assert(std::get(continuations(result.zones[9])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[9])[0].__at.__time == std::chrono::hours(0)); // The man page expresses it in hours + assert(continuations(result.zones[9])[0].__at.__clock == std::chrono::__tz::__clock::__local); +} + +static void test_continuation() { + const std::chrono::tzdb& result = parse( + R"( +Z na 0 r f +0 r f 1000 +0 r f -1000 N +0 r f ma S 31 +0 r f 0 Ja lastW +0 r f -42 Jun M<=1 +0 r f 42 Jul Su>=12 +0 r f 42 Jul 1 2w +0 r f 42 Jul 1 01:28:14u +0 r f 42 Jul 1 - +)"); + + assert(result.zones.size() == 1); + assert(continuations(result.zones[0]).size() == 10); + + std::chrono::__tz::__constrained_weekday r; + + assert(continuations(result.zones[0])[0].__year == std::chrono::year::min()); + assert(continuations(result.zones[0])[0].__in == std::chrono::January); + assert(std::get(continuations(result.zones[0])[0].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[0].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[0].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[1].__year == std::chrono::year(1000)); + assert(continuations(result.zones[0])[1].__in == std::chrono::January); + assert(std::get(continuations(result.zones[0])[1].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[1].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[1].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[2].__year == std::chrono::year(-1000)); + assert(continuations(result.zones[0])[2].__in == std::chrono::November); + assert(std::get(continuations(result.zones[0])[2].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[2].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[2].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[3].__year == std::chrono::year::max()); + assert(continuations(result.zones[0])[3].__in == std::chrono::September); + assert(std::get(continuations(result.zones[0])[3].__on) == std::chrono::day(31)); + assert(continuations(result.zones[0])[3].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[3].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[4].__year == std::chrono::year(0)); + assert(continuations(result.zones[0])[4].__in == std::chrono::January); + assert(std::get(continuations(result.zones[0])[4].__on) == + std::chrono::weekday_last{std::chrono::Wednesday}); + assert(continuations(result.zones[0])[4].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[4].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[5].__year == std::chrono::year(-42)); + assert(continuations(result.zones[0])[5].__in == std::chrono::June); + r = std::get(continuations(result.zones[0])[5].__on); + assert(r.__weekday == std::chrono::Monday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le); + assert(r.__day == std::chrono::day(1)); + assert(continuations(result.zones[0])[5].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[5].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[6].__year == std::chrono::year(42)); + assert(continuations(result.zones[0])[6].__in == std::chrono::July); + r = std::get(continuations(result.zones[0])[6].__on); + assert(r.__weekday == std::chrono::Sunday); + assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge); + assert(r.__day == std::chrono::day(12)); + assert(continuations(result.zones[0])[6].__at.__time == std::chrono::seconds(0)); + assert(continuations(result.zones[0])[6].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[7].__year == std::chrono::year(42)); + assert(continuations(result.zones[0])[7].__in == std::chrono::July); + assert(std::get(continuations(result.zones[0])[7].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[7].__at.__time == std::chrono::hours(2)); + assert(continuations(result.zones[0])[7].__at.__clock == std::chrono::__tz::__clock::__local); + + assert(continuations(result.zones[0])[8].__year == std::chrono::year(42)); + assert(continuations(result.zones[0])[8].__in == std::chrono::July); + assert(std::get(continuations(result.zones[0])[8].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[8].__at.__time == + std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14)); + assert(continuations(result.zones[0])[8].__at.__clock == std::chrono::__tz::__clock::__universal); + + assert(continuations(result.zones[0])[9].__year == std::chrono::year(42)); + assert(continuations(result.zones[0])[9].__in == std::chrono::July); + assert(std::get(continuations(result.zones[0])[9].__on) == std::chrono::day(1)); + assert(continuations(result.zones[0])[9].__at.__time == std::chrono::hours(0)); // The man page expresses it in hours + assert(continuations(result.zones[0])[9].__at.__clock == std::chrono::__tz::__clock::__local); +} + +int main(int, const char**) { + test_invalid(); + test_name(); + test_stdoff(); + test_rules(); + test_format(); + test_until(); + + test_continuation(); + + return 0; +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv index d4c22023db7a0..59c37fc369b85 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -127,6 +127,7 @@ chrono string chrono string_view chrono tuple chrono type_traits +chrono vector chrono version cinttypes cstdint cmath limits @@ -833,13 +834,17 @@ system_error type_traits system_error version thread array thread atomic +thread cctype thread cerrno thread chrono +thread clocale thread compare thread cstddef thread cstdint +thread cstdlib thread cstring thread ctime +thread cwchar thread functional thread iosfwd thread limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv index 5eb839a3aa7a6..60381e1d99458 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -127,6 +127,7 @@ chrono string chrono string_view chrono tuple chrono type_traits +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv index 7b372fb37dcd5..3feee83875206 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -127,6 +127,7 @@ chrono string chrono string_view chrono tuple chrono type_traits +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv index 7b372fb37dcd5..3feee83875206 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -127,6 +127,7 @@ chrono string chrono string_view chrono tuple chrono type_traits +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv index 9b4915a468d1c..25822c942a979 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -133,6 +133,7 @@ chrono string chrono string_view chrono tuple chrono type_traits +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index bd8241118f4b9..64ac8dd324c11 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -87,6 +87,7 @@ chrono stdexcept chrono string chrono string_view chrono tuple +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index bd8241118f4b9..64ac8dd324c11 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -87,6 +87,7 @@ chrono stdexcept chrono string chrono string_view chrono tuple +chrono vector chrono version cinttypes cstdint cmath limits diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb.pass.cpp index 4fec102b66aa2..335e8d20c2391 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb.pass.cpp @@ -12,16 +12,16 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // // const tzdb& get_tzdb(); -#include - +#include #include +#include #include "test_macros.h" @@ -30,5 +30,15 @@ int main(int, const char**) { assert(!db.version.empty()); + LIBCPP_ASSERT(!db.__rules.empty()); + + assert(!db.zones.empty()); + assert(std::ranges::is_sorted(db.zones)); + assert(std::ranges::adjacent_find(db.zones) == db.zones.end()); // is unique? + + assert(!db.links.empty()); + assert(std::ranges::is_sorted(db.links)); + assert(std::ranges::adjacent_find(db.links) == db.links.end()); // is unique? + return 0; } diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb_list.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb_list.pass.cpp index 4d7647145ee08..34af9b576361f 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb_list.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.access/get_tzdb_list.pass.cpp @@ -12,7 +12,7 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/front.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/front.pass.cpp index 485cf9766962a..ac5fee8183b9b 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/front.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/front.pass.cpp @@ -12,7 +12,7 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/iterators.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/iterators.pass.cpp index 7fec4cf8556b5..8bd9b321b2a0f 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/iterators.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.list/iterators.pass.cpp @@ -12,7 +12,7 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/reload_tzdb.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/reload_tzdb.pass.cpp index 35d40b32d485f..bbf9002c0430c 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/reload_tzdb.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/reload_tzdb.pass.cpp @@ -12,7 +12,7 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/remote_version.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/remote_version.pass.cpp index f749adc3a63c6..861075cd82aa6 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/remote_version.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.remote/remote_version.pass.cpp @@ -12,7 +12,7 @@ // XFAIL: libcpp-has-no-incomplete-tzdb // XFAIL: availability-tzdb-missing -// TODO TZDB Enable tests +// TODO TZDB (#81654) Enable tests // UNSUPPORTED: c++20, c++23, c++26 // diff --git a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/tzdb.members.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/tzdb.members.pass.cpp index 66d939f125eab..51c6d364c9be0 100644 --- a/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/tzdb.members.pass.cpp +++ b/libcxx/test/std/time/time.zone/time.zone.db/time.zone.db.tzdb/tzdb.members.pass.cpp @@ -37,7 +37,11 @@ int main(int, const char**) { tzdb.version = "version"; assert(tzdb.version == "version"); - // TODO TZDB add the other data members + [[maybe_unused]] std::vector& zones = tzdb.zones; + + [[maybe_unused]] std::vector& links = tzdb.links; + + // TODO TZDB add the leap data member return 0; } diff --git a/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/name.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/name.pass.cpp new file mode 100644 index 0000000000000..95d86d586666e --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/name.pass.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB (#81654) Enable tests +// UNSUPPORTED: c++20, c++23, c++26 + +// + +// class time_zone_link; + +// string_view name() const noexcept; + +#include +#include + +#include "test_macros.h" + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + assert(tzdb.links.size() > 1); + + std::same_as auto name = tzdb.links[0].name(); + static_assert(noexcept(tzdb.links[0].name())); + assert(name != tzdb.links[1].name()); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/target.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/target.pass.cpp new file mode 100644 index 0000000000000..305fbd21f625b --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.members/target.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB (#81654) Enable tests +// UNSUPPORTED: c++20, c++23, c++26 + +// + +// class time_zone_link; + +// string_view target() const noexcept; + +#include +#include + +#include "test_macros.h" + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + assert(tzdb.links.size() > 1); + + [[maybe_unused]] std::same_as auto _ = tzdb.links[0].target(); + static_assert(noexcept(tzdb.links[0].target())); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.nonmembers/comparison.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.nonmembers/comparison.pass.cpp new file mode 100644 index 0000000000000..e375d7e443ce4 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.link/time.zone.link.nonmembers/comparison.pass.cpp @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB (#81654) Enable tests +// UNSUPPORTED: c++20, c++23, c++26 + +// + +// bool operator==(const time_zone_link& x, const time_zone_link& y) noexcept; +// strong_ordering operator<=>(const time_zone_link& x, const time_zone_link& y) noexcept; + +#include +#include + +#include "test_macros.h" +#include "test_comparisons.h" + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + assert(tzdb.links.size() > 2); + + AssertOrderAreNoexcept(); + AssertOrderReturn(); + + assert(testOrder(tzdb.links[0], tzdb.links[1], std::strong_ordering::less)); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.link/types.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.link/types.compile.pass.cpp new file mode 100644 index 0000000000000..2e4f178c7c050 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.link/types.compile.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class time_zone_link +// { +// time_zone_link(time_zone_link&&) = default; +// time_zone_link& operator=(time_zone_link&&) = default; +// +// ... +// }; + +#include +#include +#include + +#include "test_macros.h" + +// It's impossible to actually obtain a non-const reference to a +// time_zone_link, and as a result the move constructor can never be exercised +// in runtime code. We still check the property pedantically. +LIBCPP_STATIC_ASSERT(!std::copy_constructible); +LIBCPP_STATIC_ASSERT(!std::is_copy_assignable_v); +static_assert(std::move_constructible); +static_assert(std::is_move_assignable_v); diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/name.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/name.pass.cpp new file mode 100644 index 0000000000000..2bbe714b71a68 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/name.pass.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB (#81654) Enable tests +// UNSUPPORTED: c++20, c++23, c++26 + +// + +// class time_zone; + +// string_view name() const noexcept; + +#include +#include + +#include "test_macros.h" + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + assert(tzdb.zones.size() > 1); + + [[maybe_unused]] std::same_as auto _ = tzdb.zones[0].name(); + static_assert(noexcept(tzdb.zones[0].name())); + assert(tzdb.zones[0].name() != tzdb.zones[1].name()); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.nonmembers/comparison.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.nonmembers/comparison.pass.cpp new file mode 100644 index 0000000000000..9eae91e80a42d --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.nonmembers/comparison.pass.cpp @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// TODO TZDB (#81654) Enable tests +// UNSUPPORTED: c++20, c++23, c++26 + +// + +// bool operator==(const time_zone& x, const time_zone& y) noexcept; +// strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; + +#include +#include + +#include "test_macros.h" +#include "test_comparisons.h" + +int main(int, const char**) { + const std::chrono::tzdb& tzdb = std::chrono::get_tzdb(); + assert(tzdb.zones.size() > 2); + + AssertOrderAreNoexcept(); + AssertOrderReturn(); + + assert(testOrder(tzdb.zones[0], tzdb.zones[1], std::strong_ordering::less)); + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.timezone/types.compile.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.timezone/types.compile.pass.cpp new file mode 100644 index 0000000000000..915d5596bf22b --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.timezone/types.compile.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-incomplete-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class time_zone +// { +// time_zone(time_zone&&) = default; +// time_zone& operator=(time_zone&&) = default; +// +// ... +// }; + +#include +#include +#include + +#include "test_macros.h" + +// It's impossible to actually obtain a non-const reference to a time_zone, and +// as a result the move constructor can never be exercised in runtime code. We +// still check the property pedantically. +LIBCPP_STATIC_ASSERT(!std::copy_constructible); +LIBCPP_STATIC_ASSERT(!std::is_copy_assignable_v); +static_assert(std::move_constructible); +static_assert(std::is_move_assignable_v);