Skip to content

Commit aca829d

Browse files
authored
[libc++][chrono] implements TAI clock. (llvm#125550)
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 ead88c7 commit aca829d

File tree

19 files changed

+1923
-3
lines changed

19 files changed

+1923
-3
lines changed

libcxx/docs/Status/FormatPaper.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
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
55
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,|Complete|,20
6-
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
6+
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",,Mark de Wever,|Complete|,21
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
99
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_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
@@ -270,6 +270,7 @@ set(files
270270
__chrono/steady_clock.h
271271
__chrono/sys_info.h
272272
__chrono/system_clock.h
273+
__chrono/tai_clock.h
273274
__chrono/time_point.h
274275
__chrono/time_zone.h
275276
__chrono/time_zone_link.h

libcxx/include/__chrono/convert_to_tm.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <__chrono/statically_widen.h>
2424
#include <__chrono/sys_info.h>
2525
#include <__chrono/system_clock.h>
26+
#include <__chrono/tai_clock.h>
2627
#include <__chrono/time_point.h>
2728
#include <__chrono/utc_clock.h>
2829
#include <__chrono/weekday.h>
@@ -35,6 +36,7 @@
3536
#include <__config>
3637
#include <__format/format_error.h>
3738
#include <__memory/addressof.h>
39+
#include <__type_traits/common_type.h>
3840
#include <__type_traits/is_convertible.h>
3941
#include <__type_traits/is_specialization.h>
4042
#include <cstdint>
@@ -112,6 +114,16 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::utc_time<_Duration> __tp) {
112114
return __result;
113115
}
114116

117+
template <class _Tm, class _Duration>
118+
_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::tai_time<_Duration> __tp) {
119+
using _Rp = common_type_t<_Duration, chrono::seconds>;
120+
// The time between the TAI epoch (1958-01-01) and UNIX epoch (1970-01-01).
121+
// This avoids leap second conversion when going from TAI to UTC.
122+
// (It also avoids issues when the date is before the UTC epoch.)
123+
constexpr chrono::seconds __offset{4383 * 24 * 60 * 60};
124+
return std::__convert_to_tm<_Tm>(chrono::sys_time<_Rp>{__tp.time_since_epoch() - __offset});
125+
}
126+
115127
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
116128
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
117129

@@ -131,6 +143,8 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
131143
# if _LIBCPP_HAS_EXPERIMENTAL_TZDB
132144
else if constexpr (same_as<typename _ChronoT::clock, chrono::utc_clock>)
133145
return std::__convert_to_tm<_Tm>(__value);
146+
else if constexpr (same_as<typename _ChronoT::clock, chrono::tai_clock>)
147+
return std::__convert_to_tm<_Tm>(__value);
134148
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
135149
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
136150
else if constexpr (same_as<typename _ChronoT::clock, chrono::file_clock>)

libcxx/include/__chrono/formatter.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# include <__chrono/statically_widen.h>
3232
# include <__chrono/sys_info.h>
3333
# include <__chrono/system_clock.h>
34+
# include <__chrono/tai_clock.h>
3435
# include <__chrono/time_point.h>
3536
# include <__chrono/utc_clock.h>
3637
# include <__chrono/weekday.h>
@@ -232,9 +233,11 @@ _LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const
232233
if constexpr (same_as<_Tp, chrono::sys_info>)
233234
return {__value.abbrev, __value.offset};
234235
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
236+
else if constexpr (__is_time_point<_Tp> && requires { requires same_as<typename _Tp::clock, chrono::tai_clock>; })
237+
return {"TAI", chrono::seconds{0}};
235238
else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
236239
return __formatter::__convert_to_time_zone(__value.get_info());
237-
# endif
240+
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
238241
else
239242
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
240243
return {"UTC", chrono::seconds{0}};
@@ -734,6 +737,17 @@ struct _LIBCPP_TEMPLATE_VIS formatter<chrono::utc_time<_Duration>, _CharT> : pub
734737
}
735738
};
736739

740+
template <class _Duration, __fmt_char_type _CharT>
741+
struct _LIBCPP_TEMPLATE_VIS formatter<chrono::tai_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
742+
public:
743+
using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;
744+
745+
template <class _ParseContext>
746+
_LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
747+
return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
748+
}
749+
};
750+
737751
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
738752
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
739753

libcxx/include/__chrono/ostream.h

Lines changed: 7 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/tai_clock.h>
2930
# include <__chrono/utc_clock.h>
3031
# include <__chrono/weekday.h>
3132
# include <__chrono/year.h>
@@ -71,6 +72,12 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const utc_time<_Duration>& __tp
7172
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp);
7273
}
7374

75+
template <class _CharT, class _Traits, class _Duration>
76+
_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
77+
operator<<(basic_ostream<_CharT, _Traits>& __os, const tai_time<_Duration>& __tp) {
78+
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp);
79+
}
80+
7481
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
7582
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
7683

