[llvm-branch-commits] [libcxx] [libc++][chrono] implements UTC clock. (PR #90393)

Matt Stephanson via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Apr 30 09:15:53 PDT 2024


================
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-filesystem, no-localization, no-tzdb
+
+// XFAIL: libcpp-has-no-incomplete-tzdb
+// XFAIL: availability-tzdb-missing
+
+// <chrono>
+//
+// class utc_clock;
+
+// template<class Duration>
+// leap_second_info get_leap_second_info(const utc_time<Duration>& ut);
+
+#include <chrono>
+#include <cassert>
+#include <fstream>
+#include <string>
+#include <string_view>
+
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "filesystem_test_helper.h"
+#include "test_tzdb.h"
+
+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");
+
+std::string_view std::chrono::__libcpp_tzdb_directory() {
+  static std::string result = dir.string();
+  return result;
+}
+
+static void write(std::string_view input) {
+  static int version = 0;
+
+  std::ofstream f{tzdata};
+  f << "# version " << version++ << '\n';
+  std::ofstream{leap_seconds}.write(input.data(), input.size());
+}
+
+template <class Duration>
+static void test_leap_second_info(
+    std::chrono::time_point<std::chrono::utc_clock, Duration> time, bool is_leap_second, std::chrono::seconds elapsed) {
+  std::chrono::leap_second_info result = std::chrono::get_leap_second_info(time);
+  TEST_REQUIRE(
+      result.is_leap_second == is_leap_second && result.elapsed == elapsed,
+      TEST_WRITE_CONCATENATED(
+          "\nExpected output [",
+          is_leap_second,
+          ", ",
+          elapsed,
+          "]\nActual output   [",
+          result.is_leap_second,
+          ", ",
+          result.elapsed,
+          "]\n"));
+}
+
+// Note at the time of writing all leap seconds are positive. This test uses
+// fake data to test the behaviour of negative leap seconds.
+int main(int, const char**) {
+  using namespace std::literals::chrono_literals;
+
+  // Use small values for simplicity. The dates are seconds since 1.1.1900.
+  write(
+      R"(
+1 10
+60 11
+120 12
+180 11
+240 12
+300 13
+360 12
+)");
+
+  // Transitions from the start of UTC.
+  auto test_transition = [](std::chrono::utc_seconds time, std::chrono::seconds elapsed, bool positive) {
+    if (positive) {
+      // Every transition has the following tests
+      // - 1ns before the start of the transition is_leap_second -> false, elapsed -> elapsed
+      // -         at the start of the transition is_leap_second -> true,  elapsed -> elapsed + 1
+      // - 1ns after  the start of the transition is_leap_second -> true,  elapsed -> elapsed + 1
+      // - 1ns before the end   of the transition is_leap_second -> true,  elapsed -> elapsed + 1
+      // -         at the end   of the transition is_leap_second -> false, elapsed -> elapsed + 1
+
+      test_leap_second_info(time - 1ns, false, elapsed);
+      test_leap_second_info(time, true, elapsed + 1s);
+      test_leap_second_info(time + 1ns, true, elapsed + 1s);
+      test_leap_second_info(time + 1s - 1ns, true, elapsed + 1s);
+      test_leap_second_info(time + 1s, false, elapsed + 1s);
+    } else {
+      // Every transition has the following tests
+      // - 1ns before the transition is_leap_second -> false, elapsed -> elapsed
+      // -         at the transition is_leap_second -> false  elapsed -> elapsed - 1
+      // - 1ns after  the transition is_leap_second -> false, elapsed -> elapsed - 1
+      test_leap_second_info(time - 1ns, false, elapsed);
+      test_leap_second_info(time, false, elapsed - 1s);
+      test_leap_second_info(time + 1ns, false, elapsed - 1s);
+    }
+  };
+
+  std::chrono::utc_seconds epoch{std::chrono::sys_days{std::chrono::January / 1 / 1900}.time_since_epoch()};
+  test_leap_second_info(epoch, false, 0s);
+
+  // The UTC times are:
+  //   epoch + transition time in the database + leap seconds before the transition.
+  test_transition(epoch + 60s + 0s, 0s, true);
+  test_transition(epoch + 120s + 1s, 1s, true);
+  test_transition(epoch + 180s + 2s, 2s, false);
----------------
MattStephanson wrote:

I don't think this test is quite right. But since we don't have any historical practice with negative leap seconds, my reasoning is based on an assumption: the IANA db contains the earliest time after a leap second is fully inserted. I'm basing this on the fact that positive leap seconds are inserted at 11:59:60 PM on 30 June/31 December, but the db contains 12:00:00 AM on 1 July/1 January.

For a positive leap second, the `utc_time` when insertion begins is this `sys_time` plus the accumulated offset, because the insertion begins at the time that would otherwise be 12:00:00 AM on the following day. The test is correct for those cases. But negative leap seconds are different. Insertion happens at the time that would otherwise be 11:59:59 PM, i.e., one second earlier, so I think you have to subtract `1s` to get the correct transition time for negative leap seconds.

https://github.com/llvm/llvm-project/pull/90393


More information about the llvm-branch-commits mailing list