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