Skip to content

Commit 0cd794d

Browse files
authored
[libc++][chrono] implements UTC clock. (llvm#90393)
While implementing this feature and its associated LWG issues it turns out - LWG3316 Correctly define epoch for utc_clock / utc_timepoint only added non-normative wording to the standard. Implements parts of: - P0355 Extending <chrono> to Calendars and Time Zones - P1361 Integration of chrono with text formatting - LWG3359 <chrono> leap second support should allow for negative leap seconds
1 parent bd8a818 commit 0cd794d

File tree

24 files changed

+2653
-5
lines changed

24 files changed

+2653
-5
lines changed

libcxx/docs/Status/Cxx20Issues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@
238238
"`LWG3313 <https://wg21.link/LWG3313>`__","``join_view::iterator::operator--``\ is incorrectly constrained","2020-02 (Prague)","|Complete|","14",""
239239
"`LWG3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","2020-02 (Prague)","|Complete|","16",""
240240
"`LWG3315 <https://wg21.link/LWG3315>`__","LWG3315: Correct Allocator Default Behavior","2020-02 (Prague)","|Complete|","",""
241-
"`LWG3316 <https://wg21.link/LWG3316>`__","Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ ","2020-02 (Prague)","","",""
241+
"`LWG3316 <https://wg21.link/LWG3316>`__","Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ ","2020-02 (Prague)","|Nothing To Do|","",""
242242
"`LWG3317 <https://wg21.link/LWG3317>`__","Incorrect ``operator<<``\ for floating-point durations","2020-02 (Prague)","|Complete|","16",""
243243
"`LWG3318 <https://wg21.link/LWG3318>`__","Clarify whether clocks can represent time before their epoch","2020-02 (Prague)","","",""
244244
"`LWG3319 <https://wg21.link/LWG3319>`__","Properly reference specification of IANA time zone database","2020-02 (Prague)","|Nothing To Do|","",""

libcxx/docs/Status/FormatPaper.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
22
`P1361 <https://wg21.link/P1361>`__ `P2372 <https://wg21.link/P2372>`__,"Formatting chrono"
33
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|Complete|,16
44
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_time<Duration>``",,Mark de Wever,|Complete|,17
5-
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
5+
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,|Complete|,20
66
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
77
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::gps_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
88
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::file_time<Duration>``",,Mark de Wever,|Complete|,17

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ set(files
275275
__chrono/time_zone_link.h
276276
__chrono/tzdb.h
277277
__chrono/tzdb_list.h
278+
__chrono/utc_clock.h
278279
__chrono/weekday.h
279280
__chrono/year.h
280281
__chrono/year_month.h

libcxx/include/__chrono/convert_to_tm.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <__chrono/sys_info.h>
2525
#include <__chrono/system_clock.h>
2626
#include <__chrono/time_point.h>
27+
#include <__chrono/utc_clock.h>
2728
#include <__chrono/weekday.h>
2829
#include <__chrono/year.h>
2930
#include <__chrono/year_month.h>
@@ -98,6 +99,22 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const chrono::sys_time<_Duration> __tp
9899
return __result;
99100
}
100101

102+
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
103+
# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
104+
105+
template <class _Tm, class _Duration>
106+
_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::utc_time<_Duration> __tp) {
107+
_Tm __result = std::__convert_to_tm<_Tm>(chrono::utc_clock::to_sys(__tp));
108+
109+
if (chrono::get_leap_second_info(__tp).is_leap_second)
110+
++__result.tm_sec;
111+
112+
return __result;
113+
}
114+
115+
# endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
116+
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
117+
101118
// Convert a chrono (calendar) time point, or dururation to the given _Tm type,
102119
// which must have the same properties as std::tm.
103120
template <class _Tm, class _ChronoT>
@@ -110,6 +127,12 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
110127
if constexpr (__is_time_point<_ChronoT>) {
111128
if constexpr (same_as<typename _ChronoT::clock, chrono::system_clock>)
112129
return std::__convert_to_tm<_Tm>(__value);
130+
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
131+
# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
132+
else if constexpr (same_as<typename _ChronoT::clock, chrono::utc_clock>)
133+
return std::__convert_to_tm<_Tm>(__value);
134+
# endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
135+
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
113136
else if constexpr (same_as<typename _ChronoT::clock, chrono::file_clock>)
114137
return std::__convert_to_tm<_Tm>(_ChronoT::clock::to_sys(__value));
115138
else if constexpr (same_as<typename _ChronoT::clock, chrono::local_t>)

libcxx/include/__chrono/formatter.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# include <__chrono/sys_info.h>
3333
# include <__chrono/system_clock.h>
3434
# include <__chrono/time_point.h>
35+
# include <__chrono/utc_clock.h>
3536
# include <__chrono/weekday.h>
3637
# include <__chrono/year.h>
3738
# include <__chrono/year_month.h>
@@ -719,6 +720,23 @@ struct _LIBCPP_TEMPLATE_VIS formatter<chrono::sys_time<_Duration>, _CharT> : pub
719720
}
720721
};
721722

723+
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
724+
# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
725+
726+
template <class _Duration, __fmt_char_type _CharT>
727+
struct _LIBCPP_TEMPLATE_VIS formatter<chrono::utc_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
728+
public:
729+
using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
730+
731+
template <class _ParseContext>
732+
_LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
733+
return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
734+
}
735+
};
736+
737+
# endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
738+
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
739+
722740
template <class _Duration, __fmt_char_type _CharT>
723741
struct _LIBCPP_TEMPLATE_VIS formatter<chrono::file_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
724742
public:

