Skip to content

[libc++][chrono] implements TAI clock. #125550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libcxx/docs/Status/FormatPaper.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|Complete|,16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_time<Duration>``",,Mark de Wever,|Complete|,17
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,|Complete|,20
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",,Mark de Wever,|Complete|,21
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::gps_time<Duration>``",A ``<chrono>`` implementation,Mark de Wever,,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::file_time<Duration>``",,Mark de Wever,|Complete|,17
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_time<Duration>``",,Mark de Wever,|Complete|,17
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ set(files
__chrono/steady_clock.h
__chrono/sys_info.h
__chrono/system_clock.h
__chrono/tai_clock.h
__chrono/time_point.h
__chrono/time_zone.h
__chrono/time_zone_link.h
Expand Down
14 changes: 14 additions & 0 deletions libcxx/include/__chrono/convert_to_tm.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <__chrono/statically_widen.h>
#include <__chrono/sys_info.h>
#include <__chrono/system_clock.h>
#include <__chrono/tai_clock.h>
#include <__chrono/time_point.h>
#include <__chrono/utc_clock.h>
#include <__chrono/weekday.h>
Expand All @@ -35,6 +36,7 @@
#include <__config>
#include <__format/format_error.h>
#include <__memory/addressof.h>
#include <__type_traits/common_type.h>
#include <__type_traits/is_convertible.h>
#include <__type_traits/is_specialization.h>
#include <cstdint>
Expand Down Expand Up @@ -112,6 +114,16 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::utc_time<_Duration> __tp) {
return __result;
}

template <class _Tm, class _Duration>
_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::tai_time<_Duration> __tp) {
using _Rp = common_type_t<_Duration, chrono::seconds>;
// The time between the TAI epoch (1958-01-01) and UNIX epoch (1970-01-01).
// This avoids leap second conversion when going from TAI to UTC.
// (It also avoids issues when the date is before the UTC epoch.)
constexpr chrono::seconds __offset{4383 * 24 * 60 * 60};
return std::__convert_to_tm<_Tm>(chrono::sys_time<_Rp>{__tp.time_since_epoch() - __offset});
}

# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION

Expand All @@ -131,6 +143,8 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
# if _LIBCPP_HAS_EXPERIMENTAL_TZDB
else if constexpr (same_as<typename _ChronoT::clock, chrono::utc_clock>)
return std::__convert_to_tm<_Tm>(__value);
else if constexpr (same_as<typename _ChronoT::clock, chrono::tai_clock>)
return std::__convert_to_tm<_Tm>(__value);
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
else if constexpr (same_as<typename _ChronoT::clock, chrono::file_clock>)
Expand Down
16 changes: 15 additions & 1 deletion libcxx/include/__chrono/formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# include <__chrono/statically_widen.h>
# include <__chrono/sys_info.h>
# include <__chrono/system_clock.h>
# include <__chrono/tai_clock.h>
# include <__chrono/time_point.h>
# include <__chrono/utc_clock.h>
# include <__chrono/weekday.h>
Expand Down Expand Up @@ -232,9 +233,11 @@ _LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const
if constexpr (same_as<_Tp, chrono::sys_info>)
return {__value.abbrev, __value.offset};
# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
else if constexpr (__is_time_point<_Tp> && requires { requires same_as<typename _Tp::clock, chrono::tai_clock>; })
return {"TAI", chrono::seconds{0}};
else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
return __formatter::__convert_to_time_zone(__value.get_info());
# endif
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
else
# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
return {"UTC", chrono::seconds{0}};
Expand Down Expand Up @@ -734,6 +737,17 @@ struct _LIBCPP_TEMPLATE_VIS formatter<chrono::utc_time<_Duration>, _CharT> : pub
}
};

template <class _Duration, __fmt_char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS formatter<chrono::tai_time<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
public:
using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>;

template <class _ParseContext>
_LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock);
}
};

# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM

Expand Down
7 changes: 7 additions & 0 deletions libcxx/include/__chrono/ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# include <__chrono/statically_widen.h>
# include <__chrono/sys_info.h>
# include <__chrono/system_clock.h>
# include <__chrono/tai_clock.h>
# include <__chrono/utc_clock.h>
# include <__chrono/weekday.h>
# include <__chrono/year.h>
Expand Down Expand Up @@ -71,6 +72,12 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const utc_time<_Duration>& __tp
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp);
}

