================ @@ -0,0 +1,155 @@ +// -*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___CHRONO_UTC_CLOCK_H +#define _LIBCPP___CHRONO_UTC_CLOCK_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 <__chrono/tzdb.h> +# include <__chrono/tzdb_list.h> +# include <__config> +# include <__type_traits/common_type.h> + +# 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 { + +class utc_clock; + +template <class _Duration> +using utc_time = time_point<utc_clock, _Duration>; +using utc_seconds = utc_time<seconds>; + +class utc_clock { +public: + using rep = system_clock::rep; + using period = system_clock::period; + using duration = chrono::duration<rep, period>; + using time_point = chrono::time_point<utc_clock>; + static constexpr bool is_steady = false; // The system_clock is not steady. + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static time_point now() { return from_sys(system_clock::now()); } + + template <class _Duration> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static sys_time<common_type_t<_Duration, seconds>> + to_sys(const utc_time<_Duration>& __time); + + template <class _Duration> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static utc_time<common_type_t<_Duration, seconds>> + from_sys(const sys_time<_Duration>& __time) { + using _Rp = utc_time<common_type_t<_Duration, seconds>>; + // TODO TZDB investigate optimizations. + // + // The leap second database stores all transitions, this mean to calculate + // the current number of leap seconds the code needs to iterate over all + // leap seconds to accumulate the sum. Then the sum can be used to determine + // the sys_time. Accessing the database involves acquiring a mutex. + // + // The historic entries in the database are immutable. Hard-coding these + // values in a table would allow: + // - To store the sum, allowing a binary search on the data. + // - Avoid acquiring a mutex. + // The disadvantage are: + // - A slightly larger code size. + // + // There are two optimization directions + // - hard-code the database and do a linear search for future entries. This + // search can start at the back, and should probably contain very few + // entries. (Adding leap seconds is quite rare and new release of libc++ + // can add the new entries; they are announced half a year before they are + // added.) + // - During parsing the leap seconds store an additional database in the + // dylib with the list of the sum of the leap seconds. In that case there + // can be a private function __get_utc_to_sys_table that returns the + // table. + // + // Note for to_sys there are no optimizations to be done; it uses + // get_leap_second_info. The function get_leap_second_info could benefit + // from optimizations as described above; again both options apply. + + // Both UTC and the system clock use the same epoch. The Standard + // specifies from 1970-01-01 even when UTC starts at + // 1972-01-01 00:00:10 TAI. So when the sys_time is before epoch we can be + // sure there both clocks return the same value. + + const tzdb& __tzdb = chrono::get_tzdb(); + _Rp __result{__time.time_since_epoch()}; + for (const auto& __leap_second : __tzdb.leap_seconds) { + if (__time < __leap_second) + return __result; + + __result += __leap_second.value(); + } + return __result; + } +}; + +struct leap_second_info { + bool is_leap_second; + seconds elapsed; +}; + +template <class _Duration> +[[nodiscard]] _LIBCPP_HIDE_FROM_ABI leap_second_info get_leap_second_info(const utc_time<_Duration>& __time) { + const tzdb& __tzdb = chrono::get_tzdb(); + if (__tzdb.leap_seconds.empty()) + return {false, chrono::seconds{0}}; + + sys_seconds __sys{chrono::floor<seconds>(__time).time_since_epoch()}; + seconds __elapsed{0}; + for (const auto& __leap_second : __tzdb.leap_seconds) { + if (__sys == __leap_second.date() + __elapsed) + return {__leap_second.value() > 0s, // only positive leap seconds are considered leap seconds + __elapsed + __leap_second.value()}; + + if (__sys < __leap_second.date() + __elapsed) + return {false, __elapsed}; + + __elapsed += __leap_second.value(); + } + + return {false, __elapsed}; +} + +template <class _Duration> +[[nodiscard]] _LIBCPP_HIDE_FROM_ABI sys_time<common_type_t<_Duration, seconds>> +utc_clock::to_sys(const utc_time<_Duration>& __time) { + using _Dp = common_type_t<_Duration, seconds>; + leap_second_info __info = get_leap_second_info(__time); + + sys_time<common_type_t<_Duration, seconds>> __result{__time.time_since_epoch() - __info.elapsed}; + if (__info.is_leap_second) + return chrono::floor<seconds>(__result) + chrono::seconds{1} - _Dp{1}; ---------------- MattStephanson wrote:
I don't think this is right for floating point `duration` reps, where the ulp can be more or less than `_Dp{1}`. I think `nextafter` is needed for reps where `treat_as_floating_point_v` is `true`. (Assuming you have such an overload for extended-precision floats. I'm also uncertain if [[time.duration.general]/2](https://eel.is/c++draft/time#duration.general-2) allows a user-defined type to "emulate" a floating point type by specializing `treat_as_floating_point`.) https://github.com/llvm/llvm-project/pull/90393 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits