xref: /llvm-project/libcxx/include/__chrono/utc_clock.h (revision 3b30f20c60d020e43f5700dae68cf1080158b725)
1 // -*- C++ -*-
2 //===----------------------------------------------------------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #ifndef _LIBCPP___CHRONO_UTC_CLOCK_H
11 #define _LIBCPP___CHRONO_UTC_CLOCK_H
12 
13 #include <version>
14 // Enable the contents of the header only when libc++ was built with experimental features enabled.
15 #if _LIBCPP_HAS_EXPERIMENTAL_TZDB
16 
17 #  include <__chrono/duration.h>
18 #  include <__chrono/leap_second.h>
19 #  include <__chrono/system_clock.h>
20 #  include <__chrono/time_point.h>
21 #  include <__chrono/tzdb.h>
22 #  include <__chrono/tzdb_list.h>
23 #  include <__config>
24 #  include <__type_traits/common_type.h>
25 
26 #  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
27 #    pragma GCC system_header
28 #  endif
29 
30 _LIBCPP_BEGIN_NAMESPACE_STD
31 
32 #  if _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
33 
34 namespace chrono {
35 
36 class utc_clock;
37 
38 template <class _Duration>
39 using utc_time    = time_point<utc_clock, _Duration>;
40 using utc_seconds = utc_time<seconds>;
41 
42 class utc_clock {
43 public:
44   using rep                       = system_clock::rep;
45   using period                    = system_clock::period;
46   using duration                  = chrono::duration<rep, period>;
47   using time_point                = chrono::time_point<utc_clock>;
48   static constexpr bool is_steady = false; // The system_clock is not steady.
49 
50   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static time_point now() { return from_sys(system_clock::now()); }
51 
52   template <class _Duration>
53   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static sys_time<common_type_t<_Duration, seconds>>
54   to_sys(const utc_time<_Duration>& __time);
55 
56   template <class _Duration>
57   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static utc_time<common_type_t<_Duration, seconds>>
58   from_sys(const sys_time<_Duration>& __time) {
59     using _Rp = utc_time<common_type_t<_Duration, seconds>>;
60     // TODO TZDB investigate optimizations.
61     //
62     // The leap second database stores all transitions, this mean to calculate
63     // the current number of leap seconds the code needs to iterate over all
64     // leap seconds to accumulate the sum. Then the sum can be used to determine
65     // the sys_time. Accessing the database involves acquiring a mutex.
66     //
67     // The historic entries in the database are immutable. Hard-coding these
68     // values in a table would allow:
69     // - To store the sum, allowing a binary search on the data.
70     // - Avoid acquiring a mutex.
71     // The disadvantage are:
72     // - A slightly larger code size.
73     //
74     // There are two optimization directions
75     // - hard-code the database and do a linear search for future entries. This
76     //   search can start at the back, and should probably contain very few
77     //   entries. (Adding leap seconds is quite rare and new release of libc++
78     //   can add the new entries; they are announced half a year before they are
79     //   added.)
80     // - During parsing the leap seconds store an additional database in the
81     //   dylib with the list of the sum of the leap seconds. In that case there
82     //   can be a private function __get_utc_to_sys_table that returns the
83     //   table.
84     //
85     // Note for to_sys there are no optimizations to be done; it uses
86     // get_leap_second_info. The function get_leap_second_info could benefit
87     // from optimizations as described above; again both options apply.
88 
89     // Both UTC and the system clock use the same epoch. The Standard
90     // specifies from 1970-01-01 even when UTC starts at
91     // 1972-01-01 00:00:10 TAI. So when the sys_time is before epoch we can be
92     // sure there both clocks return the same value.
93 
94     const tzdb& __tzdb = chrono::get_tzdb();
95     _Rp __result{__time.time_since_epoch()};
96     for (const auto& __leap_second : __tzdb.leap_seconds) {
97       if (__leap_second > __time)
98         return __result;
99 
100       __result += __leap_second.value();
101     }
102     return __result;
103   }
104 };
105 
106 struct leap_second_info {
107   bool is_leap_second;
108   seconds elapsed;
109 };
110 
111 template <class _Duration>
112 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI leap_second_info get_leap_second_info(const utc_time<_Duration>& __time) {
113   const tzdb& __tzdb = chrono::get_tzdb();
114   if (__tzdb.leap_seconds.empty()) [[unlikely]]
115     return {false, chrono::seconds{0}};
116 
117   sys_seconds __sys{chrono::floor<seconds>(__time).time_since_epoch()};
118   seconds __elapsed{0};
119   for (const auto& __leap_second : __tzdb.leap_seconds) {
120     if (__sys == __leap_second.date() + __elapsed)
121       // A time point may only be a leap second during a positive leap second
122       // insertion, since time points that occur during a (theoretical)
123       // negative leap second don't exist.
124       return {__leap_second.value() > 0s, __elapsed + __leap_second.value()};
125 
126     if (__sys < __leap_second.date() + __elapsed)
127       return {false, __elapsed};
128 
129     __elapsed += __leap_second.value();
130   }
131 
132   return {false, __elapsed};
133 }
134 
135 template <class _Duration>
136 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI sys_time<common_type_t<_Duration, seconds>>
137 utc_clock::to_sys(const utc_time<_Duration>& __time) {
138   using _Dp               = common_type_t<_Duration, seconds>;
139   leap_second_info __info = chrono::get_leap_second_info(__time);
140 
141   // [time.clock.utc.members]/2
142   //   Returns: A sys_time t, such that from_sys(t) == u if such a mapping
143   //   exists. Otherwise u represents a time_point during a positive leap
144   //   second insertion, the conversion counts that leap second as not
145   //   inserted, and the last representable value of sys_time prior to the
146   //   insertion of the leap second is returned.
147   sys_time<common_type_t<_Duration, seconds>> __result{__time.time_since_epoch() - __info.elapsed};
148   if (__info.is_leap_second)
149     return chrono::floor<seconds>(__result) + chrono::seconds{1} - _Dp{1};
150 
151   return __result;
152 }
153 
154 } // namespace chrono
155 
156 #  endif // _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM &&
157          // _LIBCPP_HAS_LOCALIZATION
158 
159 _LIBCPP_END_NAMESPACE_STD
160 
161 #endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
162 
163 #endif // _LIBCPP___CHRONO_UTC_CLOCK_H
164