[llvm-branch-commits] [libcxx] [libc++][chrono] Adds the sys_info class. (PR #85619)

Louis Dionne via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Mar 26 11:00:44 PDT 2024


================
@@ -0,0 +1,1374 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 time_zone;
+
+// template <class _Duration>
+//   sys_info get_info(const sys_time<_Duration>& time) const;
+
+// This test uses the system provided database. This makes the test portable,
+// but may cause failures when the database information changes. Historic data
+// may change if new facts are uncovered, future data may change when regions
+// change their time zone or daylight saving time. Most tests will not look in
+// the future to attempt to avoid issues. All tests list the data on which they
+// are based, this makes debugging easier upon failure; including to see whether
+// the provided data has not been changed
+//
+//
+// The data in the tests can be validated by using the zdump tool. For
+// example
+//   zdump -v Asia/Hong_Kong
+// show all transistions in the Hong Kong time zone. Or
+//   zdump -c1970,1980 -v Asia/Hong_Kong
+// shows all transitions in Hong Kong between 1970 and 1980.
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <format>
+
+#include "test_macros.h"
+#include "assert_macros.h"
+#include "concat_macros.h"
+
+/***** ***** HELPERS ***** *****/
+
+[[nodiscard]] static std::chrono::sys_seconds to_sys_seconds(
+    std::chrono::year year,
+    std::chrono::month month,
+    std::chrono::day day,
+    std::chrono::hours h   = std::chrono::hours(0),
+    std::chrono::minutes m = std::chrono::minutes{0},
+    std::chrono::seconds s = std::chrono::seconds{0}) {
+  std::chrono::year_month_day result{year, month, day};
+
+  return std::chrono::time_point_cast<std::chrono::seconds>(static_cast<std::chrono::sys_days>(result)) + h + m + s;
+}
+
+static void assert_equal(const std::chrono::sys_info& lhs, const std::chrono::sys_info& rhs) {
+  TEST_REQUIRE(lhs.begin == rhs.begin,
+               TEST_WRITE_CONCATENATED("\nBegin:\nExpected output ", lhs.begin, "\nActual output   ", rhs.begin, '\n'));
+  TEST_REQUIRE(lhs.end == rhs.end,
+               TEST_WRITE_CONCATENATED("\nEnd:\nExpected output ", lhs.end, "\nActual output   ", rhs.end, '\n'));
+  TEST_REQUIRE(
+      lhs.offset == rhs.offset,
+      TEST_WRITE_CONCATENATED("\nOffset:\nExpected output ", lhs.offset, "\nActual output   ", rhs.offset, '\n'));
+  TEST_REQUIRE(lhs.save == rhs.save,
+               TEST_WRITE_CONCATENATED("\nSave:\nExpected output ", lhs.save, "\nActual output   ", rhs.save, '\n'));
+  TEST_REQUIRE(
+      lhs.abbrev == rhs.abbrev,
+      TEST_WRITE_CONCATENATED("\nAbbrev:\nExpected output ", lhs.abbrev, "\nActual output   ", rhs.abbrev, '\n'));
+}
+
+static void assert_equal(std::string_view expected, const std::chrono::sys_info& value) {
+  // Note the output of operator<< is implementation defined, use this
+  // format to keep the test portable.
+  std::string result = std::format(
+      "[{}, {}) {:%T} {:%Q%q} {}",
+      value.begin,
+      value.end,
+      std::chrono::hh_mm_ss{value.offset},
+      value.save,
+      value.abbrev);
+
+  TEST_REQUIRE(expected == result,
+               TEST_WRITE_CONCATENATED("\nExpected output ", expected, "\nActual output   ", result, '\n'));
+}
+
+static void
+assert_range(std::string_view expected, const std::chrono::sys_info& begin, const std::chrono::sys_info& end) {
+  assert_equal(expected, begin);
+  assert_equal(expected, end);
+}
+
+static void assert_cycle(
+    std::string_view expected_1,
+    const std::chrono::sys_info& begin_1,
+    const std::chrono::sys_info& end_1,
+    std::string_view expected_2,
+    const std::chrono::sys_info& begin_2,
+    const std::chrono::sys_info& end_2
+
+) {
+  assert_range(expected_1, begin_1, end_1);
+  assert_range(expected_2, begin_2, end_2);
+}
+
+/***** ***** TESTS ***** *****/
+
+static void test_gmt() {
+  // Simple zone always valid, no rule entries, lookup using a link.
+  // L Etc/GMT GMT
+  // Z Etc/GMT 0 - GMT
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT");
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(),
+          std::chrono::sys_seconds::max(),
+          std::chrono::seconds(0),
+          std::chrono::minutes(0),
+          "GMT"),
+      tz->get_info(std::chrono::sys_seconds::min()));
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(),
+          std::chrono::sys_seconds::max(),
+          std::chrono::seconds(0),
+          std::chrono::minutes(0),
+          "GMT"),
+      tz->get_info(std::chrono::sys_seconds(std::chrono::seconds{0})));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(),
+          std::chrono::sys_seconds::max(),
+          std::chrono::seconds(0),
+          std::chrono::minutes(0),
+          "GMT"),
+      tz->get_info(std::chrono::sys_seconds::max() - std::chrono::seconds{1})); // max is not valid
+}
+
+static void test_durations() {
+  // Doesn't test a location, instead tests whether different duration
+  // specializations work.
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("GMT");
+
+  // Using the GMT zone means every call gives the same result.
+  std::chrono::sys_info expected(
+      std::chrono::sys_seconds::min(),
+      std::chrono::sys_seconds::max(),
+      std::chrono::seconds(0),
+      std::chrono::minutes(0),
+      "GMT");
+
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::nanoseconds>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::microseconds>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::milliseconds>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::seconds>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::minutes>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::minutes>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::hours>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::days>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::weeks>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::months>{}));
+  assert_equal(expected, tz->get_info(std::chrono::sys_time<std::chrono::years>{}));
+}
+
+static void test_indian_kerguelen() {
+  // One change, no rules, no dst changes.
+
+  // Z Indian/Kerguelen 0 - -00 1950
+  // 5 - +05
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Indian/Kerguelen");
+
+  std::chrono::sys_seconds transition =
+      to_sys_seconds(std::chrono::year(1950), std::chrono::January, std::chrono::day(1));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(), //
+          transition,                      //
+          std::chrono::seconds(0),         //
+          std::chrono::minutes(0),         //
+          "-00"),                          //
+      tz->get_info(std::chrono::sys_seconds::min()));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(), //
+          transition,                      //
+          std::chrono::seconds(0),         //
+          std::chrono::minutes(0),         //
+          "-00"),                          //
+      tz->get_info(transition - std::chrono::seconds{1}));
+
+  assert_equal(
+      std::chrono::sys_info(
+          transition,                      //
+          std::chrono::sys_seconds::max(), //
+          std::chrono::hours(5),           //
+          std::chrono::minutes(0),         //
+          "+05"),                          //
+      tz->get_info(transition));
+}
+
+static void test_antarctica_syowa() {
+  // One change, no rules, no dst changes
+  // This change uses an ON field with a day number
+  //
+  // There don't seem to be rule-less zones that use last day or a
+  // contrained day
+
+  // Z Antarctica/Syowa 0 - -00 1957 Ja 29
+  // 3 - +03
+
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Antarctica/Syowa");
+
+  std::chrono::sys_seconds transition =
+      to_sys_seconds(std::chrono::year(1957), std::chrono::January, std::chrono::day(29));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(), //
+          transition,                      //
+          std::chrono::seconds(0),         //
+          std::chrono::minutes(0),         //
+          "-00"),                          //
+      tz->get_info(std::chrono::sys_seconds::min()));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(), //
+          transition,                      //
+          std::chrono::seconds(0),         //
+          std::chrono::minutes(0),         //
+          "-00"),                          //
+      tz->get_info(transition - std::chrono::seconds(1)));
+
+  assert_equal(
+      std::chrono::sys_info(
+          transition,                      //
+          std::chrono::sys_seconds::max(), //
+          std::chrono::hours(3),           //
+          std::chrono::minutes(0),         //
+          "+03"),                          //
+      tz->get_info(transition));
+}
+
+static void test_asia_hong_kong() {
+  // A more typical entry, first some hard-coded entires and then at the
+  // end a rules based entry. This rule is valid for its entire period
+  //
+  // Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 0:36:42
+  // 8 - HKT 1941 Jun 15 3
+  // 8 1 HKST 1941 O 1 4
+  // 8 0:30 HKWT 1941 D 25
+  // 9 - JST 1945 N 18 2
+  // 8 HK HK%sT
+  //
+  // R HK 1946 o - Ap 21 0 1 S
+  // R HK 1946 o - D 1 3:30s 0 -
+  // R HK 1947 o - Ap 13 3:30s 1 S
+  // R HK 1947 o - N 30 3:30s 0 -
+  // R HK 1948 o - May 2 3:30s 1 S
+  // R HK 1948 1952 - O Su>=28 3:30s 0 -
+  // R HK 1949 1953 - Ap Su>=1 3:30 1 S
+  // R HK 1953 1964 - O Su>=31 3:30 0 -
+  // R HK 1954 1964 - Mar Su>=18 3:30 1 S
+  // R HK 1965 1976 - Ap Su>=16 3:30 1 S
+  // R HK 1965 1976 - O Su>=16 3:30 0 -
+  // R HK 1973 o - D 30 3:30 1 S
+  // R HK 1979 o - May 13 3:30 1 S
+  // R HK 1979 o - O 21 3:30 0 -
+
+  using namespace std::literals::chrono_literals;
+  const std::chrono::time_zone* tz = std::chrono::locate_zone("Asia/Hong_Kong");
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(),
+          to_sys_seconds(1904y, std::chrono::October, 29d, 17h), // 7:36:42 - LMT 1904 O 30 0:36:42
+          7h + 36min + 42s,
+          0min,
+          "LMT"),
+      tz->get_info(std::chrono::sys_seconds::min()));
+
+  assert_equal(
+      std::chrono::sys_info(
+          std::chrono::sys_seconds::min(),
+          to_sys_seconds(1904y, std::chrono::October, 29d, 17h), // 7:36:42 - LMT 1904 O 30 0:36:42
+          7h + 36min + 42s,
+          0min,
+          "LMT"),
+      tz->get_info(to_sys_seconds(1904y, std::chrono::October, 29d, 16h, 59min, 59s)));
+
+  assert_range("[1904-10-29 17:00:00, 1941-06-14 19:00:00) 08:00:00 0min HKT", // 8 - HKT 1941 Jun 15 3
+               tz->get_info(to_sys_seconds(1904y, std::chrono::October, 29d, 17h)),
+               tz->get_info(to_sys_seconds(1941y, std::chrono::June, 14d, 18h, 59min, 59s)));
+
+  assert_range("[1941-06-14 19:00:00, 1941-09-30 19:00:00) 09:00:00 60min HKST", // 8 1 HKST 1941 O 1 4
+               tz->get_info(to_sys_seconds(1941y, std::chrono::June, 14d, 19h)),
+               tz->get_info(to_sys_seconds(1941y, std::chrono::September, 30d, 18h, 59min, 59s)));
+
+  assert_range("[1941-09-30 19:00:00, 1941-12-24 15:30:00) 08:30:00 30min HKWT", // 8 0:30 HKWT 1941 D 25
+               tz->get_info(to_sys_seconds(1941y, std::chrono::September, 30d, 19h)),
+               tz->get_info(to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 29min, 59s)));
+
+  assert_range("[1941-12-24 15:30:00, 1945-11-17 17:00:00) 09:00:00 0min JST", // 9 - JST 1945 N 18 2
+               tz->get_info(to_sys_seconds(1941y, std::chrono::December, 24d, 15h, 30min)),
+               tz->get_info(to_sys_seconds(1945y, std::chrono::November, 17d, 16h, 59min, 59s)));
+
+  assert_range("[1945-11-17 17:00:00, 1946-04-20 16:00:00) 08:00:00 0min HKT", // 8 HK%sT
+               tz->get_info(to_sys_seconds(1945y, std::chrono::November, 17d, 17h)),
+               tz->get_info(to_sys_seconds(1946y, std::chrono::April, 20d, 15h, 59min, 59s)));
+
+  assert_cycle( // 8 HK%sT
+      "[1946-04-20 16:00:00, 1946-11-30 19:30:00) 09:00:00 60min HKST",
+      tz->get_info(to_sys_seconds(1946y, std::chrono::April, 20d, 16h)),                // 1946 o Ap 21 0 1 S
+      tz->get_info(to_sys_seconds(1946y, std::chrono::November, 30d, 19h, 29min, 59s)), // 1946 o D 1 3:30s 0 -
+      "[1946-11-30 19:30:00, 1947-04-12 19:30:00) 08:00:00 0min HKT",
+      tz->get_info(to_sys_seconds(1946y, std::chrono::November, 30d, 19h, 30min)),    // 1946 o D 1 3:30s 0 -
+      tz->get_info(to_sys_seconds(1947y, std::chrono::April, 12d, 19h, 29min, 59s))); // 1947 o Ap 13 3:30s 1 S
+
+  assert_cycle( // 8 HK%sT
+      "[1947-04-12 19:30:00, 1947-11-29 19:30:00) 09:00:00 60min HKST",
+      tz->get_info(to_sys_seconds(1947y, std::chrono::April, 12d, 19h, 30min)),         // 1947 o Ap 13 3:30s 1 S
+      tz->get_info(to_sys_seconds(1947y, std::chrono::November, 29d, 19h, 29min, 59s)), // 1947 o N 30 3:30s 0 -
+      "[1947-11-29 19:30:00, 1948-05-01 19:30:00) 08:00:00 0min HKT",
+      tz->get_info(to_sys_seconds(1947y, std::chrono::November, 29d, 19h, 30min)), // 1947 o N 30 3:30s 0 -
+      tz->get_info(to_sys_seconds(1948y, std::chrono::May, 1d, 19h, 29min, 59s))); // 1948 o May 2 3:30s 1 S
+
+  assert_cycle( // 8 HK%sT
+      "[1948-05-01 19:30:00, 1948-10-30 19:30:00) 09:00:00 60min HKST",
+      tz->get_info(to_sys_seconds(1948y, std::chrono::May, 1d, 19h, 30min)),           // 1948 o May 2 3:30s 1 S
+      tz->get_info(to_sys_seconds(1948y, std::chrono::October, 30d, 19h, 29min, 59s)), // 1948 1952 O Su>=28 3:30s 0 -
+      "[1948-10-30 19:30:00, 1949-04-02 19:30:00) 08:00:00 0min HKT",
+      tz->get_info(to_sys_seconds(1948y, std::chrono::October, 30d, 19h, 30min)),    // 1948 1952 O Su>=28 3:30s 0 -
+      tz->get_info(to_sys_seconds(1949y, std::chrono::April, 2d, 19h, 29min, 59s))); // 1949 1953 Ap Su>=1 3:30 1 S
+
+  assert_cycle( // 8 HK%sT
+      "[1949-04-02 19:30:00, 1949-10-29 19:30:00) 09:00:00 60min HKST",
+      tz->get_info(to_sys_seconds(1949y, std::chrono::April, 2d, 19h, 30min)),         // 1949 1953 Ap Su>=1 3:30 1 S
+      tz->get_info(to_sys_seconds(1949y, std::chrono::October, 29d, 19h, 29min, 59s)), // 1948 1952 O Su>=28 3:30s 0
+      "[1949-10-29 19:30:00, 1950-04-01 19:30:00) 08:00:00 0min HKT",
+      tz->get_info(to_sys_seconds(1949y, std::chrono::October, 29d, 19h, 30min)),    // 1948 1952 O Su>=28 3:30s 0
+      tz->get_info(to_sys_seconds(1950y, std::chrono::April, 1d, 19h, 29min, 59s))); // 1949 1953 Ap Su>=1 3:30 1 S
+
+  assert_range(
----------------
ldionne wrote:

Wow. We discussed how you came up with these tests and I understand it now. I will not go through all the test cases in detail though since that would probably not add a lot of value.

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


More information about the llvm-branch-commits mailing list