Skip to content
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

[libc++][chrono] implements GPS clock. #125921

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mordante
Copy link
Member

@mordante mordante commented Feb 5, 2025

Completes:

  • LWG3359 leap second support should allow for negative leap seconds
  • P1361R2 Integration of chrono with text formatting

Implements parts of:

  • P0355 Extending to Calendars and Time Zones

Fixes: #100432
Fixes: #100014

Base automatically changed from users/mordante/tai_clock to main February 6, 2025 16:55
@mordante mordante force-pushed the users/mordante/gps_clock branch 2 times, most recently from aba7982 to 7fbba32 Compare February 6, 2025 17:51
@mordante mordante force-pushed the users/mordante/gps_clock branch from 28b566f to 7b5a265 Compare February 9, 2025 14:09
@mordante mordante marked this pull request as ready for review February 9, 2025 14:17
@mordante mordante requested a review from a team as a code owner February 9, 2025 14:17
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Feb 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 9, 2025

@llvm/pr-subscribers-libcxx

Author: Mark de Wever (mordante)

Changes

Completes:

  • LWG3359 <chrono> leap second support should allow for negative leap seconds
  • P1361 Integration of chrono with text formatting

Implements parts of:

  • P0355 Extending <chrono> to Calendars and Time Zones

Patch is 83.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/125921.diff

18 Files Affected:

  • (modified) libcxx/docs/Status/FormatPaper.csv (+1-1)
  • (modified) libcxx/include/CMakeLists.txt (+1)
  • (modified) libcxx/include/__chrono/convert_to_tm.h (+6)
  • (modified) libcxx/include/__chrono/formatter.h (+14)
  • (added) libcxx/include/__chrono/gps_clock.h (+90)
  • (modified) libcxx/include/__chrono/ostream.h (+7)
  • (modified) libcxx/include/chrono (+29)
  • (modified) libcxx/include/module.modulemap (+4)
  • (modified) libcxx/modules/std/chrono.inc (-2)
  • (modified) libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp (+11)
  • (added) libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.from_utc.pass.cpp (+73)
  • (added) libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.to_utc.pass.cpp (+73)
  • (added) libcxx/test/std/time/time.clock/time.clock.gps/gps_time.ostream.pass.cpp (+164)
  • (added) libcxx/test/std/time/time.clock/time.clock.gps/time.clock.gps.members/from_utc.pass.cpp (+158)
  • (added) libcxx/test/std/time/time.clock/time.clock.gps/time.clock.gps.members/now.pass.cpp (+33)
  • (added) libcxx/test/std/time/time.clock/time.clock.gps/time.clock.gps.members/to_utc.pass.cpp (+153)
  • (added) libcxx/test/std/time/time.clock/time.clock.gps/types.compile.pass.cpp (+60)
  • (added) libcxx/test/std/time/time.syn/formatter.gps_time.pass.cpp (+990)
diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index beec97b8c01790c..6387b5ac20a3d9a 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -4,7 +4,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
 `[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>``",,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::gps_time<Duration>``",,Mark de Wever,|Complete|,21
 `[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
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local-time-format-t<Duration>``",,,|Nothing To Do|,
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index ce805b4eb7b8b4f..4aab32a7850db6e 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -256,6 +256,7 @@ set(files
   __chrono/exception.h
   __chrono/file_clock.h
   __chrono/formatter.h
+  __chrono/gps_clock.h
   __chrono/hh_mm_ss.h
   __chrono/high_resolution_clock.h
   __chrono/leap_second.h
diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h
index d9ccf693160d292..0a6b62772609162 100644
--- a/libcxx/include/__chrono/convert_to_tm.h
+++ b/libcxx/include/__chrono/convert_to_tm.h
@@ -15,6 +15,7 @@
 #include <__chrono/day.h>
 #include <__chrono/duration.h>
 #include <__chrono/file_clock.h>
+#include <__chrono/gps_clock.h>
 #include <__chrono/hh_mm_ss.h>
 #include <__chrono/local_info.h>
 #include <__chrono/month.h>
@@ -124,6 +125,11 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::tai_time<_Duration> __tp) {
   return std::__convert_to_tm<_Tm>(chrono::sys_time<_Rp>{__tp.time_since_epoch() - __offset});
 }
 
+template <class _Tm, class _Duration>
+_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::gps_time<_Duration> __tp) {
+  return std::__convert_to_tm<_Tm>(chrono::utc_clock::to_sys(chrono::gps_clock::to_utc(__tp)));
+}
+
 #    endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB
 #  endif   // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
 
diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index 13bcb717291f699..a5caecce8a4f6ce 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -21,6 +21,7 @@
 #  include <__chrono/day.h>
 #  include <__chrono/duration.h>
 #  include <__chrono/file_clock.h>
+#  include <__chrono/gps_clock.h>
 #  include <__chrono/hh_mm_ss.h>
 #  include <__chrono/local_info.h>
 #  include <__chrono/month.h>
@@ -235,6 +236,8 @@ _LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const
 #      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_time_point<_Tp> && requires { requires same_as<typename _Tp::clock, chrono::gps_clock>; })
+    return {"GPS", chrono::seconds{0}};
   else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>)
     return __formatter::__convert_to_time_zone(__value.get_info());
 #      endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
@@ -748,6 +751,17 @@ struct _LIBCPP_TEMPLATE_VIS formatter<chrono::tai_time<_Duration>, _CharT> : pub
   }
 };
 
+template <class _Duration, __fmt_char_type _CharT>
+struct _LIBCPP_TEMPLATE_VIS formatter<chrono::gps_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
 
diff --git a/libcxx/include/__chrono/gps_clock.h b/libcxx/include/__chrono/gps_clock.h
new file mode 100644
index 000000000000000..2e220cab946e368
--- /dev/null
+++ b/libcxx/include/__chrono/gps_clock.h
@@ -0,0 +1,90 @@
+// -*- 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_GPS_CLOCK_H
+#define _LIBCPP___CHRONO_GPS_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 gps_clock;
+
+template <class _Duration>
+using gps_time    = time_point<gps_clock, _Duration>;
+using gps_seconds = gps_time<seconds>;
+
+class gps_clock {
+public:
+  using rep                       = utc_clock::rep;
+  using period                    = utc_clock::period;
+  using duration                  = chrono::duration<rep, period>;
+  using time_point                = chrono::time_point<gps_clock>;
+  static constexpr bool is_steady = false; // The utc_clock is not steady.
+
+  // The static difference between UTC and GPS time as specified in the Standard.
+  static constexpr chrono::seconds __offset{315964809};
+
+  [[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 gps_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 GPS to UTC conversion would underflow");
+
+    return utc_time<_Rp>{__time_since_epoch + __offset};
+  }
+
+  template <class _Duration>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static gps_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 GPS conversion would overflow");
+
+    return gps_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_GPS_CLOCK_H
diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h
index b8cd6a4680662b0..7a01b186780cb5e 100644
--- a/libcxx/include/__chrono/ostream.h
+++ b/libcxx/include/__chrono/ostream.h
@@ -18,6 +18,7 @@
 #  include <__chrono/day.h>
 #  include <__chrono/duration.h>
 #  include <__chrono/file_clock.h>
+#  include <__chrono/gps_clock.h>
 #  include <__chrono/hh_mm_ss.h>
 #  include <__chrono/local_info.h>
 #  include <__chrono/month.h>
@@ -78,6 +79,12 @@ 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);
 }
 
