[libcxx-commits] [libcxx] [libc++] Enable experimental tzdb on Apple platforms (PR #122010)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jan 8 07:17:19 PST 2025


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/122010

>From d335a22d9523c3a9ebed701dba4a1a9ad26d0096 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 7 Jan 2025 18:32:34 -0500
Subject: [PATCH 1/3] [libc++] Enable experimental tzdb on Apple platforms

This patch implements binary parsing of the leapseconds file provided
on Apple platforms and enables experimental tzdb support on Apple.
---
 libcxx/CMakeLists.txt            |   2 +-
 libcxx/docs/ReleaseNotes/20.rst  |   2 +
 libcxx/src/experimental/tzdb.cpp | 134 +++++++++++++++++++++++++++----
 3 files changed, 122 insertions(+), 16 deletions(-)

diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index abe12c2805a7cf..c957c12f081fa6 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -113,7 +113,7 @@ option(LIBCXX_ENABLE_MONOTONIC_CLOCK
 #
 # TODO TZDB make the default always ON when most platforms ship with the IANA
 # database.
-if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|Darwin")
   set(ENABLE_TIME_ZONE_DATABASE_DEFAULT ON)
 else()
   set(ENABLE_TIME_ZONE_DATABASE_DEFAULT OFF)
diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst
index c8a07fb8b73348..cc34127b5b2202 100644
--- a/libcxx/docs/ReleaseNotes/20.rst
+++ b/libcxx/docs/ReleaseNotes/20.rst
@@ -73,6 +73,8 @@ Improvements and New Features
   optimized, resulting in a performance improvement of up to 2x for trivial element types (e.g., `std::vector<int>`),
   and up to 3.4x for non-trivial element types (e.g., `std::vector<std::vector<int>>`).
 
+- Experimental support for ``std::chrono::tzdb`` has now been implemented on Apple platforms.
+
 Deprecations and Removals
 -------------------------
 
diff --git a/libcxx/src/experimental/tzdb.cpp b/libcxx/src/experimental/tzdb.cpp
index d22de21c998198..acc95960ade75f 100644
--- a/libcxx/src/experimental/tzdb.cpp
+++ b/libcxx/src/experimental/tzdb.cpp
@@ -50,6 +50,8 @@ namespace chrono {
 _LIBCPP_WEAK string_view __libcpp_tzdb_directory() {
 #if defined(__linux__)
   return "/usr/share/zoneinfo/";
+#elif defined(__APPLE__)
+  return "/usr/share/zoneinfo/";
 #else
 #  error "unknown path to the IANA Time Zone Database"
 #endif
@@ -94,14 +96,23 @@ static void __skip(istream& __input, string_view __suffix) {
 }
 
 static void __matches(istream& __input, char __expected) {
-  if (std::tolower(__input.get()) != __expected)
-    std::__throw_runtime_error((string("corrupt tzdb: expected character '") + __expected + '\'').c_str());
+  _LIBCPP_ASSERT_INTERNAL(std::islower(__expected), "lowercase characters only here!");
+  char __c = __input.get();
+  if (std::tolower(__c) != __expected)
+    std::__throw_runtime_error(
+        (string("corrupt tzdb: expected character '") + __expected + "', got '" + __c + "'").c_str());
 }
 
 static void __matches(istream& __input, string_view __expected) {
-  for (auto __c : __expected)
-    if (std::tolower(__input.get()) != __c)
-      std::__throw_runtime_error((string("corrupt tzdb: expected string '") + string(__expected) + '\'').c_str());
+  for (auto __c : __expected) {
+    _LIBCPP_ASSERT_INTERNAL(std::islower(__c), "lowercase strings only here!");
+    char __actual = __input.get();
+    if (std::tolower(__actual) != __c)
+      std::__throw_runtime_error(
+          (string("corrupt tzdb: expected character '") + __c + "' from string '" + string(__expected) + "', got '" +
+           __actual + "' instead")
+              .c_str());
+  }
 }
 
 [[nodiscard]] static string __parse_string(istream& __input) {
@@ -621,7 +632,96 @@ static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istr
   }
 }
 
-static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) {
+// This function parses the leap-seconds "binary file" compiled from the .list file
+// by the zic compiler. That format is what's provided on some platforms like Darwin.
+//
+// The format looks like:
+//
+//    # Leap  YEAR    MON     DAY     23:59:60        +       S
+//    Leap    1972    Jun     30      23:59:60        +       S
+//    Leap    1972    Dec     31      23:59:60        +       S
+//    Leap    1973    Dec     31      23:59:60        +       S
+//
+inline vector<leap_second> __parse_leap_seconds_binary(istream&& __input) {
+  vector<leap_second> __result;
+  [&] {
+    while (true) {
+      switch (__input.peek()) {
+      case istream::traits_type::eof():
+        return;
+
+      case ' ':
+      case '\t':
+      case '\n':
+        __input.get();
+        continue;
+
+      case '#':
+        chrono::__skip_line(__input);
+        continue;
+      }
+
+      chrono::__matches(__input, "leap");
+      chrono::__skip_mandatory_whitespace(__input);
+
+      year __year = chrono::__parse_year_value(__input);
+      chrono::__skip_mandatory_whitespace(__input);
+
+      month __month = chrono::__parse_month(__input);
+      chrono::__skip_mandatory_whitespace(__input);
+
+      day __day = chrono::__parse_day(__input);
+      chrono::__skip_mandatory_whitespace(__input);
+
+      hours __hour(chrono::__parse_integral(__input, /* leading zeros allowed */ true));
+      chrono::__matches(__input, ':');
+
+      minutes __minute(chrono::__parse_integral(__input, /* leading zeros allowed */ true));
+      chrono::__matches(__input, ':');
+
+      seconds __second(chrono::__parse_integral(__input, /* leading zeros allowed */ true));
+      chrono::__skip_mandatory_whitespace(__input);
+
+      // Now create a timestamp from everything we parsed
+      year_month_day __date   = __year / __month / __day;
+      seconds __time          = __hour + __minute + __second;
+      sys_seconds __timestamp = sys_seconds(sys_days(__date)) + __time;
+
+      // Finally, parse the value of the leap second (1s or -1s)
+      string __sign = __parse_string(__input);
+      seconds __value;
+      if (__sign == "+")
+        __value = seconds{1};
+      else if (__sign == "-")
+        __value = seconds{-1};
+      else
+        std::__throw_runtime_error(("corrupt tzdb: invalid leap second sign " + __sign).c_str());
+
+      chrono::__skip_line(__input);
+
+      __result.emplace_back(std::__private_constructor_tag{}, __timestamp, __value);
+    }
+  }();
+
+  // Ensure the leap seconds are sorted properly.
+  ranges::sort(__result, {}, &leap_second::date);
+
+  return __result;
+}
+
+// This function parses leap-seconds.list file as can be found at
+// https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list
+//
+// The format looks like
+//
+//    #NTP Time      DTAI    Day Month Year
+//    #
+//    2272060800      10      # 1 Jan 1972
+//    2287785600      11      # 1 Jul 1972
+//    2303683200      12      # 1 Jan 1973
+//
+// Where the timestamps are expressed as a number of seconds since 1 January 1900, 00:00:00.
+inline vector<leap_second> __parse_leap_seconds_list(istream&& __input) {
   // The file stores dates since 1 January 1900, 00:00:00, we want
   // seconds since 1 January 1970.
   constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1};
@@ -664,11 +764,21 @@ static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&&
   // The database should contain the number of seconds inserted by a leap
   // second (1 or -1). So the difference between the two elements is stored.
   // std::ranges::views::adjacent has not been implemented yet.
+  vector<leap_second> __result;
   (void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) {
-    __leap_seconds.emplace_back(
-        std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
+    __result.emplace_back(std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
     return false;
   });
+  return __result;
+}
+
+// Parse leap seconds from the appropriate location based on the platform.
+static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, filesystem::path const& __tzdb_directory) {
+#if defined(__APPLE__)
+  __leap_seconds.append_range(chrono::__parse_leap_seconds_binary(ifstream{__tzdb_directory / "leapseconds"}));
+#else
+  __leap_seconds.append_range(chrono::__parse_leap_seconds_list(ifstream{__tzdb_directory / "leap-seconds.list"}));
+#endif
 }
 
 void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
@@ -680,13 +790,7 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
   ranges::sort(__tzdb.zones);
   ranges::sort(__tzdb.links);
   ranges::sort(__rules, {}, [](const auto& p) { return p.first; });
-
-  // There are two files with the leap second information
-  // - leapseconds as specified by zic
-  // - leap-seconds.list the source data
-  // The latter is much easier to parse, it seems Howard shares that
-  // opinion.
-  chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leap-seconds.list"});
+  chrono::__parse_leap_seconds(__tzdb.leap_seconds, __root);
 }
 
 #ifdef _WIN32

>From ecef59431f66eb9844b590a67fdb4e0e9c3b4a24 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 8 Jan 2025 10:01:57 -0500
Subject: [PATCH 2/3] [WIP] Fix some tests for binary leap second parsing

---
 libcxx/src/experimental/tzdb.cpp              |  2 +-
 .../time.zone.db/leap_seconds.pass.cpp        | 46 +++++++++++++++----
 .../time.zone/time.zone.db/rules.pass.cpp     | 15 ++++--
 .../time.zone/time.zone.db/version.pass.cpp   |  5 +-
 4 files changed, 52 insertions(+), 16 deletions(-)

diff --git a/libcxx/src/experimental/tzdb.cpp b/libcxx/src/experimental/tzdb.cpp
index acc95960ade75f..509b17e2ea15e4 100644
--- a/libcxx/src/experimental/tzdb.cpp
+++ b/libcxx/src/experimental/tzdb.cpp
@@ -100,7 +100,7 @@ static void __matches(istream& __input, char __expected) {
   char __c = __input.get();
   if (std::tolower(__c) != __expected)
     std::__throw_runtime_error(
-        (string("corrupt tzdb: expected character '") + __expected + "', got '" + __c + "'").c_str());
+        (string("corrupt tzdb: expected character '") + __expected + "', got '" + __c + "' instead").c_str());
 }
 
 static void __matches(istream& __input, string_view __expected) {
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
index d7ae21926b4b26..e05279bfca4f6f 100644
--- a/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
@@ -28,10 +28,20 @@
 #include "filesystem_test_helper.h"
 #include "test_tzdb.h"
 
+#if defined(__APPLE__)
+#  define TEST_USE_BINARY_LEAP_SECONDS
+#else
+#  define TEST_USE_LIST_LEAP_SECONDS
+#endif
+
 scoped_test_env env;
 [[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo");
 const std::filesystem::path tzdata               = env.create_file("zoneinfo/tzdata.zi");
-const std::filesystem::path leap_seconds         = env.create_file("zoneinfo/leap-seconds.list");
+#ifdef TEST_USE_BINARY_LEAP_SECONDS
+const std::filesystem::path leap_seconds = env.create_file("zoneinfo/leapseconds");
+#else
+const std::filesystem::path leap_seconds = env.create_file("zoneinfo/leap-seconds.list");
+#endif
 
 std::string_view std::chrono::__libcpp_tzdb_directory() {
   static std::string result = dir.string();
@@ -65,21 +75,23 @@ static void test_exception(std::string_view input, [[maybe_unused]] std::string_
 }
 
 static void test_invalid() {
+#ifdef TEST_USE_BINARY_LEAP_SECONDS
+  test_exception("0", "corrupt tzdb: expected character 'l' from string 'leap', got '0' instead");
+  test_exception("Leap  x", "corrupt tzdb: expected a digit");
+  test_exception("Leap  1970  J", "corrupt tzdb month: invalid name");
+  test_exception("Leap  1970  Jan   1   23:59:60    x", "corrupt tzdb: invalid leap second sign x");
+#else
   test_exception("0", "corrupt tzdb: expected a non-zero digit");
-
   test_exception("1", "corrupt tzdb: expected whitespace");
-
   test_exception("1 ", "corrupt tzdb: expected a non-zero digit");
-
   test_exception("5764607523034234880 2", "corrupt tzdb: integral too large");
+#endif
 }
 
 static void test_leap_seconds() {
   using namespace std::chrono;
 
-  // Test whether loading also sorts the entries in the proper order.
-  const tzdb& result = parse(
-      R"(
+  std::string list_format = R"(
 2303683200  12  # 1 Jan 1973
 2287785600  11  # 1 Jul 1972
 2272060800  10  # 1 Jan 1972
@@ -91,7 +103,25 @@ static void test_leap_seconds() {
 
 # largest accepted value by the parser
 5764607523034234879 12
-)");
+)";
+
+  std::string binary_format = R"(
+Leap  1973  Jan   1   23:59:60    +   S
+Leap  1972  Jul   1   23:59:60    +   S
+Leap  1972  Jan   1   23:59:60    +   S
+Leap  1900  Jan   2   23:59:60    +   S # 2 Jan 1900 Dummy entry to test before 1970
+Leap  1900  Jan   2   00:00:01    +   S # 2 Jan 1900 Dummy entry to test before 1970
+
+Leap  1973  Jan   2   23:59:60    -   S # Fictional negative leap second
+Leap  32767 Jan   1   23:59:60    +   S # Largest year accepted by the parser
+)";
+
+  // Test whether loading also sorts the entries in the proper order.
+#ifdef TEST_USE_BINARY_LEAP_SECONDS
+  const tzdb& result = parse(binary_format);
+#else
+  const tzdb& result = parse(list_format);
+#endif
 
   assert(result.leap_seconds.size() == 6);
 
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp
index 7d9759320c535b..237a206b3a95bd 100644
--- a/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/rules.pass.cpp
@@ -20,9 +20,10 @@
 // ADDITIONAL_COMPILE_FLAGS: -I %{libcxx-dir}/src/experimental/include
 
 #include <chrono>
+#include <cstdio>
 #include <fstream>
-#include <string>
 #include <string_view>
+#include <string>
 #include <variant>
 
 #include "assert_macros.h"
@@ -96,7 +97,7 @@ static void test_invalid() {
   test_exception("R r 0 mix", "corrupt tzdb: expected whitespace");
   test_exception("R r 0 1", "corrupt tzdb: expected whitespace");
 
-  test_exception("R r 0 1 X", "corrupt tzdb: expected character '-'");
+  test_exception("R r 0 1 X", "corrupt tzdb: expected character '-', got 'X' instead");
 
   test_exception("R r 0 1 -", "corrupt tzdb: expected whitespace");
 
@@ -106,13 +107,17 @@ static void test_invalid() {
 
   test_exception("R r 0 1 - Ja +", "corrupt tzdb weekday: invalid name");
   test_exception("R r 0 1 - Ja 32", "corrupt tzdb day: value too large");
-  test_exception("R r 0 1 - Ja l", "corrupt tzdb: expected string 'last'");
+  test_exception(
+      "R r 0 1 - Ja l",
+      std::string{"corrupt tzdb: expected character 'a' from string 'last', got '"} + (char)EOF + "' instead");
   test_exception("R r 0 1 - Ja last", "corrupt tzdb weekday: invalid name");
   test_exception("R r 0 1 - Ja lastS", "corrupt tzdb weekday: invalid name");
   test_exception("R r 0 1 - Ja S", "corrupt tzdb weekday: invalid name");
   test_exception("R r 0 1 - Ja Su", "corrupt tzdb on: expected '>=' or '<='");
-  test_exception("R r 0 1 - Ja Su>", "corrupt tzdb: expected character '='");
-  test_exception("R r 0 1 - Ja Su<", "corrupt tzdb: expected character '='");
+  test_exception(
+      "R r 0 1 - Ja Su>", std::string{"corrupt tzdb: expected character '=', got '"} + (char)EOF + "' instead");
+  test_exception(
+      "R r 0 1 - Ja Su<", std::string{"corrupt tzdb: expected character '=', got '"} + (char)EOF + "' instead");
   test_exception("R r 0 1 - Ja Su>=+", "corrupt tzdb: expected a non-zero digit");
   test_exception("R r 0 1 - Ja Su>=0", "corrupt tzdb: expected a non-zero digit");
   test_exception("R r 0 1 - Ja Su>=32", "corrupt tzdb day: value too large");
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/version.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/version.pass.cpp
index b4f32a1b6fd785..ca3a890f1fa548 100644
--- a/libcxx/test/libcxx/time/time.zone/time.zone.db/version.pass.cpp
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/version.pass.cpp
@@ -18,9 +18,10 @@
 // This is not part of the public tzdb interface.
 
 #include <chrono>
+#include <cstdio>
 #include <fstream>
-#include <string>
 #include <string_view>
+#include <string>
 
 #include "assert_macros.h"
 #include "concat_macros.h"
@@ -60,7 +61,7 @@ static void test_exception(std::string_view input, [[maybe_unused]] std::string_
 }
 
 int main(int, const char**) {
-  test_exception("", "corrupt tzdb: expected character '#'");
+  test_exception("", std::string{"corrupt tzdb: expected character '#', got '"} + (char)EOF + "' instead");
   test_exception("#version", "corrupt tzdb: expected whitespace");
   test("#version     \t                      ABCD", "ABCD");
   test("#Version     \t                      ABCD", "ABCD");

>From 957fca1703af8a82e6143ce161ebe8bdec80211c Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 8 Jan 2025 10:17:06 -0500
Subject: [PATCH 3/3] WIP: Try using the binary tzdb unconditionally on all
 platforms

---
 libcxx/src/experimental/tzdb.cpp              | 86 ++-----------------
 .../time.zone.db/leap_seconds.pass.cpp        | 45 +---------
 2 files changed, 10 insertions(+), 121 deletions(-)

diff --git a/libcxx/src/experimental/tzdb.cpp b/libcxx/src/experimental/tzdb.cpp
index 509b17e2ea15e4..f9c3684f55bcb9 100644
--- a/libcxx/src/experimental/tzdb.cpp
+++ b/libcxx/src/experimental/tzdb.cpp
@@ -633,7 +633,8 @@ static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istr
 }
 
 // This function parses the leap-seconds "binary file" compiled from the .list file
-// by the zic compiler. That format is what's provided on some platforms like Darwin.
+// by the zic compiler. That format is widely available as it comes by default with
+// the IANA Time Zone Database.
 //
 // The format looks like:
 //
@@ -642,8 +643,7 @@ static void __parse_tzdata(tzdb& __db, __tz::__rules_storage_type& __rules, istr
 //    Leap    1972    Dec     31      23:59:60        +       S
 //    Leap    1973    Dec     31      23:59:60        +       S
 //
-inline vector<leap_second> __parse_leap_seconds_binary(istream&& __input) {
-  vector<leap_second> __result;
+static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, istream&& __input) {
   [&] {
     while (true) {
       switch (__input.peek()) {
@@ -699,86 +699,12 @@ inline vector<leap_second> __parse_leap_seconds_binary(istream&& __input) {
 
       chrono::__skip_line(__input);
 
-      __result.emplace_back(std::__private_constructor_tag{}, __timestamp, __value);
+      __leap_seconds.emplace_back(std::__private_constructor_tag{}, __timestamp, __value);
     }
   }();
 
   // Ensure the leap seconds are sorted properly.
-  ranges::sort(__result, {}, &leap_second::date);
-
-  return __result;
-}
-
-// This function parses leap-seconds.list file as can be found at
-// https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list
-//
-// The format looks like
-//
-//    #NTP Time      DTAI    Day Month Year
-//    #
-//    2272060800      10      # 1 Jan 1972
-//    2287785600      11      # 1 Jul 1972
-//    2303683200      12      # 1 Jan 1973
-//
-// Where the timestamps are expressed as a number of seconds since 1 January 1900, 00:00:00.
-inline vector<leap_second> __parse_leap_seconds_list(istream&& __input) {
-  // The file stores dates since 1 January 1900, 00:00:00, we want
-  // seconds since 1 January 1970.
-  constexpr auto __offset = sys_days{1970y / January / 1} - sys_days{1900y / January / 1};
-
-  struct __entry {
-    sys_seconds __timestamp;
-    seconds __value;
-  };
-  vector<__entry> __entries;
-  [&] {
-    while (true) {
-      switch (__input.peek()) {
-      case istream::traits_type::eof():
-        return;
-
-      case ' ':
-      case '\t':
-      case '\n':
-        __input.get();
-        continue;
-
-      case '#':
-        chrono::__skip_line(__input);
-        continue;
-      }
-
-      sys_seconds __date = sys_seconds{seconds{chrono::__parse_integral(__input, false)}} - __offset;
-      chrono::__skip_mandatory_whitespace(__input);
-      seconds __value{chrono::__parse_integral(__input, false)};
-      chrono::__skip_line(__input);
-
-      __entries.emplace_back(__date, __value);
-    }
-  }();
-  // The Standard requires the leap seconds to be sorted. The file
-  // leap-seconds.list usually provides them in sorted order, but that is not
-  // guaranteed so we ensure it here.
-  ranges::sort(__entries, {}, &__entry::__timestamp);
-
-  // The database should contain the number of seconds inserted by a leap
-  // second (1 or -1). So the difference between the two elements is stored.
-  // std::ranges::views::adjacent has not been implemented yet.
-  vector<leap_second> __result;
-  (void)ranges::adjacent_find(__entries, [&](const __entry& __first, const __entry& __second) {
-    __result.emplace_back(std::__private_constructor_tag{}, __second.__timestamp, __second.__value - __first.__value);
-    return false;
-  });
-  return __result;
-}
-
-// Parse leap seconds from the appropriate location based on the platform.
-static void __parse_leap_seconds(vector<leap_second>& __leap_seconds, filesystem::path const& __tzdb_directory) {
-#if defined(__APPLE__)
-  __leap_seconds.append_range(chrono::__parse_leap_seconds_binary(ifstream{__tzdb_directory / "leapseconds"}));
-#else
-  __leap_seconds.append_range(chrono::__parse_leap_seconds_list(ifstream{__tzdb_directory / "leap-seconds.list"}));
-#endif
+  ranges::sort(__leap_seconds, {}, &leap_second::date);
 }
 
 void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
@@ -790,7 +716,7 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
   ranges::sort(__tzdb.zones);
   ranges::sort(__tzdb.links);
   ranges::sort(__rules, {}, [](const auto& p) { return p.first; });
-  chrono::__parse_leap_seconds(__tzdb.leap_seconds, __root);
+  chrono::__parse_leap_seconds(__tzdb.leap_seconds, ifstream{__root / "leapseconds"});
 }
 
 #ifdef _WIN32
diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
index e05279bfca4f6f..7e8685d1f41069 100644
--- a/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
+++ b/libcxx/test/libcxx/time/time.zone/time.zone.db/leap_seconds.pass.cpp
@@ -28,20 +28,10 @@
 #include "filesystem_test_helper.h"
 #include "test_tzdb.h"
 
-#if defined(__APPLE__)
-#  define TEST_USE_BINARY_LEAP_SECONDS
-#else
-#  define TEST_USE_LIST_LEAP_SECONDS
-#endif
-
 scoped_test_env env;
 [[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo");
 const std::filesystem::path tzdata               = env.create_file("zoneinfo/tzdata.zi");
-#ifdef TEST_USE_BINARY_LEAP_SECONDS
-const std::filesystem::path leap_seconds = env.create_file("zoneinfo/leapseconds");
-#else
-const std::filesystem::path leap_seconds = env.create_file("zoneinfo/leap-seconds.list");
-#endif
+const std::filesystem::path leap_seconds         = env.create_file("zoneinfo/leapseconds");
 
 std::string_view std::chrono::__libcpp_tzdb_directory() {
   static std::string result = dir.string();
@@ -75,37 +65,17 @@ static void test_exception(std::string_view input, [[maybe_unused]] std::string_
 }
 
 static void test_invalid() {
-#ifdef TEST_USE_BINARY_LEAP_SECONDS
   test_exception("0", "corrupt tzdb: expected character 'l' from string 'leap', got '0' instead");
   test_exception("Leap  x", "corrupt tzdb: expected a digit");
   test_exception("Leap  1970  J", "corrupt tzdb month: invalid name");
   test_exception("Leap  1970  Jan   1   23:59:60    x", "corrupt tzdb: invalid leap second sign x");
-#else
-  test_exception("0", "corrupt tzdb: expected a non-zero digit");
-  test_exception("1", "corrupt tzdb: expected whitespace");
-  test_exception("1 ", "corrupt tzdb: expected a non-zero digit");
-  test_exception("5764607523034234880 2", "corrupt tzdb: integral too large");
-#endif
 }
 
 static void test_leap_seconds() {
   using namespace std::chrono;
 
-  std::string list_format = R"(
-2303683200  12  # 1 Jan 1973
-2287785600  11  # 1 Jul 1972
-2272060800  10  # 1 Jan 1972
-86400        9  # 2 Jan 1900 Dummy entry to test before 1970
-1            8  # 2 Jan 1900 Dummy entry to test before 1970
-
-# Fictional negative leap second
-2303769600  11  # 2 Jan 1973
-
-# largest accepted value by the parser
-5764607523034234879 12
-)";
-
-  std::string binary_format = R"(
+  // Test whether loading also sorts the entries in the proper order.
+  const tzdb& result = parse(R"(
 Leap  1973  Jan   1   23:59:60    +   S
 Leap  1972  Jul   1   23:59:60    +   S
 Leap  1972  Jan   1   23:59:60    +   S
@@ -114,14 +84,7 @@ Leap  1900  Jan   2   00:00:01    +   S # 2 Jan 1900 Dummy entry to test before
 
 Leap  1973  Jan   2   23:59:60    -   S # Fictional negative leap second
 Leap  32767 Jan   1   23:59:60    +   S # Largest year accepted by the parser
-)";
-
-  // Test whether loading also sorts the entries in the proper order.
-#ifdef TEST_USE_BINARY_LEAP_SECONDS
-  const tzdb& result = parse(binary_format);
-#else
-  const tzdb& result = parse(list_format);
-#endif
+)");
 
   assert(result.leap_seconds.size() == 6);
 



More information about the libcxx-commits mailing list