libcxx/include/__chrono/tai_clock.h

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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_TAI_CLOCK_H
11+
#define _LIBCPP___CHRONO_TAI_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 <__assert>
18+
# include <__chrono/duration.h>
19+
# include <__chrono/time_point.h>
20+
# include <__chrono/utc_clock.h>
21+
# include <__config>
22+
# include <__type_traits/common_type.h>
23+
24+
# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
25+
# pragma GCC system_header
26+
# endif
27+
28+
_LIBCPP_PUSH_MACROS
29+
# include <__undef_macros>
30+
31+
_LIBCPP_BEGIN_NAMESPACE_STD
32+
33+
# if _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
34+
35+
namespace chrono {
36+
37+
class tai_clock;
38+
39+
template <class _Duration>
40+
using tai_time = time_point<tai_clock, _Duration>;
41+
using tai_seconds = tai_time<seconds>;
42+
43+
// [time.clock.tai.overview]/1
44+
// The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is
45+
// offset 10s ahead of UTC at this date. That is, 1958-01-01 00:00:00 TAI is
46+
// equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into
47+
// TAI. Therefore every time a leap second is inserted into UTC, UTC shifts
48+
// another second with respect to TAI. For example by 2000-01-01 there had
49+
// been 22 positive and 0 negative leap seconds inserted so 2000-01-01
50+
// 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the
51+
// initial 10s offset).
52+
//
53+
// Note this does not specify what the UTC offset before 1958-01-01 00:00:00
54+
// TAI is, nor does it follow the "real" TAI clock between 1958-01-01 and the
55+
// start of the UTC epoch. So while the member functions are fully specified in
56+
// the standard, they do not technically follow the "real-world" TAI clock with
57+
// 100% accuracy.
58+
//
59+
// https://koka-lang.github.io/koka/doc/std_time_utc.html contains more
60+
// information and references.
61+
class tai_clock {
62+
public:
63+
using rep = utc_clock::rep;
64+
using period = utc_clock::period;
65+
using duration = chrono::duration<rep, period>;
66+
using time_point = chrono::time_point<tai_clock>;
67+
static constexpr bool is_steady = false; // The utc_clock is not steady.
68+
69+
// The static difference between UTC and TAI time.
70+
static constexpr chrono::seconds __offset{378691210};
71+
72+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static time_point now() { return from_utc(utc_clock::now()); }
73+
74+
template <class _Duration>
75+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static utc_time<common_type_t<_Duration, seconds>>
76+
to_utc(const tai_time<_Duration>& __time) noexcept {
77+
using _Rp = common_type_t<_Duration, seconds>;
78+
_Duration __time_since_epoch = __time.time_since_epoch();
79+
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch >= utc_time<_Rp>::min().time_since_epoch() + __offset,
80+
"the TAI to UTC conversion would underflow");
81+
82+
return utc_time<_Rp>{__time_since_epoch - __offset};
83+
}
84+
85+
template <class _Duration>
86+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static tai_time<common_type_t<_Duration, seconds>>
87+
from_utc(const utc_time<_Duration>& __time) noexcept {
88+
using _Rp = common_type_t<_Duration, seconds>;
89+
_Duration __time_since_epoch = __time.time_since_epoch();
90+
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch <= utc_time<_Rp>::max().time_since_epoch() - __offset,
91+
"the UTC to TAI conversion would overflow");
92+
93+
return tai_time<_Rp>{__time_since_epoch + __offset};
94+
}
95+
};
96+
97+
} // namespace chrono
98+
99+
# endif // _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM &&
100+
// _LIBCPP_HAS_LOCALIZATION
101+
102+
_LIBCPP_END_NAMESPACE_STD
103+
104+
_LIBCPP_POP_MACROS
105+
106+
#endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
107+
108+
#endif // _LIBCPP___CHRONO_TAI_CLOCK_H