template <class _CharT, class _Traits, class _Duration>
_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, const tai_time<_Duration>& __tp) {
return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp);
}

# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM

Expand Down
108 changes: 108 additions & 0 deletions libcxx/include/__chrono/tai_clock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// -*- 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_TAI_CLOCK_H
#define _LIBCPP___CHRONO_TAI_CLOCK_H

#include <version>
// Enable the contents of the header only when libc++ was built with experimental features enabled.
#if _LIBCPP_HAS_EXPERIMENTAL_TZDB

# include <__assert>
# include <__chrono/duration.h>
# include <__chrono/time_point.h>
# include <__chrono/utc_clock.h>
# include <__config>
# include <__type_traits/common_type.h>

# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
# endif

_LIBCPP_PUSH_MACROS
# include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

# if _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION

namespace chrono {

class tai_clock;

template <class _Duration>
using tai_time = time_point<tai_clock, _Duration>;
using tai_seconds = tai_time<seconds>;

// [time.clock.tai.overview]/1
// The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is
// offset 10s ahead of UTC at this date. That is, 1958-01-01 00:00:00 TAI is
// equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into
// TAI. Therefore every time a leap second is inserted into UTC, UTC shifts
// another second with respect to TAI. For example by 2000-01-01 there had
// been 22 positive and 0 negative leap seconds inserted so 2000-01-01
// 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the
// initial 10s offset).
//
// Note this does not specify what the UTC offset before 1958-01-01 00:00:00
// TAI is, nor does it follow the "real" TAI clock between 1958-01-01 and the
// start of the UTC epoch. So while the member functions are fully specified in
// the standard, they do not technically follow the "real-world" TAI clock with
// 100% accuracy.
//
// https://koka-lang.github.io/koka/doc/std_time_utc.html contains more
// information and references.
class tai_clock {
public:
using rep = utc_clock::rep;
using period = utc_clock::period;
using duration = chrono::duration<rep, period>;
using time_point = chrono::time_point<tai_clock>;
static constexpr bool is_steady = false; // The utc_clock is not steady.

// The static difference between UTC and TAI time.
static constexpr chrono::seconds __offset{378691210};

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static time_point now() { return from_utc(utc_clock::now()); }

template <class _Duration>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static utc_time<common_type_t<_Duration, seconds>>
to_utc(const tai_time<_Duration>& __time) noexcept {
using _Rp = common_type_t<_Duration, seconds>;
_Duration __time_since_epoch = __time.time_since_epoch();
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch >= utc_time<_Rp>::min().time_since_epoch() + __offset,
"the TAI to UTC conversion would underflow");

return utc_time<_Rp>{__time_since_epoch - __offset};
}

template <class _Duration>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static tai_time<common_type_t<_Duration, seconds>>
from_utc(const utc_time<_Duration>& __time) noexcept {
using _Rp = common_type_t<_Duration, seconds>;
_Duration __time_since_epoch = __time.time_since_epoch();
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch <= utc_time<_Rp>::max().time_since_epoch() - __offset,
"the UTC to TAI conversion would overflow");

return tai_time<_Rp>{__time_since_epoch + __offset};
}
};

} // namespace chrono

# endif // _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM &&
// _LIBCPP_HAS_LOCALIZATION

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB

#endif // _LIBCPP___CHRONO_TAI_CLOCK_H
31 changes: 31 additions & 0 deletions libcxx/include/chrono
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,34 @@ struct leap_second_info { // C++20
template<class Duration> // C++20
leap_second_info get_leap_second_info(const utc_time<Duration>& ut);


// [time.clock.tai], class tai_clock
class tai_clock { // C++20
public:
using rep = a signed arithmetic type;
using period = ratio<unspecified, unspecified>;
using duration = chrono::duration<rep, period>;
using time_point = chrono::time_point<tai_clock>;
static constexpr bool is_steady = unspecified;

static time_point now();

template<class Duration>
static utc_time<common_type_t<Duration, seconds>>
to_utc(const tai_time<Duration>& t);
template<class Duration>
static tai_time<common_type_t<Duration, seconds>>
from_utc(const utc_time<Duration>& t);
};

template<class Duration>
using tai_time = time_point<tai_clock, Duration>; // C++20
using tai_seconds = tai_time<seconds>; // C++20

template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const tai_time<Duration>& t);

