//===----------------------------------------------------------------------===//
//
// 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-experimental-tzdb
// XFAIL: availability-tzdb-missing
// <chrono>
// Tests the IANA database zones parsing and operations.
// This is not part of the public tzdb interface.
// The test uses private implementation headers.
// ADDITIONAL_COMPILE_FLAGS: -I %{libcxx-dir}/src/experimental/include
#include <cassert>
#include <chrono>
#include <fstream>
#include <string>
#include <string_view>
#include <variant>
#include "assert_macros.h"
#include "concat_macros.h"
#include "filesystem_test_helper.h"
#include "test_tzdb.h"
// headers in the dylib
#include "tzdb/types_private.h"
#include "tzdb/tzdb_private.h"
#include "tzdb/time_zone_private.h"
scoped_test_env env;
[[maybe_unused]] const std::filesystem::path dir = env.create_dir("zoneinfo");
const std::filesystem::path file = env.create_file("zoneinfo/tzdata.zi");
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{file};
f << "# version " << version++ << '\n';
f.write(input.data(), input.size());
}
static const std::chrono::tzdb& parse(std::string_view input) {
write(input);
return std::chrono::reload_tzdb();
}
static const std::vector<std::chrono::__tz::__continuation>& continuations(const std::chrono::time_zone& time_zone) {
return time_zone.__implementation().__continuations();
}
static void test_exception(std::string_view input, [[maybe_unused]] std::string_view what) {
write(input);
TEST_VALIDATE_EXCEPTION(
std::runtime_error,
[&]([[maybe_unused]] const std::runtime_error& e) {
TEST_LIBCPP_REQUIRE(
e.what() == what,
TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
},
TEST_IGNORE_NODISCARD std::chrono::reload_tzdb());
}
static void test_invalid() {
test_exception("Z", "corrupt tzdb: expected whitespace");
test_exception("Z ", "corrupt tzdb: expected a string");
test_exception("Z n", "corrupt tzdb: expected whitespace");
test_exception("Z n ", "corrupt tzdb: expected a digit");
test_exception("Z n x", "corrupt tzdb: expected a digit");
test_exception("Z n +", "corrupt tzdb: expected a digit");
test_exception("Z n 0", "corrupt tzdb: expected whitespace");
test_exception("Z n 0 ", "corrupt tzdb: expected a string");
test_exception("Z n 0 r", "corrupt tzdb: expected whitespace");
test_exception("Z n 0 r ", "corrupt tzdb: expected a string");
}
static void test_name() {
const std::chrono::tzdb& result = parse(
R"(
Z n 0 r f
)");
assert(result.zones.size() == 1);
assert(result.zones[0].name() == "n");
}
static void test_stdoff() {
const std::chrono::tzdb& result = parse(
R"(
# Based on the examples in the man page.
# Note the input is not expected to have fractional seconds, they are truncated.
Zo na 2 r f
Zon nb 2:00 r f
Zone nc 01:28:14 r f
zONE nd 00:19:32.10 r f
zoNe ne 12:00 r f
Z nf 15:00 r f
Z ng 24:00 r f
Z nh 260:00 r f
Z ni -2:30 r f
Z nj - r f
)");
assert(result.zones.size() == 10);
for (std::size_t i = 0; i < result.zones.size(); ++i)
assert(continuations(result.zones[0]).size() == 1);
assert(continuations(result.zones[0])[0].__stdoff == std::chrono::hours(2));
assert(continuations(result.zones[1])[0].__stdoff == std::chrono::hours(2));
assert(continuations(result.zones[2])[0].__stdoff ==
std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14));
assert(continuations(result.zones[3])[0].__stdoff == std::chrono::minutes(19) + std::chrono::seconds(32));
assert(continuations(result.zones[4])[0].__stdoff == std::chrono::hours(12));
assert(continuations(result.zones[5])[0].__stdoff == std::chrono::hours(15));
assert(continuations(result.zones[6])[0].__stdoff == std::chrono::hours(24));
assert(continuations(result.zones[7])[0].__stdoff == std::chrono::hours(260));
assert(continuations(result.zones[8])[0].__stdoff == -(std::chrono::hours(2) + std::chrono::minutes(30)));
assert(continuations(result.zones[9])[0].__stdoff == std::chrono::hours(0)); // The man page expresses it in hours
}
static void test_rules() {
const std::chrono::tzdb& result = parse(
R"(
Z na 0 - f
Z nb 0 r f
Z nc 0 2d f
Z nd 0 2:00s f
Z ne 0 0 f
Z nf 0 0:00:01 f
Z ng 0 -0:00:01 f
)");
assert(result.zones.size() == 7);
for (std::size_t i = 0; i < result.zones.size(); ++i)
assert(continuations(result.zones[0]).size() == 1);
assert(std::holds_alternative<std::monostate>(continuations(result.zones[0])[0].__rules));
assert(std::get<std::string>(continuations(result.zones[1])[0].__rules) == "r");
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[2])[0].__rules).__time ==
std::chrono::hours(2));
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[2])[0].__rules).__is_dst == true);
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[3])[0].__rules).__time ==
std::chrono::hours(2));
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[3])[0].__rules).__is_dst == false);
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[4])[0].__rules).__time ==
std::chrono::hours(0));
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[4])[0].__rules).__is_dst == false);
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[5])[0].__rules).__time ==
std::chrono::seconds(1));
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[5])[0].__rules).__is_dst == true);
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[6])[0].__rules).__time ==
-std::chrono::seconds(1));
assert(std::get<std::chrono::__tz::__save>(continuations(result.zones[6])[0].__rules).__is_dst == true);
}
static void test_format() {
const std::chrono::tzdb& result = parse(
R"(
Z n 0 r f
)");
assert(result.zones.size() == 1);
assert(continuations(result.zones[0]).size() == 1);
assert(continuations(result.zones[0])[0].__format == "f");
}
static void test_until() {
const std::chrono::tzdb& result = parse(
R"(
Z na 0 r f
Z nb 0 r f 1000
Z nc 0 r f -1000 N
Z nd 0 r f ma S 31
Z ne 0 r f 0 jA LASTw
Z nf 0 r f -42 jUN m<=1
Z ng 0 r f 42 jul Su>=12
Z nh 0 r f 42 JUl 1 2w
Z ni 0 r f 42 July 1 01:28:14u
Z nj 0 r f 42 Jul 1 -
)");
assert(result.zones.size() == 10);
for (std::size_t i = 0; i < result.zones.size(); ++i)
assert(continuations(result.zones[0]).size() == 1);
std::chrono::__tz::__constrained_weekday r;
assert(continuations(result.zones[0])[0].__year == std::chrono::year::min());
assert(continuations(result.zones[0])[0].__in == std::chrono::January);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[1])[0].__year == std::chrono::year(1000));
assert(continuations(result.zones[1])[0].__in == std::chrono::January);
assert(std::get<std::chrono::day>(continuations(result.zones[1])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[1])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[1])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[2])[0].__year == std::chrono::year(-1000));
assert(continuations(result.zones[2])[0].__in == std::chrono::November);
assert(std::get<std::chrono::day>(continuations(result.zones[2])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[2])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[2])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[3])[0].__year == std::chrono::year::max());
assert(continuations(result.zones[3])[0].__in == std::chrono::September);
assert(std::get<std::chrono::day>(continuations(result.zones[3])[0].__on) == std::chrono::day(31));
assert(continuations(result.zones[3])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[3])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[4])[0].__year == std::chrono::year(0));
assert(continuations(result.zones[4])[0].__in == std::chrono::January);
assert(std::get<std::chrono::weekday_last>(continuations(result.zones[4])[0].__on) ==
std::chrono::weekday_last{std::chrono::Wednesday});
assert(continuations(result.zones[4])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[4])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[5])[0].__year == std::chrono::year(-42));
assert(continuations(result.zones[5])[0].__in == std::chrono::June);
r = std::get<std::chrono::__tz::__constrained_weekday>(continuations(result.zones[5])[0].__on);
assert(r.__weekday == std::chrono::Monday);
assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le);
assert(r.__day == std::chrono::day(1));
assert(continuations(result.zones[5])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[5])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[6])[0].__year == std::chrono::year(42));
assert(continuations(result.zones[6])[0].__in == std::chrono::July);
r = std::get<std::chrono::__tz::__constrained_weekday>(continuations(result.zones[6])[0].__on);
assert(r.__weekday == std::chrono::Sunday);
assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge);
assert(r.__day == std::chrono::day(12));
assert(continuations(result.zones[6])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[6])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[7])[0].__year == std::chrono::year(42));
assert(continuations(result.zones[7])[0].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[7])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[7])[0].__at.__time == std::chrono::hours(2));
assert(continuations(result.zones[7])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[8])[0].__year == std::chrono::year(42));
assert(continuations(result.zones[8])[0].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[8])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[8])[0].__at.__time ==
std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14));
assert(continuations(result.zones[8])[0].__at.__clock == std::chrono::__tz::__clock::__universal);
assert(continuations(result.zones[9])[0].__year == std::chrono::year(42));
assert(continuations(result.zones[9])[0].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[9])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[9])[0].__at.__time == std::chrono::hours(0)); // The man page expresses it in hours
assert(continuations(result.zones[9])[0].__at.__clock == std::chrono::__tz::__clock::__local);
}
static void test_continuation() {
const std::chrono::tzdb& result = parse(
R"(
Z na 0 r f
0 r f 1000
0 r f -1000 N
0 r f ma S 31
0 r f 0 Ja lastW
0 r f -42 Jun M<=1
0 r f 42 Jul Su>=12
0 r f 42 Jul 1 2w
0 r f 42 Jul 1 01:28:14u
0 r f 42 Jul 1 -
)");
assert(result.zones.size() == 1);
assert(continuations(result.zones[0]).size() == 10);
std::chrono::__tz::__constrained_weekday r;
assert(continuations(result.zones[0])[0].__year == std::chrono::year::min());
assert(continuations(result.zones[0])[0].__in == std::chrono::January);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[0].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[0].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[0].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[1].__year == std::chrono::year(1000));
assert(continuations(result.zones[0])[1].__in == std::chrono::January);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[1].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[1].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[1].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[2].__year == std::chrono::year(-1000));
assert(continuations(result.zones[0])[2].__in == std::chrono::November);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[2].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[2].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[2].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[3].__year == std::chrono::year::max());
assert(continuations(result.zones[0])[3].__in == std::chrono::September);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[3].__on) == std::chrono::day(31));
assert(continuations(result.zones[0])[3].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[3].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[4].__year == std::chrono::year(0));
assert(continuations(result.zones[0])[4].__in == std::chrono::January);
assert(std::get<std::chrono::weekday_last>(continuations(result.zones[0])[4].__on) ==
std::chrono::weekday_last{std::chrono::Wednesday});
assert(continuations(result.zones[0])[4].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[4].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[5].__year == std::chrono::year(-42));
assert(continuations(result.zones[0])[5].__in == std::chrono::June);
r = std::get<std::chrono::__tz::__constrained_weekday>(continuations(result.zones[0])[5].__on);
assert(r.__weekday == std::chrono::Monday);
assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__le);
assert(r.__day == std::chrono::day(1));
assert(continuations(result.zones[0])[5].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[5].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[6].__year == std::chrono::year(42));
assert(continuations(result.zones[0])[6].__in == std::chrono::July);
r = std::get<std::chrono::__tz::__constrained_weekday>(continuations(result.zones[0])[6].__on);
assert(r.__weekday == std::chrono::Sunday);
assert(r.__comparison == std::chrono::__tz::__constrained_weekday::__ge);
assert(r.__day == std::chrono::day(12));
assert(continuations(result.zones[0])[6].__at.__time == std::chrono::seconds(0));
assert(continuations(result.zones[0])[6].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[7].__year == std::chrono::year(42));
assert(continuations(result.zones[0])[7].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[7].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[7].__at.__time == std::chrono::hours(2));
assert(continuations(result.zones[0])[7].__at.__clock == std::chrono::__tz::__clock::__local);
assert(continuations(result.zones[0])[8].__year == std::chrono::year(42));
assert(continuations(result.zones[0])[8].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[8].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[8].__at.__time ==
std::chrono::hours(1) + std::chrono::minutes(28) + std::chrono::seconds(14));
assert(continuations(result.zones[0])[8].__at.__clock == std::chrono::__tz::__clock::__universal);
assert(continuations(result.zones[0])[9].__year == std::chrono::year(42));
assert(continuations(result.zones[0])[9].__in == std::chrono::July);
assert(std::get<std::chrono::day>(continuations(result.zones[0])[9].__on) == std::chrono::day(1));
assert(continuations(result.zones[0])[9].__at.__time == std::chrono::hours(0)); // The man page expresses it in hours
assert(continuations(result.zones[0])[9].__at.__clock == std::chrono::__tz::__clock::__local);
}
int main(int, const char**) {
test_invalid();
test_name();
test_stdoff();
test_rules();
test_format();
test_until();
test_continuation();
return 0;
}