[llvm-branch-commits] [libcxx] [libc++][chrono] Adds the sys_info class. (PR #85619)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Mar 18 02:44:52 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Mark de Wever (mordante)
<details>
<summary>Changes</summary>
Adds the sys_info class and time_zone::get_info(). The code still has a few quirks and has not been optimized for performance yet.
The returned sys_info is compared against the output of the zdump tool in the test giving confidence the implementation is correct.
Implements parts of:
- P0355 Extending <chrono> to Calendars and Time Zones
Implements:
- LWGXXXX The sys_info range should be affected by save
---
Patch is 119.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/85619.diff
17 Files Affected:
- (modified) libcxx/docs/Status/Cxx2cIssues.csv (+1)
- (modified) libcxx/include/CMakeLists.txt (+1)
- (added) libcxx/include/__chrono/sys_info.h (+53)
- (modified) libcxx/include/__chrono/time_zone.h (+9)
- (modified) libcxx/include/chrono (+13)
- (modified) libcxx/include/libcxx.imp (+1)
- (modified) libcxx/modules/std/chrono.inc (+2)
- (modified) libcxx/src/include/tzdb/types_private.h (+13-2)
- (modified) libcxx/src/include/tzdb/tzdb_list_private.h (+7)
- (modified) libcxx/src/time_zone.cpp (+855)
- (modified) libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp (+1)
- (modified) libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.verify.cpp (+2)
- (added) libcxx/test/libcxx/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp (+140)
- (added) libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/sys_info.members.compile.pass.cpp (+33)
- (added) libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/get_info.sys_time.pass.cpp (+1374)
- (added) libcxx/test/std/time/time.zone/time.zone.timezone/time.zone.members/sys_info.zdump.pass.cpp (+124)
- (modified) libcxx/utils/libcxx/test/features.py (+6)
``````````diff
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 58e995809777c1..a6aa70ecc866e0 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -41,4 +41,5 @@
"`4001 <https://wg21.link/LWG4001>`__","``iota_view`` should provide ``empty``","Kona November 2023","","","|ranges|"
"","","","","",""
"`3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Yet Adopted","|Complete|","16.0",""
+"XXXX","","The sys_info range should be affected by save","Not Yet Adopted","|Complete|","19.0"
"","","","","",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 5d9a693d152e4a..24cef9cd877c62 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -289,6 +289,7 @@ set(files
__chrono/ostream.h
__chrono/parser_std_format_spec.h
__chrono/statically_widen.h
+ __chrono/sys_info.h
__chrono/steady_clock.h
__chrono/system_clock.h
__chrono/time_point.h
diff --git a/libcxx/include/__chrono/sys_info.h b/libcxx/include/__chrono/sys_info.h
new file mode 100644
index 00000000000000..16ec5dd254d59a
--- /dev/null
+++ b/libcxx/include/__chrono/sys_info.h
@@ -0,0 +1,53 @@
+// -*- 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_SYS_INFO_H
+#define _LIBCPP___CHRONO_SYS_INFO_H
+
+#include <version>
+// Enable the contents of the header only when libc++ was built with experimental features enabled.
+#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)
+
+# include <__chrono/duration.h>
+# include <__chrono/system_clock.h>
+# include <__chrono/time_point.h>
+# include <__config>
+# include <string>
+
+# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+# endif
+
+_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 {
+
+struct sys_info {
+ sys_seconds begin;
+ sys_seconds end;
+ seconds offset;
+ minutes save;
+ string abbrev;
+};
+
+} // 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
+
+#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)
+
+#endif // _LIBCPP___CHRONO_SYS_INFO_H
diff --git a/libcxx/include/__chrono/time_zone.h b/libcxx/include/__chrono/time_zone.h
index 7d97327a6c8e99..a5af6297a4cf92 100644
--- a/libcxx/include/__chrono/time_zone.h
+++ b/libcxx/include/__chrono/time_zone.h
@@ -16,6 +16,7 @@
// Enable the contents of the header only when libc++ was built with experimental features enabled.
#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)
+# include <__chrono/sys_info.h>
# include <__compare/strong_order.h>
# include <__config>
# include <__memory/unique_ptr.h>
@@ -55,10 +56,18 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI string_view name() const noexcept { return __name(); }
+ template <class _Duration>
+ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI sys_info get_info(const sys_time<_Duration>& __time) const {
+ return __get_info(chrono::time_point_cast<seconds>(__time));
+ }
+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; }
private:
[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view __name() const noexcept;
+
+ [[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info __get_info(sys_seconds __time) const;
+
unique_ptr<__impl> __impl_;
};
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index 8fdc30a3624dfc..00b940a6610a3a 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -724,6 +724,15 @@ const time_zone* current_zone()
const tzdb& reload_tzdb(); // C++20
string remote_version(); // C++20
+// [time.zone.info], information classes
+struct sys_info { // C++20
+ sys_seconds begin;
+ sys_seconds end;
+ seconds offset;
+ minutes save;
+ string abbrev;
+};
+
// 25.10.5, class time_zone // C++20
enum class choose {earliest, latest};
class time_zone {
@@ -733,6 +742,9 @@ class time_zone {
// unspecified additional constructors
string_view name() const noexcept;
+
+ template<class Duration>
+ sys_info get_info(const sys_time<Duration>& st) const;
};
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
@@ -906,6 +918,7 @@ 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/leap_second.h>
+# include <__chrono/sys_info.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__chrono/tzdb.h>
diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp
index 2d6ac5f9e982aa..e3a08860c0b059 100644
--- a/libcxx/include/libcxx.imp
+++ b/libcxx/include/libcxx.imp
@@ -287,6 +287,7 @@
{ include: [ "<__chrono/parser_std_format_spec.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/statically_widen.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/steady_clock.h>", "private", "<chrono>", "public" ] },
+ { include: [ "<__chrono/sys_info.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/system_clock.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/time_point.h>", "private", "<chrono>", "public" ] },
{ include: [ "<__chrono/time_zone.h>", "private", "<chrono>", "public" ] },
diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc
index e14228043d3b84..575e6347aecce1 100644
--- a/libcxx/modules/std/chrono.inc
+++ b/libcxx/modules/std/chrono.inc
@@ -212,10 +212,12 @@ export namespace std {
// [time.zone.exception], exception classes
using std::chrono::ambiguous_local_time;
using std::chrono::nonexistent_local_time;
+# endif // if 0
// [time.zone.info], information classes
using std::chrono::sys_info;
+# if 0
// [time.zone.timezone], class time_zone
using std::chrono::choose;
# endif // if 0
diff --git a/libcxx/src/include/tzdb/types_private.h b/libcxx/src/include/tzdb/types_private.h
index 4604b9fc88114d..bdc9418a8866be 100644
--- a/libcxx/src/include/tzdb/types_private.h
+++ b/libcxx/src/include/tzdb/types_private.h
@@ -33,7 +33,17 @@ 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
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI year_month_day operator()(year __year, month __month) const {
+ auto __result = static_cast<sys_days>(year_month_day{__year, __month, __day});
+ weekday __wd{static_cast<sys_days>(__result)};
+
+ if (__comparison == __le)
+ __result -= __wd - __weekday;
+ else
+ __result += __weekday - __wd;
+
+ return __result;
+ }
weekday __weekday;
enum __comparison_t { __le, __ge } __comparison;
@@ -85,7 +95,8 @@ struct __continuation {
// used.
// If this field contains - then standard time always
// applies. This is indicated by the monostate.
- using __rules_t = variant<monostate, __tz::__save, string, size_t>;
+ // TODO TZDB Investigate implemention the size_t based caching.
+ using __rules_t = variant<monostate, __tz::__save, string /*, size_t*/>;
__rules_t __rules;
diff --git a/libcxx/src/include/tzdb/tzdb_list_private.h b/libcxx/src/include/tzdb/tzdb_list_private.h
index f43d7d8ea772be..a3f56e79a51791 100644
--- a/libcxx/src/include/tzdb/tzdb_list_private.h
+++ b/libcxx/src/include/tzdb/tzdb_list_private.h
@@ -84,6 +84,13 @@ class tzdb_list::__impl {
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
+ const __tz::__rules_storage_type& __rules() const noexcept {
+#ifndef _LIBCPP_HAS_NO_THREADS
+ shared_lock __lock{__mutex_};
+#endif
+ return __rules_.front();
+ }
+
private:
// Loads the tzdbs
// pre: The caller ensures the locking, if needed, is done.
diff --git a/libcxx/src/time_zone.cpp b/libcxx/src/time_zone.cpp
index b6bf06a116f68b..84ff0e1ae49268 100644
--- a/libcxx/src/time_zone.cpp
+++ b/libcxx/src/time_zone.cpp
@@ -8,14 +8,706 @@
// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html
+// TODO TZDB look at optimizations
+//
+// The current algorithm is correct but not efficient. For example, in a named
+// rule based continuation finding the next rule does quite a bit of work,
+// returns the next rule and "forgets" its state. This could be better.
+//
+// It would be possible to cache lookups. If a time for a zone is calculated its
+// sys_info could be kept and the next lookup could test whether the time is in
+// a "known" sys_info. The wording in the Standard hints at this slowness by
+// "suggesting" this could be implemented at the user's side.
+
+// TODO TZDB look at removing quirks
+//
+// The code has some special rules to adjust the timing at the continuation
+// switches. This works correctly, but some of the places feel odd. It would be
+// good to investigate this further and see whether all quirks are needed or
+// that there are better fixes.
+//
+// These quirks often use a 12h interval; this is the scan interval of zdump,
+// which implies there are no sys_info objects with a duration of less than 12h.
+
+#include <algorithm>
#include <chrono>
+#include <expected>
+#include <map>
+#include <ranges>
#include "include/tzdb/time_zone_private.h"
+#include "include/tzdb/tzdb_list_private.h"
+
+// TODO TZDB remove debug printing
+#ifdef PRINT
+# include <print>
+#endif
_LIBCPP_BEGIN_NAMESPACE_STD
+#ifdef PRINT
+template <>
+struct formatter<chrono::sys_info, char> {
+ template <class ParseContext>
+ constexpr typename ParseContext::iterator parse(ParseContext& ctx) {
+ return ctx.begin();
+ }
+
+ template <class FormatContext>
+ typename FormatContext::iterator format(const chrono::sys_info& info, FormatContext& ctx) const {
+ return std::format_to(
+ ctx.out(), "[{}, {}) {:%Q%q} {:%Q%q} {}", info.begin, info.end, info.offset, info.save, info.abbrev);
+ }
+};
+#endif
+
namespace chrono {
+//===----------------------------------------------------------------------===//
+// Details
+//===----------------------------------------------------------------------===//
+
+struct __sys_info {
+ sys_info __info;
+ bool __can_merge; // Can the returned sys_info object be merged with
+};
+
+// Return type for helper function to get a sys_info.
+// - The expected result returns the "best" sys_info object. This object can be
+// before the requested time. Sometimes sys_info objects from different
+// continuations share their offset, save, and abbrev and these objects are
+// merged to one sys_info object. The __can_merge flag determines whether the
+// current result can be merged with the next result.
+// - The unexpected result means no sys_info object was found and the time is
+// the time to be used for the next search iteration.
+using __sys_info_result = expected<__sys_info, sys_seconds>;
+
+template <ranges::forward_range _Range,
+ class _Type,
+ class _Proj = identity,
+ indirect_strict_weak_order<const _Type*, projected<ranges::iterator_t<_Range>, _Proj>> _Comp = ranges::less>
+[[nodiscard]] static ranges::borrowed_iterator_t<_Range>
+__binary_find(_Range&& __r, const _Type& __value, _Comp __comp = {}, _Proj __proj = {}) {
+ auto __end = ranges::end(__r);
+ auto __ret = ranges::lower_bound(ranges::begin(__r), __end, __value, __comp, __proj);
+ if (__ret == __end)
+ return __end;
+
+ // When the value does not match the predicate it's equal and a valid result
+ // was found.
+ return !std::invoke(__comp, __value, std::invoke(__proj, *__ret)) ? __ret : __end;
+}
+
+// Format based on https://data.iana.org/time-zones/tz-how-to.html
+//
+// 1 a time zone abbreviation that is a string of three or more characters that
+// are either ASCII alphanumerics, “+”, or “-”
+// 2 the string “%z”, in which case the “%z” will be replaced by a numeric time
+// zone abbreviation
+// 3 a pair of time zone abbreviations separated by a slash (‘/’), in which
+// case the first string is the abbreviation for the standard time name and
+// the second string is the abbreviation for the daylight saving time name
+// 4 a string containing “%s”, in which case the “%s” will be replaced by the
+// text in the appropriate Rule’s LETTER column, and the resulting string should
+// be a time zone abbreviation
+//
+// Accepting invalid formats that can be processed in a sensible way would better
+// serve the user than throwing an exception. So some of these rules are not
+// strictly validated.
+// 1 This is not validated. Some examples that will be accepted are, "+04:30",
+// "Q", "42".
+// 2 How this format is formatted is not specified. In the current tzdata.zi
+// this value is not used. This value is accepted in a part of the format. So
+// "a%s%zb" will be considered valid.
+// 3 This is not validated, the output might be incorrect.
+// Proper validation would make the algorithm more complex. Then the first
+// element of the pair is used the parsing of FORMAT can stop. To do proper
+// validation the tail should be validated.
+// 4 This value is accepted in a part of the format. So "a%s%zb" will be
+// considered valid.
+[[nodiscard]] static string
+__format(const __tz::__continuation& __continuation, const string& __letters, seconds __save) {
+ bool __shift = false;
+ string __result;
+ for (char __c : __continuation.__format) {
+ if (__shift) {
+ switch (__c) {
+ case 's':
+ std::ranges::copy(__letters, std::back_inserter(__result));
+ break;
+
+ case 'z': {
+ chrono::hh_mm_ss __offset{__continuation.__stdoff + __save};
+ if (__offset.is_negative()) {
+ __result += '-';
+ __offset = chrono::hh_mm_ss{-(__continuation.__stdoff + __save)};
+ } else
+ __result += '+';
+
+ if (__offset.minutes() != 0min)
+ std::format_to(std::back_inserter(__result), "{:%H%M}", __offset);
+ else
+ std::format_to(std::back_inserter(__result), "{:%H}", __offset);
+ } break;
+
+ default:
+ std::__throw_runtime_error(
+ std::format("corrupt tzdb FORMAT field: invalid sequence '%{}' found, expected %s or %z", __c).c_str());
+ }
+ __shift = false;
+
+ } else if (__c == '/') {
+ if (__save != 0s)
+ __result.clear();
+ else
+ break;
+
+ } else if (__c == '%') {
+ __shift = true;
+ } else {
+ __result.push_back(__c);
+ }
+ }
+
+ if (__shift)
+ std::__throw_runtime_error("corrupt tzdb FORMAT field: input ended with the start of the escape sequence '%'");
+
+ return __result;
+}
+
+[[nodiscard]] static sys_seconds __to_sys_seconds(year_month_day __ymd, seconds __seconds) {
+ seconds __result = static_cast<sys_days>(__ymd).time_since_epoch() + __seconds;
+ return sys_seconds{__result};
+}
+
+[[nodiscard]] static seconds __at_to_sys_seconds(const __tz::__continuation& __continuation) {
+ switch (__continuation.__at.__clock) {
+ case __tz::__clock::__local:
+ return __continuation.__at.__time - __continuation.__stdoff -
+ std::visit(
+ [](const auto& __value) {
+ using _Tp = decay_t<decltype(__value)>;
+ if constexpr (same_as<_Tp, monostate>)
+ return chrono::seconds{0};
+ else if constexpr (same_as<_Tp, __tz::__save>)
+ return chrono::duration_cast<seconds>(__value.__time);
+ else if constexpr (same_as<_Tp, std::string>)
+ // For a named rule based continuation the SAVE depends on the RULE
+ // active at the end. This should be determined separately.
+ return chrono::seconds{0};
+ else
+ static_assert(false);
+
+ std::__libcpp_unreachable();
+ },
+ __continuation.__rules);
+
+ case __tz::__clock::__universal:
+ return __continuation.__at.__time;
+
+ case __tz::__clock::__standard:
+ return __continuation.__at.__time - __continuation.__stdoff;
+ }
+}
+
+[[nodiscard]] static year_month_day __to_year_month_day(year __year, month __month, __tz::__on __on) {
+ return std::visit(
+ [&](const auto& __value) {
+ using T = decay_t<decltype(__value)>;
+ if constexpr (same_as<T, chrono::day>)
+ return year_month_day{__year, __month, __value};
+ else if constexpr (same_as<T, weekday_last>)
+ return year_month_day{static_cast<sys_days>(year_month_weekday_last{__year, __month, __value})};
+ else if constexpr (same_as<T, __tz::__constrained_weekday>)
+ return __value(__year, __month);
+ else
+ static_assert(false);
+
+ std::__libcpp_unreachable();
+ },
+ __on);
+}
+
+[[nodiscard]] static sys_seconds __until_to_sys_seconds(const __tz::__continuation& __continuation) {
+ // Does UNTIL contain the magic value for the last continuation?
+ if (__continuation.__year == chrono::year::min())
+ return sys_seconds::max();
+
+ year_month_day __ymd = chrono::__to_year_month_day(__continuation.__year, __continuation.__in, __continuation.__on);
+ return chrono::__to_sys_seconds(__ymd, chrono::__at_to_sys_seconds(__continuation));
+}
+
+// Holds the UNTIL time for a continuation with a named rule.
+//
+// Unlike continuations with an fixed SAVE named rules have a variable SAVE.
+// This means when the UNTIL uses the local wall time the actual UNTIL value can
+// only be determined when the SAVE is known. This class holds that abstraction.
+class __named_rule_until {
+public:
+ explicit __named_rule_until(const __tz::__continuation& __continuation)
+ : __until_{chrono::__until_to_sys_seconds(__continuation)},
+ __needs_adjustment_{
+ // The last continuation of a ZONE has no UNTIL which basically is
+ // until the end of _local_ time. This value is expressed by
+ // sys_seconds::max(). Subtracting the SAVE leaves large value.
+ // However SAVE can be negative, which would add a value to maximum
+ // leading to undefined behaviour. In practice this often results in
+ // an overflow to a very small value.
+ __until_ != sys_seconds::max() && __continuation.__at.__clock == __tz::__clock::__local} {}
+
+ // Gives the unadjusted until value, this is useful when the SAVE is not known
+ // at all.
+ sys_seconds __until() const noexcept { return __until_; }
+
+ bool __needs_adjustment() const noexcept { return __needs_adjustment_; }
+
+ // Returns the UNTIL adjusted for SAVE.
+ sys_seconds operator()(seconds __save) const noexcept { return __until_ - __needs_adjustment_ * __save; }
+
+private:
+ sys_seconds __until_;
+ bool __needs_adjustment_;
+};
+
+[[nodiscard]] static seconds __at_to_seconds(seconds __stdoff, const __tz::__rule...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/85619
More information about the llvm-branch-commits
mailing list