class file_clock // C++20
{
public:
Expand Down Expand Up @@ -898,6 +926,8 @@ namespace std {
struct formatter<chrono::sys_time<Duration>, charT>; // C++20
template<class Duration, class charT>
struct formatter<chrono::utc_time<Duration>, charT>; // C++20
template<class Duration, class charT>
struct formatter<chrono::tai_time<Duration>, charT>; // C++20
template<class Duration, class charT>
struct formatter<chrono::filetime<Duration>, charT>; // C++20
template<class Duration, class charT>
Expand Down Expand Up @@ -1014,6 +1044,7 @@ constexpr chrono::year operator ""y(unsigned lo

# if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
# include <__chrono/leap_second.h>
# include <__chrono/tai_clock.h>
# include <__chrono/time_zone.h>
# include <__chrono/time_zone_link.h>
# include <__chrono/tzdb.h>
Expand Down
4 changes: 4 additions & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,10 @@ module std [system] {
header "__chrono/system_clock.h"
export std.chrono.time_point
}
module tai_clock {
header "__chrono/tai_clock.h"
export std.chrono.time_point
}
module time_point { header "__chrono/time_point.h" }
module time_zone_link { header "__chrono/time_zone_link.h" }
module time_zone { header "__chrono/time_zone.h" }
Expand Down
2 changes: 1 addition & 1 deletion libcxx/modules/std/chrono.inc
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ export namespace std {

using std::chrono::get_leap_second_info;

# if 0
// [time.clock.tai], class tai_clock
using std::chrono::tai_clock;

using std::chrono::tai_seconds;
using std::chrono::tai_time;

# if 0
// [time.clock.gps], class gps_clock
using std::chrono::gps_clock;

Expand Down
11 changes: 11 additions & 0 deletions libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,15 @@ void test(std::chrono::time_zone tz, std::chrono::time_zone_link link, std::chro
zt.get_sys_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
zt.get_info(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}

{ // [time.clock.tai]
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::chrono::tai_clock::now();

// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::chrono::tai_clock::to_utc(std::chrono::tai_seconds{});

// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
std::chrono::tai_clock::from_utc(std::chrono::utc_seconds{});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// REQUIRES: std-at-least-c++20
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-experimental-tzdb
// XFAIL: availability-tzdb-missing

// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
// REQUIRES: has-unix-headers
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing

// <chrono>
//
// class tai_clock;

// static tai_time<common_type_t<_Duration, seconds>>
// from_utc(const utc_time<_Duration>& t) noexcept;

#include <chrono>

#include "check_assertion.h"

// The function is specified as
// tai_time<common_type_t<Duration, seconds>>{t.time_since_epoch()} + 378691210s
// When t == t.max() there will be a signed integral overflow (other values too).
int main(int, char**) {
using namespace std::literals::chrono_literals;
constexpr std::chrono::seconds offset{378691210};

(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset);
TEST_LIBCPP_ASSERT_FAILURE(
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset + 1ns),
"the UTC to TAI conversion would overflow");

(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset);
TEST_LIBCPP_ASSERT_FAILURE(
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset + 1us),
"the UTC to TAI conversion would overflow");

(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset);
TEST_LIBCPP_ASSERT_FAILURE(
std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset + 1ms),
"the UTC to TAI conversion would overflow");

(void)std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset);
TEST_LIBCPP_ASSERT_FAILURE(std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset + 1s),
"the UTC to TAI conversion would overflow");

// The conversion uses `common_type_t<Duration, seconds>` so types "larger"
// than seconds are converted to seconds. Types "larger" than seconds are
// stored in "smaller" intergral and the overflow can never occur.

// Validate the types can never overflow on all current (and future) supported platforms.
static_assert(std::chrono::utc_time<std::chrono::days>::max() <= std::chrono::utc_seconds::max() - offset);
static_assert(std::chrono::utc_time<std::chrono::weeks>::max() <= std::chrono::utc_seconds::max() - offset);
static_assert(std::chrono::utc_time<std::chrono::months>::max() <= std::chrono::utc_seconds::max() - offset);
static_assert(std::chrono::utc_time<std::chrono::years>::max() <= std::chrono::utc_seconds::max() - offset);

// Validate the run-time conversion works.
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::days>::max());
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::weeks>::max());
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::months>::max());
(void)std::chrono::tai_clock::from_utc(std::chrono::utc_time<std::chrono::years>::max());

return 0;
}
Loading