#include "../test.h"
#include <string>
#define TEST(name) TEST_CASE("time-tz: " name, "[time-tz]")
#ifdef _WIN32
#define TEST_NO_SETENV
#endif
#ifdef __FreeBSD__
#include <osreldate.h>
#if __FreeBSD_version <= 1201000
#define TEST_NO_SETENV
#endif
#endif
TEST("available timezones(embed)") {
auto list = available_timezones();
CHECK(list.size() == 1212);
}
TEST("available timezones(system)") {
if (!getenv("TEST_FULL")) return;
auto old = tzdir();
use_system_timezones();
auto list = available_timezones();
CHECK(list.size() > 0);
tzdir(old);
}
TEST("rule parsing") {
auto wrong = [](string_view zone) {
SECTION("check bad virtual zone: " + std::string(zone.data(), zone.size())) {
CHECK(tzget(zone)->name == "GMT0");
}
};
struct CheckTzRule {
int gmt_offset = 999999;
string_view abbrev = "";
std::vector<int> end = {}; // <mon>, <week>, <wday>, <hour>, <min>, <sec>
int isdst = -1;
};
auto check_tzrulezone = [](const Timezone::Rule::Zone& info, const CheckTzRule& chk) {
CHECK(info.gmt_offset == chk.gmt_offset);
if (chk.abbrev.size()) CHECK(info.abbrev == chk.abbrev);
switch (chk.end.size()) {
case 6 : CHECK(info.end.sec == chk.end[5]); // fallthrough
case 5 : CHECK(info.end.min == chk.end[4]); // fallthrough
case 4 : CHECK(info.end.hour == chk.end[3]); // fallthrough
case 3 : CHECK(info.end.wday == chk.end[2]); // fallthrough
case 2 : CHECK(info.end.yday == chk.end[1]); // fallthrough
case 1 : CHECK(info.end.mon == chk.end[0]); // fallthrough
default: break;
}
if (chk.isdst == 1) CHECK(info.isdst);
else if (!chk.isdst) CHECK(!info.isdst);
};
auto check = [&check_tzrulezone](string_view zname, int hasdst = -1, const CheckTzRule& outer = {}, const CheckTzRule& inner = {}) {
SECTION("check virtual zone: " + std::string(zname.data(), zname.size())) {
auto zone = tzget(zname);
CHECK(zone->name == zname);
CHECK(!zone->is_local);
if (hasdst == 1) CHECK(zone->future.hasdst);
else if (!hasdst) CHECK(!zone->future.hasdst);
if (outer.gmt_offset != 999999) check_tzrulezone(zone->future.outer, outer);
if (inner.gmt_offset != 999999) check_tzrulezone(zone->future.inner, inner);
}
};
wrong("A");
wrong("MSK");
check("MSK-1", 0, {3600});
check("MSK2", 0, {-7200});
check("MSK+3", 0, {-10800});
check("MSK-4MSD", 0, {14400});
wrong("MSK-4:");
check("MSK-4:20", 0, {15600});
wrong("MSK-4:20:");
check("MSK-4:20:08", 0, {15608});
wrong("MSK-4:20:01:");
check("MSK-4:20:08MSA", 0, {15608});
wrong("MSK-4MSD,");
wrong("MSK-4MSD,asdfdasfds");
wrong("MSK-4MSD,M3.1.0");
wrong("MSK-4MSD,M3.1.0,M10.5.0,");
check("MSK-4MSD,M3.1.0,M10.5.0", 1, {14400, "MSK", {2,1,0,2,0,0}, 0}, {18000, "MSD", {9,5,0,2,0,0}, 1});
check("MSK-4MSD,M3.1.0,M10.5.0/3", 1, {14400, "MSK", {2,1,0,2,0,0}, 0}, {18000, "MSD", {9,5,0,3,0,0}, 1});
check("MSK-4MSD,M3.1.0,M10.5.0/3:15", 1, {14400, "MSK", {2,1,0,2,0,0}, 0}, {18000, "MSD", {9,5,0,3,15,0}, 1});
check("MSK-4MSD,M3.1.0,M10.5.0/3:15:02", 1, {14400, "MSK", {2,1,0,2,0,0}, 0}, {18000, "MSD", {9,5,0,3,15,2}, 1});
check("MSK-4MSD,M3.1.0/1,M10.5.0/3:15:02", 1, {14400, "MSK", {2,1,0,1,0,0}, 0}, {18000, "MSD", {9,5,0,3,15,2}, 1});
check("MSK-4MSD,M3.1.0/1:59,M10.5.0/3:15:02", 1, {14400, "MSK", {2,1,0,1,59,0}, 0}, {18000, "MSD", {9,5,0,3,15,2}, 1});
check("MSK-4MSD,M3.1.0/1:59:58,M10.5.0/3:15:02", 1, {14400, "MSK", {2,1,0,1,59,58}, 0}, {18000, "MSD", {9,5,0,3,15,2}, 1});
wrong("MSK-4MSD,M3.1.0/1:59:58,M10.5.0/3:15:02:");
wrong("MSK-4MSD,M3.1.0/1:59:58,M10.5.0/3:15:");
wrong("MSK-4MSD,M3.1.0/1:59:58,M10.5.0/3:");
wrong("MSK-4MSD,M3.1.0/1:59:58,M10.5.0/");
wrong("MSK-4MSD,M3.1.0/1:59:,M10.5.0");
wrong("MSK-4MSD,M3.1.0/1:,M10.5.0");
wrong("MSK-4MSD,M3.1.0/,M10.5.0");
check("MSK-4MSD,M3.1.0/-1,M10.5.0");
wrong("MSK-4MSD,M3.0.0,M10.5.0");
wrong("MSK-4MSD,M3.6.0,M10.5.0");
wrong("MSK-4MSD,M3.0.0,M13.5.0");
wrong("MSK-4MSD,M3.0.0,M0.5.0");
wrong("MSK-4MSD,M3.1.-1,M0.5.0");
wrong("MSK-4MSD,M3.1.7,M0.5.0");
wrong("MSK-4-5");
check("<MSK-4>-5", 0, {18000, "MSK-4"});
wrong(":MSK-4");
wrong("MS1K-4");
wrong("SK-4");
}
TEST("timezones parsing") {
auto list = available_timezones();
for (auto& zname : list) {
auto zone = tzget(zname);
CHECK(zone);
CHECK(zone->name == zname);
}
}
TEST("tzset") {
tzset("Europe/Moscow");
CHECK(tzlocal()->name == "Europe/Moscow");
tzset(tzget("America/New_York"));
CHECK(tzlocal()->name == "America/New_York");
}
#ifndef TEST_NO_SETENV
TEST("tzset via ENV{TZ}") {
setenv("TZ", "Europe/Moscow", 1);
panda::time::tzset();
CHECK(tzlocal()->name == "Europe/Moscow");
setenv("TZ", "America/New_York", 1);
panda::time::tzset();
CHECK(tzlocal()->name == "America/New_York");
unsetenv("TZ");
panda::time::tzset();
CHECK(tzlocal()->name);
}
#endif
TEST("tzdir") {
auto now = ::time(NULL);
tzset("Europe/Moscow");
CHECK(tzlocal()->name == "Europe/Moscow");
auto date1 = localtime(now);
tzset("America/New_York");
CHECK(tzlocal()->name == "America/New_York");
auto date2 = localtime(now);
auto old = tzdir();
tzdir("tests/time/testzones");
CHECK(available_timezones().size() == 2);
tzset("Moscow");
CHECK(tzlocal()->name == "Moscow");
CHECK_DATETIME(localtime(now), date1);
CHECK(timelocal(&date1) == now);
tzset("New_York");
CHECK(tzlocal()->name == "New_York");
CHECK_DATETIME(localtime(now), date2);
CHECK(timelocal(&date2) == now);
tzdir(old);
}