libcxx/include/chrono

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,34 @@ struct leap_second_info { // C++20
335335
template<class Duration> // C++20
336336
leap_second_info get_leap_second_info(const utc_time<Duration>& ut);
337337
338+
339+
// [time.clock.tai], class tai_clock
340+
class tai_clock { // C++20
341+
public:
342+
using rep = a signed arithmetic type;
343+
using period = ratio<unspecified, unspecified>;
344+
using duration = chrono::duration<rep, period>;
345+
using time_point = chrono::time_point<tai_clock>;
346+
static constexpr bool is_steady = unspecified;
347+
348+
static time_point now();
349+
350+
template<class Duration>
351+
static utc_time<common_type_t<Duration, seconds>>
352+
to_utc(const tai_time<Duration>& t);
353+
template<class Duration>
354+
static tai_time<common_type_t<Duration, seconds>>
355+
from_utc(const utc_time<Duration>& t);
356+
};
357+
358+
template<class Duration>
359+
using tai_time = time_point<tai_clock, Duration>; // C++20
360+
using tai_seconds = tai_time<seconds>; // C++20
361+
362+
template<class charT, class traits, class Duration> // C++20
363+
basic_ostream<charT, traits>&
364+
operator<<(basic_ostream<charT, traits>& os, const tai_time<Duration>& t);
365+
338366
class file_clock // C++20
339367
{
340368
public:
@@ -898,6 +926,8 @@ namespace std {
898926
struct formatter<chrono::sys_time<Duration>, charT>; // C++20
899927
template<class Duration, class charT>
900928
struct formatter<chrono::utc_time<Duration>, charT>; // C++20
929+
template<class Duration, class charT>
930+
struct formatter<chrono::tai_time<Duration>, charT>; // C++20
901931
template<class Duration, class charT>
902932
struct formatter<chrono::filetime<Duration>, charT>; // C++20
903933
template<class Duration, class charT>
@@ -1014,6 +1044,7 @@ constexpr chrono::year operator ""y(unsigned lo
10141044

10151045
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
10161046
# include <__chrono/leap_second.h>
1047+
# include <__chrono/tai_clock.h>
10171048
# include <__chrono/time_zone.h>
10181049
# include <__chrono/time_zone_link.h>
10191050
# include <__chrono/tzdb.h>

libcxx/include/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,10 @@ module std [system] {
967967
header "__chrono/system_clock.h"
968968
export std.chrono.time_point
969969
}
970+
module tai_clock {
971+
header "__chrono/tai_clock.h"
972+
export std.chrono.time_point
973+
}
970974
module time_point { header "__chrono/time_point.h" }
971975
module time_zone_link { header "__chrono/time_zone_link.h" }
972976
module time_zone { header "__chrono/time_zone.h" }

libcxx/modules/std/chrono.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ export namespace std {
9797

9898
using std::chrono::get_leap_second_info;
9999

100-
# if 0
101100
// [time.clock.tai], class tai_clock
102101
using std::chrono::tai_clock;
103102

104103
using std::chrono::tai_seconds;
105104
using std::chrono::tai_time;
106105

106+
# if 0
107107
// [time.clock.gps], class gps_clock
108108
using std::chrono::gps_clock;
109109

libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,15 @@ void test(std::chrono::time_zone tz, std::chrono::time_zone_link link, std::chro
102102
zt.get_sys_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
103103
zt.get_info(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
104104
}
105+
106+
{ // [time.clock.tai]
107+
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
108+
std::chrono::tai_clock::now();
109+
110+
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
111+
std::chrono::tai_clock::to_utc(std::chrono::tai_seconds{});
112+
113+
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
114+
std::chrono::tai_clock::from_utc(std::chrono::utc_seconds{});
115+
}
105116
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: std-at-least-c++20
10+
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb
11+
12+
// XFAIL: libcpp-has-no-experimental-tzdb
13+
// XFAIL: availability-tzdb-missing
14+
15+
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
16+
// REQUIRES: has-unix-headers
17+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
18+
19+
// <chrono>
20+
//
21+
// class tai_clock;
22+
23+
// static tai_time<common_type_t<_Duration, seconds>>
24+
// from_utc(const utc_time<_Duration>& t) noexcept;
25+
26+
#include <chrono>
27+
28+
#include "check_assertion.h"
29+
30+
// The function is specified as
31+
// tai_time<common_type_t<Duration, seconds>>{t.time_since_epoch()} + 378691210s
32+
// When t == t.max() there will be a signed integral overflow (other values too).
33+
int main(int, char**) {
34+
using namespace std::literals::chrono_literals;
35+
constexpr std::chrono::seconds offset{378691210};
36+
37+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset);
38+
TEST_LIBCPP_ASSERT_FAILURE(
39+
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset + 1ns),
40+
"the UTC to TAI conversion would overflow");
41+
42+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset);
43+
TEST_LIBCPP_ASSERT_FAILURE(
44+
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset + 1us),
45+
"the UTC to TAI conversion would overflow");
46+
47+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset);
48+
TEST_LIBCPP_ASSERT_FAILURE(
49+
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset + 1ms),
50+
"the UTC to TAI conversion would overflow");
51+
52+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset);
53+
TEST_LIBCPP_ASSERT_FAILURE(std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset + 1s),
54+
"the UTC to TAI conversion would overflow");
55+
56+
// The conversion uses `common_type_t<Duration, seconds>` so types "larger"
57+
// than seconds are converted to seconds. Types "larger" than seconds are
58+
// stored in "smaller" intergral and the overflow can never occur.
59+
60+
// Validate the types can never overflow on all current (and future) supported platforms.
61+
static_assert(std::chrono::utc_time<std::chrono::days>::max() <= std::chrono::utc_seconds::max() - offset);
62+
static_assert(std::chrono::utc_time<std::chrono::weeks>::max() <= std::chrono::utc_seconds::max() - offset);
63+
static_assert(std::chrono::utc_time<std::chrono::months>::max() <= std::chrono::utc_seconds::max() - offset);
64+
static_assert(std::chrono::utc_time<std::chrono::years>::max() <= std::chrono::utc_seconds::max() - offset);
65+
66+
// Validate the run-time conversion works.
67+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::days>::max());
68+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::weeks>::max());
69+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::months>::max());
70+
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::years>::max());
71+
72+
return 0;
73+
}

0 commit comments

Comments
 (0)