1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03, c++11, c++14, c++17
10 // UNSUPPORTED: no-filesystem, no-localization, no-tzdb, has-no-zdump
11 // REQUIRES: long_tests
12 
13 // XFAIL: libcpp-has-no-experimental-tzdb
14 // XFAIL: availability-tzdb-missing
15 
16 #include <chrono>
17 #include <format>
18 #include <fstream>
19 #include <cassert>
20 
21 #include "filesystem_test_helper.h"
22 #include "assert_macros.h"
23 #include "concat_macros.h"
24 
25 // The year range to validate. The dates used in practice are expected to be
26 // inside the tested range.
27 constexpr std::chrono::year first{1800};
28 constexpr std::chrono::year last{sizeof(time_t) == 8 ? 2100 : 2037};
29 
30 // A custom sys_info class that also stores the name of the time zone.
31 // Its formatter matches the output of zdump.
32 struct sys_info : public std::chrono::sys_info {
33   sys_info(std::string_view name_, std::chrono::sys_info info) : std::chrono::sys_info{info}, name{name_} {}
34 
35   std::string name;
36 };
37 
38 template <>
39 struct std::formatter<sys_info, char> {
40   template <class ParseContext>
41   constexpr typename ParseContext::iterator parse(ParseContext& ctx) {
42     return ctx.begin();
43   }
44 
45   template <class FormatContext>
46   typename FormatContext::iterator format(const sys_info& info, FormatContext& ctx) const {
47     using namespace std::literals::chrono_literals;
48 
49     // Every "sys_info" entry of zdump consists of 2 lines.
50     // - 1 for first second of the range
51     // - 1 for last second of the range
52     // For example:
53     // Africa/Casablanca  Sun Mar 25 02:00:00 2018 UT = Sun Mar 25 03:00:00 2018 +01 isdst=1 gmtoff=3600
54     // Africa/Casablanca  Sun May 13 01:59:59 2018 UT = Sun May 13 02:59:59 2018 +01 isdst=1 gmtoff=3600
55 
56     if (info.begin != std::chrono::sys_seconds::min())
57       ctx.advance_to(std::format_to(
58           ctx.out(),
59           "{}  {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n",
60           info.name,
61           info.begin,
62           info.begin + info.offset,
63           info.abbrev,
64           info.save != 0s,
65           info.offset));
66 
67     if (info.end != std::chrono::sys_seconds::max())
68       ctx.advance_to(std::format_to(
69           ctx.out(),
70           "{}  {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n",
71           info.name,
72           info.end - 1s,
73           info.end - 1s + info.offset,
74           info.abbrev,
75           info.save != 0s,
76           info.offset));
77 
78     return ctx.out();
79   }
80 };
81 
82 void process(std::ostream& stream, const std::chrono::time_zone& zone) {
83   using namespace std::literals::chrono_literals;
84 
85   constexpr auto begin = std::chrono::time_point_cast<std::chrono::seconds>(
86       static_cast<std::chrono::sys_days>(std::chrono::year_month_day{first, std::chrono::January, 1d}));
87   constexpr auto end = std::chrono::time_point_cast<std::chrono::seconds>(
88       static_cast<std::chrono::sys_days>(std::chrono::year_month_day{last, std::chrono::January, 1d}));
89 
90   std::chrono::sys_seconds s = begin;
91   do {
92     sys_info info{zone.name(), zone.get_info(s)};
93 
94     if (info.end >= end)
95       info.end = std::chrono::sys_seconds::max();
96 
97     stream << std::format("{}", info);
98     s = info.end;
99   } while (s != std::chrono::sys_seconds::max());
100 }
101 
102 // This test compares the output of the zdump against the output based on the
103 // standard library implementation. It tests all available time zones and
104 // validates them. The specification of how to use the IANA database is limited
105 // and the real database contains quite a number of "interesting" cases.
106 int main(int, const char**) {
107   scoped_test_env env;
108   const std::string file = env.create_file("zdump.txt");
109 
110   const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
111   for (const auto& zone : tzdb.zones) {
112     std::stringstream libcxx;
113     process(libcxx, zone);
114 
115     int result = std::system(std::format("zdump -V -c{},{} {} > {}", first, last, zone.name(), file).c_str());
116     assert(result == 0);
117 
118     std::stringstream zdump;
119     zdump << std::ifstream(file).rdbuf();
120 
121     TEST_REQUIRE(
122         libcxx.str() == zdump.str(),
123         TEST_WRITE_CONCATENATED("\nTZ=", zone.name(), "\nlibc++\n", libcxx.str(), "|\n\nzdump\n", zdump.str(), "|"));
124   }
125 
126   return 0;
127 }
128