+template <class _CharT, class _Traits, class _Duration>
+_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>&
+operator<<(basic_ostream<_CharT, _Traits>& __os, const gps_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
 
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index bd4c98600440c48..8d4adc80dc1e530 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -363,6 +363,33 @@ template<class charT, class traits, class Duration>     // C++20
   basic_ostream<charT, traits>&
     operator<<(basic_ostream<charT, traits>& os, const tai_time<Duration>& t);
 
+// [time.clock.gps], class gps_clock
+class gps_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<gps_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 gps_time<Duration>& t);
+    template<class Duration>
+      static gps_time<common_type_t<Duration, seconds>>
+        from_utc(const utc_time<Duration>& t);
+};
+
+template<class Duration>
+using gps_time  = time_point<gps_clock, Duration>;      // C++20
+using gps_seconds = gps_time<seconds>;                  // C++20
+
+template<class charT, class traits, class Duration>     // C++20
+  basic_ostream<charT, traits>&
+    operator<<(basic_ostream<charT, traits>& os, const gps_time<Duration>& t);
+
 class file_clock                                        // C++20
 {
 public:
@@ -928,6 +955,8 @@ namespace std {
     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::gps_time<Duration>, charT>;                          // C++20
   template<class Duration, class charT>
     struct formatter<chrono::filetime<Duration>, charT>;                          // C++20
   template<class Duration, class charT>
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index fd39c946b992a43..eb46c2f95e055f8 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -935,6 +935,10 @@ module std [system] {
     module exception                  { header "__chrono/exception.h" }
     module file_clock                 { header "__chrono/file_clock.h" }
     module formatter                  { header "__chrono/formatter.h" }
+    module gps_clock {
+      header "__chrono/gps_clock.h"
+      export std.chrono.time_point
+    }
     module hh_mm_ss                   { header "__chrono/hh_mm_ss.h" }
     module high_resolution_clock {
       header "__chrono/high_resolution_clock.h"
diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc
index 43e8da36e904489..66eccd8d290ad17 100644
--- a/libcxx/modules/std/chrono.inc
+++ b/libcxx/modules/std/chrono.inc
@@ -103,13 +103,11 @@ export namespace std {
     using std::chrono::tai_seconds;
     using std::chrono::tai_time;
 
-#    if 0
     // [time.clock.gps], class gps_clock
     using std::chrono::gps_clock;
 
     using std::chrono::gps_seconds;
     using std::chrono::gps_time;
-#    endif
 #  endif // _LIBCPP_ENABLE_EXPERIMENTAL
 #endif   //  _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION
 
diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
index bb40e0cfc4e1b8a..4e7380b86399359 100644
--- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp
@@ -113,4 +113,15 @@ void test(std::chrono::time_zone tz, std::chrono::time_zone_link link, std::chro
     // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
     std::chrono::tai_clock::from_utc(std::chrono::utc_seconds{});
   }
+
+  { // [time.clock.gps]
+    // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+    std::chrono::gps_clock::now();
+
+    // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+    std::chrono::gps_clock::to_utc(std::chrono::gps_seconds{});
+
+    // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+    std::chrono::gps_clock::from_utc(std::chrono::utc_seconds{});
+  }
 }
diff --git a/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.from_utc.pass.cpp b/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.from_utc.pass.cpp
new file mode 100644
index 000000000000000..d8200439d973761
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.from_utc.pass.cpp
@@ -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 gps_clock;
+
+// static gps_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
+//   gps_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{315964809};
+
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::nanoseconds>::max() - offset + 1ns),
+      "the UTC to GPS conversion would overflow");
+
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::microseconds>::max() - offset + 1us),
+      "the UTC to GPS conversion would overflow");
+
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::milliseconds>::max() - offset + 1ms),
+      "the UTC to GPS conversion would overflow");
+
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_seconds::max() - offset);
+  TEST_LIBCPP_ASSERT_FAILURE(std::chrono::gps_clock::from_utc(std::chrono::utc_seconds::max() - offset + 1s),
+                             "the UTC to GPS 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::gps_clock::from_utc(std::chrono::utc_time<std::chrono::days>::max());
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::weeks>::max());
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::months>::max());
+  (void)std::chrono::gps_clock::from_utc(std::chrono::utc_time<std::chrono::years>::max());
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.to_utc.pass.cpp b/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.to_utc.pass.cpp
new file mode 100644
index 000000000000000..d61b3374f661f46
--- /dev/null
+++ b/libcxx/test/libcxx/time/time.clock/time.clock.gps/time.clock.gps.members/assert.to_utc.pass.cpp
@@ -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 gps_clock;
+
+// static utc_time<common_type_t<_Duration, seconds>>
+// to_utc(const gps_time<_Duration>& t) noexcept;
+
+#include <chrono>
+
+#include "check_assertion.h"
+
+// The function is specified as
+//   utc_time<common_type_t<Duration, seconds>>{t.time_since_epoch()} - 378691210s
+// When t == t.min() there will be a signed integral underlow (other values too).
+int main(int, char**) {
+  using namespace std::literals::chrono_literals;
+  constexpr std::chrono::seconds offset{315964809};
+
+  (void)std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::nanoseconds>::min() + offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::nanoseconds>::min() + offset - 1ns),
+      "the GPS to UTC conversion would underflow");
+
+  (void)std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::microseconds>::min() + offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::microseconds>::min() + offset - 1us),
+      "the GPS to UTC conversion would underflow");
+
+  (void)std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::milliseconds>::min() + offset);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::chrono::gps_clock::to_utc(std::chrono::gps_time<std::chrono::milliseconds>::min() + offset - 1ms),
+      "the GPS to UTC conversion would underflow");
+
+  (void)std::chrono::gps_clock::to_utc(s...
[truncated]

Completes:
- LWG3359 <chrono> leap second support should allow for negative leap seconds

Implements parts of:
- P0355 Extending <chrono> to Calendars and Time Zones
- P1361 Integration of chrono with text formatting

NOTE The original version of this patch was written before finishing the
tzdb formatters so need to review the status of the "parts of papers"
are they still part of are complete after this patch?
@mordante mordante force-pushed the users/mordante/gps_clock branch from 7b5a265 to 45205ed Compare February 20, 2025 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
4 participants