libcxx/include/__chrono/ostream.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
# include <__chrono/statically_widen.h>
2727
# include <__chrono/sys_info.h>
2828
# include <__chrono/system_clock.h>
29+
# include <__chrono/utc_clock.h>
2930
# include <__chrono/weekday.h>
3031
# include <__chrono/year.h>
3132
# include <__chrono/year_month.h>
@@ -61,6 +62,18 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days& __dp) {
6162
return __os << year_month_day{__dp};
6263
}
6364

65+
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
66+
# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
67+
68+
template <class _CharT, class _Traits, class _Duration>
69+
_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
70+
operator<<(basic_ostream<_CharT, _Traits>& __os, const utc_time<_Duration>& __tp) {
71+
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp);
72+
}
73+
74+
# endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
75+
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
76+
6477
template <class _CharT, class _Traits, class _Duration>
6578
_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
6679
operator<<(basic_ostream<_CharT, _Traits>& __os, const file_time<_Duration> __tp) {

libcxx/include/__chrono/utc_clock.h

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 !defined(_LIBCPP_HAS_NO_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 // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
162+
163+
#endif // _LIBCPP___CHRONO_UTC_CLOCK_H

libcxx/include/chrono

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,41 @@ template<class charT, class traits> // C++20
300300
basic_ostream<charT, traits>&
301301
operator<<(basic_ostream<charT, traits>& os, const sys_days& dp);
302302
303+
// [time.clock.utc], class utc_clock
304+
class utc_clock { // C++20
305+
public:
306+
using rep = a signed arithmetic type;
307+
using period = ratio<unspecified, unspecified>;
308+
using duration = chrono::duration<rep, period>;
309+
using time_point = chrono::time_point<utc_clock>;
310+
static constexpr bool is_steady = unspecified;
311+
312+
static time_point now();
313+
314+
template<class Duration>
315+
static sys_time<common_type_t<Duration, seconds>>
316+
to_sys(const utc_time<Duration>& t);
317+
template<class Duration>
318+
static utc_time<common_type_t<Duration, seconds>>
319+
from_sys(const sys_time<Duration>& t);
320+
};
321+
322+
template<class Duration>
323+
using utc_time = time_point<utc_clock, Duration>; // C++20
324+
using utc_seconds = utc_time<seconds>; // C++20
325+
326+
template<class charT, class traits, class Duration> // C++20
327+
basic_ostream<charT, traits>&
328+
operator<<(basic_ostream<charT, traits>& os, const utc_time<Duration>& t);
329+
330+
struct leap_second_info { // C++20
331+
bool is_leap_second;
332+
seconds elapsed;
333+
};
334+
335+
template<class Duration> // C++20
336+
leap_second_info get_leap_second_info(const utc_time<Duration>& ut);
337+
303338
class file_clock // C++20
304339
{
305340
public:
@@ -861,6 +896,8 @@ strong_ordering operator<=>(const time_zone_link& x, const time_zone_link& y);
861896
namespace std {
862897
template<class Duration, class charT>
863898
struct formatter<chrono::sys_time<Duration>, charT>; // C++20
899+
template<class Duration, class charT>
900+
struct formatter<chrono::utc_time<Duration>, charT>; // C++20
864901
template<class Duration, class charT>
865902
struct formatter<chrono::filetime<Duration>, charT>; // C++20
866903
template<class Duration, class charT>
@@ -981,6 +1018,7 @@ constexpr chrono::year operator ""y(unsigned lo
9811018
# include <__chrono/time_zone_link.h>
9821019
# include <__chrono/tzdb.h>
9831020
# include <__chrono/tzdb_list.h>
1021+
# include <__chrono/utc_clock.h>
9841022
# include <__chrono/zoned_time.h>
9851023
# endif
9861024

libcxx/include/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,10 @@ module std [system] {
980980
export std.string // public data member of type std::string
981981
export std.vector // public data members of type std::vector
982982
}
983+
module utc_clock {
984+
header "__chrono/utc_clock.h"
985+
export std.chrono.time_point
986+
}
983987
module weekday { header "__chrono/weekday.h" }
984988
module year_month_day { header "__chrono/year_month_day.h" }
985989
module year_month_weekday { header "__chrono/year_month_weekday.h" }

libcxx/modules/std/chrono.inc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ export namespace std {
8484
using std::chrono::sys_seconds;
8585
using std::chrono::sys_time;
8686

87-
#if 0
87+
#if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
88+
# ifdef _LIBCPP_ENABLE_EXPERIMENTAL
89+
8890
// [time.clock.utc], class utc_clock
8991
using std::chrono::utc_clock;
9092

@@ -94,6 +96,8 @@ export namespace std {
9496
using std::chrono::leap_second_info;
9597

9698
using std::chrono::get_leap_second_info;
99+
100+
# if 0
97101
// [time.clock.tai], class tai_clock
98102
using std::chrono::tai_clock;
99103

@@ -105,7 +109,10 @@ export namespace std {
105109

106110
using std::chrono::gps_seconds;
107111
using std::chrono::gps_time;
108-
#endif
112+
# endif
113+
# endif // _LIBCPP_ENABLE_EXPERIMENTAL
114+
#endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
115+
109116
// [time.clock.file], type file_clock
110117
using std::chrono::file_clock;
111118

0 commit comments

Comments
 (0)