11fda1776SMark de Wever //===----------------------------------------------------------------------===//
21fda1776SMark de Wever //
31fda1776SMark de Wever // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
41fda1776SMark de Wever // See https://llvm.org/LICENSE.txt for license information.
51fda1776SMark de Wever // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
61fda1776SMark de Wever //
71fda1776SMark de Wever //===----------------------------------------------------------------------===//
81fda1776SMark de Wever 
91fda1776SMark de Wever // UNSUPPORTED: c++03, c++11, c++14, c++17
101fda1776SMark de Wever // UNSUPPORTED: no-filesystem, no-localization, no-tzdb, has-no-zdump
11148417bfSLouis Dionne // REQUIRES: long_tests
121fda1776SMark de Wever 
13a4422a51SMark de Wever // XFAIL: libcpp-has-no-experimental-tzdb
141fda1776SMark de Wever // XFAIL: availability-tzdb-missing
151fda1776SMark de Wever 
161fda1776SMark de Wever #include <chrono>
171fda1776SMark de Wever #include <format>
181fda1776SMark de Wever #include <fstream>
191fda1776SMark de Wever #include <cassert>
201fda1776SMark de Wever 
211fda1776SMark de Wever #include "filesystem_test_helper.h"
221fda1776SMark de Wever #include "assert_macros.h"
231fda1776SMark de Wever #include "concat_macros.h"
241fda1776SMark de Wever 
251fda1776SMark de Wever // The year range to validate. The dates used in practice are expected to be
261fda1776SMark de Wever // inside the tested range.
271fda1776SMark de Wever constexpr std::chrono::year first{1800};
28*cdd608b8SDavid Spickett constexpr std::chrono::year last{sizeof(time_t) == 8 ? 2100 : 2037};
291fda1776SMark de Wever 
301fda1776SMark de Wever // A custom sys_info class that also stores the name of the time zone.
311fda1776SMark de Wever // Its formatter matches the output of zdump.
321fda1776SMark de Wever struct sys_info : public std::chrono::sys_info {
331fda1776SMark de Wever   sys_info(std::string_view name_, std::chrono::sys_info info) : std::chrono::sys_info{info}, name{name_} {}
341fda1776SMark de Wever 
351fda1776SMark de Wever   std::string name;
361fda1776SMark de Wever };
371fda1776SMark de Wever 
381fda1776SMark de Wever template <>
391fda1776SMark de Wever struct std::formatter<sys_info, char> {
401fda1776SMark de Wever   template <class ParseContext>
411fda1776SMark de Wever   constexpr typename ParseContext::iterator parse(ParseContext& ctx) {
421fda1776SMark de Wever     return ctx.begin();
431fda1776SMark de Wever   }
441fda1776SMark de Wever 
451fda1776SMark de Wever   template <class FormatContext>
461fda1776SMark de Wever   typename FormatContext::iterator format(const sys_info& info, FormatContext& ctx) const {
471fda1776SMark de Wever     using namespace std::literals::chrono_literals;
481fda1776SMark de Wever 
491fda1776SMark de Wever     // Every "sys_info" entry of zdump consists of 2 lines.
501fda1776SMark de Wever     // - 1 for first second of the range
511fda1776SMark de Wever     // - 1 for last second of the range
521fda1776SMark de Wever     // For example:
531fda1776SMark de Wever     // Africa/Casablanca  Sun Mar 25 02:00:00 2018 UT = Sun Mar 25 03:00:00 2018 +01 isdst=1 gmtoff=3600
541fda1776SMark de Wever     // Africa/Casablanca  Sun May 13 01:59:59 2018 UT = Sun May 13 02:59:59 2018 +01 isdst=1 gmtoff=3600
551fda1776SMark de Wever 
561fda1776SMark de Wever     if (info.begin != std::chrono::sys_seconds::min())
571fda1776SMark de Wever       ctx.advance_to(std::format_to(
581fda1776SMark de Wever           ctx.out(),
591fda1776SMark de Wever           "{}  {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n",
601fda1776SMark de Wever           info.name,
611fda1776SMark de Wever           info.begin,
621fda1776SMark de Wever           info.begin + info.offset,
631fda1776SMark de Wever           info.abbrev,
641fda1776SMark de Wever           info.save != 0s,
651fda1776SMark de Wever           info.offset));
661fda1776SMark de Wever 
671fda1776SMark de Wever     if (info.end != std::chrono::sys_seconds::max())
681fda1776SMark de Wever       ctx.advance_to(std::format_to(
691fda1776SMark de Wever           ctx.out(),
701fda1776SMark de Wever           "{}  {:%a %b %e %H:%M:%S %Y} UT = {:%a %b %e %H:%M:%S %Y} {} isdst={:d} gmtoff={:%Q}\n",
711fda1776SMark de Wever           info.name,
721fda1776SMark de Wever           info.end - 1s,
731fda1776SMark de Wever           info.end - 1s + info.offset,
741fda1776SMark de Wever           info.abbrev,
751fda1776SMark de Wever           info.save != 0s,
761fda1776SMark de Wever           info.offset));
771fda1776SMark de Wever 
781fda1776SMark de Wever     return ctx.out();
791fda1776SMark de Wever   }
801fda1776SMark de Wever };
811fda1776SMark de Wever 
821fda1776SMark de Wever void process(std::ostream& stream, const std::chrono::time_zone& zone) {
831fda1776SMark de Wever   using namespace std::literals::chrono_literals;
841fda1776SMark de Wever 
851fda1776SMark de Wever   constexpr auto begin = std::chrono::time_point_cast<std::chrono::seconds>(
861fda1776SMark de Wever       static_cast<std::chrono::sys_days>(std::chrono::year_month_day{first, std::chrono::January, 1d}));
871fda1776SMark de Wever   constexpr auto end = std::chrono::time_point_cast<std::chrono::seconds>(
881fda1776SMark de Wever       static_cast<std::chrono::sys_days>(std::chrono::year_month_day{last, std::chrono::January, 1d}));
891fda1776SMark de Wever 
901fda1776SMark de Wever   std::chrono::sys_seconds s = begin;
911fda1776SMark de Wever   do {
921fda1776SMark de Wever     sys_info info{zone.name(), zone.get_info(s)};
931fda1776SMark de Wever 
941fda1776SMark de Wever     if (info.end >= end)
951fda1776SMark de Wever       info.end = std::chrono::sys_seconds::max();
961fda1776SMark de Wever 
971fda1776SMark de Wever     stream << std::format("{}", info);
981fda1776SMark de Wever     s = info.end;
991fda1776SMark de Wever   } while (s != std::chrono::sys_seconds::max());
1001fda1776SMark de Wever }
1011fda1776SMark de Wever 
1021fda1776SMark de Wever // This test compares the output of the zdump against the output based on the
1031fda1776SMark de Wever // standard library implementation. It tests all available time zones and
1041fda1776SMark de Wever // validates them. The specification of how to use the IANA database is limited
1051fda1776SMark de Wever // and the real database contains quite a number of "interesting" cases.
1061fda1776SMark de Wever int main(int, const char**) {
1071fda1776SMark de Wever   scoped_test_env env;
1081fda1776SMark de Wever   const std::string file = env.create_file("zdump.txt");
1091fda1776SMark de Wever 
1101fda1776SMark de Wever   const std::chrono::tzdb& tzdb = std::chrono::get_tzdb();
1111fda1776SMark de Wever   for (const auto& zone : tzdb.zones) {
1121fda1776SMark de Wever     std::stringstream libcxx;
1131fda1776SMark de Wever     process(libcxx, zone);
1141fda1776SMark de Wever 
1151fda1776SMark de Wever     int result = std::system(std::format("zdump -V -c{},{} {} > {}", first, last, zone.name(), file).c_str());
1161fda1776SMark de Wever     assert(result == 0);
1171fda1776SMark de Wever 
1181fda1776SMark de Wever     std::stringstream zdump;
1191fda1776SMark de Wever     zdump << std::ifstream(file).rdbuf();
1201fda1776SMark de Wever 
1211fda1776SMark de Wever     TEST_REQUIRE(
1221fda1776SMark de Wever         libcxx.str() == zdump.str(),
1231fda1776SMark de Wever         TEST_WRITE_CONCATENATED("\nTZ=", zone.name(), "\nlibc++\n", libcxx.str(), "|\n\nzdump\n", zdump.str(), "|"));
1241fda1776SMark de Wever   }
1251fda1776SMark de Wever 
1261fda1776SMark de Wever   return 0;
1271fda1776SMark de Wever }
128