#include "../test.h"
#include <algorithm>
#include <unordered_set>
static ptime_t epoch_from (int32_t year, ptime_t mon = 1, ptime_t mday = 1, ptime_t hour = 0, ptime_t min = 0, ptime_t sec = 0) {
auto dt = mkdt(year, mon-1, mday, hour, min, sec);
return timegm(&dt);
}
// if step == 0 => random check, from is DIA (+- from 1970), till is ITERS COUNT
static bool test_forward (ptime_t step, ptime_t from, ptime_t till, string_view tzname, bool (*libfunc)(ptime_t, datetime*), struct tm* (*sysfunc)(const time_t*, struct tm*)) {
datetime date1;
struct tm date2;
char* hstr = getenv("VERBOSE");
bool verbose = (hstr != NULL && strlen(hstr) > 0);
static bool sranded = false;
if (!sranded) {
srand(::time(NULL));
sranded = true;
}
bool isrand = false;
ptime_t disperse = 0, epoch;
if (step == 0) {
isrand = true;
disperse = from;
step = 1;
from = 0;
}
int cnt = 0;
for (ptime_t i = from; i < till; i += step) {
cnt++;
memset(&date1, 0, sizeof(date1));
memset(&date2, 0, sizeof(date2));
if (isrand) epoch = (((uint64_t)rand())*((uint64_t)rand())) % (2*disperse) - disperse;
else epoch = i;
time_t sys_epoch = (time_t) epoch;
libfunc(epoch, &date1);
sysfunc(&sys_epoch, &date2);
if (verbose && !(cnt % 100000)) printf("TESTED #%d, last %04i-%02i-%02i %02i:%02i:%02i (off:%ld, dst=%d, zone=%s)\n", cnt, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec, date2.tm_gmtoff, date2.tm_isdst, date2.tm_zone);
if (date1.year != (date2.tm_year + 1900) || date1.mon != date2.tm_mon || date1.mday != date2.tm_mday ||
date1.hour != date2.tm_hour || date1.min != date2.tm_min || date1.sec != date2.tm_sec ||
date1.isdst != date2.tm_isdst || date1.gmtoff != date2.tm_gmtoff || strcmp(date1.zone, date2.tm_zone) != 0) {
fprintf(stderr,
"zone=%.*s, epoch=%lli\n"
"got %d-%02lld-%02lld %02lld:%02lld:%02lld %d %d %d %d %s\n"
"should be %d-%02d-%02d %02d:%02d:%02d %d %d %d %ld %s\n",
(int)tzname.length(), tzname.data(), (long long)epoch,
date1.year, (long long)date1.mon+1, (long long)date1.mday, (long long)date1.hour, (long long)date1.min, (long long)date1.sec,
date1.wday, date1.yday, date1.isdst, date1.gmtoff, date1.zone,
date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec,
date2.tm_wday, date2.tm_yday, date2.tm_isdst, date2.tm_gmtoff, date2.tm_zone
);
return false;
}
}
if (verbose) printf("TESTED %d TIMES\n", cnt);
return true;
}
// if step == 0 => random check, from is DIA years (from 1910), till is ITERS COUNT
static bool test_backward (ptime_t step, ptime_t from, ptime_t till, bool (*libfuncF)(ptime_t, datetime*), ptime_t (*libfunc)(datetime*), time_t (*sysfunc)(struct tm*), bool rand_denorm) {
datetime date1;
struct tm date2;
char* hstr = getenv("VERBOSE");
bool verbose = (hstr != NULL && strlen(hstr) > 0);
static bool sranded = false;
if (!sranded) {
srand(::time(NULL));
sranded = true;
}
bool isrand = false;
ptime_t disperce_years;
if (step == 0) {
isrand = true;
step = 1;
disperce_years = from;
if (disperce_years > 200) disperce_years = 200;
from = 0;
}
int cnt = 0;
for (ptime_t i = from; step > 0 ? (i < till) : (i > till); i += step) {
cnt++;
memset(&date1, 0, sizeof(date1));
memset(&date2, 0, sizeof(date2));
if (isrand) {
if (rand_denorm) {
int rnum = rand();
date1.sec = rnum % 10000 - 5000;
rnum /= 1000;
date1.min = rnum % 10000 - 5000;
rnum /= 1000;
date1.hour = rnum % 100 - 50;
rnum = rand();
date1.mday = rnum % 100 - 50;
rnum /= 100;
date1.mon = rnum % 100 - 50;
rnum /= 100;
date1.year = (rnum % disperce_years) + 1910;
} else {
int rnum = rand();
date1.sec = rnum % 60;
rnum /= 1000;
date1.min = rnum % 60;
rnum /= 1000;
date1.hour = rnum % 24;
rnum = rand();
date1.mday = rnum % 31 + 1;
rnum /= 100;
date1.mon = rnum % 11;
rnum /= 100;
date1.year = (rnum % disperce_years) + 1910;
}
}
else {
libfuncF(i, &date1);
}
date1.isdst = -1;
auto dt2tm = [](tm& to, const datetime& from) {
to.tm_sec = from.sec;
to.tm_min = from.min;
to.tm_hour = from.hour;
to.tm_mday = from.mday;
to.tm_mon = from.mon;
to.tm_year = from.year-1900;
to.tm_isdst = from.isdst;
to.tm_wday = from.wday;
to.tm_yday = from.yday;
to.tm_gmtoff = from.gmtoff;
to.tm_zone = const_cast<char*>(from.zone);
};
dt2tm(date2, date1);
datetime copy1 = date1;
struct tm copy2 = date2;
auto mytime = libfunc(&date1);
auto truetime = sysfunc(&date2);
if (verbose && !(cnt % 100000)) printf("TESTED #%d, last %04d-%02d-%02d %02d:%02d:%02d\n", cnt, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec);
bool same_ymdhms = (date1.year != (date2.tm_year + 1900) || date1.mon != date2.tm_mon || date1.mday != date2.tm_mday || date1.hour != date2.tm_hour || date1.min != date2.tm_min || date1.sec != date2.tm_sec) ? false : true;
bool same_zone = (date1.isdst != date2.tm_isdst || date1.gmtoff != date2.tm_gmtoff || strcmp(date1.zone, date2.tm_zone) != 0) ? false : true;
bool same_date = same_ymdhms && same_zone;
if (mytime != truetime || !same_date) {
if (truetime == -1) continue; // OS cannot handle such dates
if (same_ymdhms) { // if ambiguity, OS may return unpredicted results. Lets handle that.
datetime tmpdate = date1;
tmpdate.isdst = 1;
mytime = libfunc(&tmpdate);
if (mytime == truetime) continue;
}
fprintf(stderr,
"MY: epoch=%lli (%04d/%02lld/%02lld %02lld:%02lld:%02lld %4s %d) from %04d/%02lld/%02lld %02lld:%02lld:%02lld DST=%d (%.*s)\n",
(long long)mytime,
date1.year, (long long)date1.mon+1, (long long)date1.mday, (long long)date1.hour, (long long)date1.min, (long long)date1.sec,
date1.zone, date1.gmtoff,
copy1.year, (long long)copy1.mon+1, (long long)copy1.mday, (long long)copy1.hour, (long long)copy1.min, (long long)copy1.sec,
copy1.isdst, (int)tzlocal()->name.length(), tzlocal()->name.data()
);
fprintf(stderr,
"OS: epoch=%li (%04d/%02d/%02d %02d:%02d:%02d %4s %ld) from %04d/%02d/%02d %02d:%02d:%02d DST=%d (%.*s)\n",
truetime, date2.tm_year+1900, date2.tm_mon+1, date2.tm_mday, date2.tm_hour, date2.tm_min, date2.tm_sec, date2.tm_zone, date2.tm_gmtoff,
copy2.tm_year+1900, copy2.tm_mon+1, copy2.tm_mday, copy2.tm_hour, copy2.tm_min, copy2.tm_sec, copy2.tm_isdst, (int)tzlocal()->name.length(), tzlocal()->name.data()
);
fprintf(stderr, "diff is %lli", (long long)(mytime - truetime));
return false;
}
}
if (verbose) printf("TESTED %d TIMES\n", cnt);
return true;
}
static bool test_gmtime (ptime_t step, ptime_t from, ptime_t till) { return test_forward(step, from, till, "GMT", &panda::time::gmtime, &::gmtime_r); }
static bool test_timegm (ptime_t step, ptime_t from, ptime_t till) { return test_backward(step, from, till, &panda::time::gmtime, &panda::time::timegm, &::timegm, true); }
static bool test_localtime (ptime_t step, ptime_t from, ptime_t till) { return test_forward(step, from, till, tzlocal()->name, &panda::time::localtime, &::localtime_r); }
// dont test denormalized values (LINUX has bugs with them + when using leap seconds zone, our normalization may differ with OS)
static bool test_timelocal (ptime_t step, ptime_t from, ptime_t till) { return test_backward(step, from, till, &panda::time::localtime, &panda::time::timelocal, &::timelocal, false); }
struct Dia {
ptime_t step;
ptime_t from;
ptime_t till;
};
using Dias = std::vector<Dia>;
TEST_CASE("full-gmtime", "[.]") {
X64ONLY;
CHECK(test_gmtime( 299, epoch_from( 2005), epoch_from(2008,12,30))); // check normal times
CHECK(test_gmtime( 1, epoch_from( 2004,12,31), epoch_from(2005))); // check QUAD YEARS threshold
CHECK(test_gmtime( 1, epoch_from( 1900,12,31), epoch_from(1901))); // check CENT YEARS threshold
CHECK(test_gmtime( 1, epoch_from( 2000,12,31), epoch_from(2001))); // check QUAD CENT YEARS threshold
CHECK(test_gmtime(86399, epoch_from(-1000), epoch_from(2014))); // negative check
// random check
CHECK(test_gmtime(0, 1500000000, 1000000));
CHECK(test_gmtime(0, 20000000000, 1000000));
}
TEST_CASE("full-timegm", "[.]") {
X64ONLY;
CHECK(test_timegm( 299, epoch_from(2005), epoch_from(2008,12,30))); // check normal times
CHECK(test_timegm( 1, epoch_from(2004,12,31), epoch_from(2005))); // check QUAD YEARS threshold
CHECK(test_timegm( 1, epoch_from(1900,12,31), epoch_from(1901))); // check CENT YEARS threshold
CHECK(test_timegm( 1, epoch_from(2000,12,31), epoch_from(2001))); // check QUAD CENT YEARS threshold
CHECK(test_timegm(9999, epoch_from(1900), epoch_from(2014))); // negative check, system's timegm cannot handle 1899-12-31 23:59:59 and earlier
// random check
CHECK(test_timegm(0, 200, 200000));
CHECK(test_timegm(0, 200, 200000));
}
TEST_CASE("full-localtime", "[.]") {
X64ONLY;
auto old = tzdir();
use_system_timezones();
for (auto tzname : {"Europe/Moscow", "America/New_York", "Australia/Melbourne"}) {
setenv("TZ", tzname, 1);
panda::time::tzset();
::tzset();
// check past
CHECK(test_localtime(39, epoch_from(1879), epoch_from(1881)));
// check transitions
CHECK(test_localtime(299, epoch_from(1980), epoch_from(1986)));
CHECK(test_localtime(299, epoch_from(2000), epoch_from(2006)));
CHECK(test_localtime(59, epoch_from(2000), epoch_from(2001)));
// check near future
CHECK(test_localtime(59, epoch_from(2016), epoch_from(2022)));
// check far future
CHECK(test_localtime(299, epoch_from(2060), epoch_from(2066)));
CHECK(test_localtime(59, epoch_from(2066), epoch_from(2067)));
// negative check
CHECK(test_localtime(59, epoch_from(-1000), epoch_from(-999)));
// random check
CHECK(test_localtime(0, 1500000000, 1000000));
CHECK(test_localtime(0, 20000000000, 1000000));
}
unsetenv("TZ");
tzdir(old);
}
TEST_CASE("full-timelocal", "[.]") {
X64ONLY;
auto old = tzdir();
use_system_timezones();
// Europe/Moscow disabled - system's timelocal has a lot of bugs with non-standart transitions which occur in Moscow
for (auto tzname : {"America/New_York", "Australia/Melbourne"}) {
setenv("TZ", tzname, 1);
panda::time::tzset();
::tzset();
// check past - unavailable, system's timelocal cannot work with these dates
// check transitions
CHECK(test_timelocal(86399, epoch_from(1910), epoch_from(1986)));
CHECK(test_timelocal(3599, epoch_from(1980), epoch_from(1986)));
CHECK(test_timelocal(3599, epoch_from(2000), epoch_from(2006)));
CHECK(test_timelocal(3599, epoch_from(2006), epoch_from(2011)));
// check near future
CHECK(test_timelocal(3599, epoch_from(2016), epoch_from(2022)));
// check far future
CHECK(test_timelocal(3599, epoch_from(2060), epoch_from(2066)));
// random check
CHECK(test_timelocal(0, 200, 400000));
}
unsetenv("TZ");
tzdir(old);
}
TEST_CASE("full-leapzone", "[.]") {
X64ONLY;
auto old = tzdir();
use_system_timezones();
Dias dias = {
// check past - unavailable, OS's timelocal cannot work with these dates
// check transitions
{86399, epoch_from(1910), epoch_from(1970)},
{3599, epoch_from(1980), epoch_from(1986)},
{3599, epoch_from(2000), epoch_from(2006)},
{3599, epoch_from(2006), epoch_from(2011)},
// check near future
{3599, epoch_from(2016), epoch_from(2022)},
// check far future
{3599, epoch_from(2060), epoch_from(2066)},
// leap moments
{1, epoch_from(1981,06,30,12,00,00), epoch_from(1981,07,01,12,00,00)},
{1, epoch_from(1982,06,30,12,00,00), epoch_from(1982,07,01,12,00,00)},
{1, epoch_from(1983,06,30,12,00,00), epoch_from(1983,07,01,12,00,00)},
{1, epoch_from(1985,06,30,12,00,00), epoch_from(1985,07,01,12,00,00)},
{1, epoch_from(1987,12,31,12,00,00), epoch_from(1988,01,01,12,00,00)},
{1, epoch_from(1989,12,31,12,00,00), epoch_from(1990,01,01,12,00,00)},
{1, epoch_from(1990,12,31,12,00,00), epoch_from(1991,01,01,12,00,00)},
{1, epoch_from(1992,06,30,12,00,00), epoch_from(1992,07,01,12,00,00)},
{1, epoch_from(1993,06,30,12,00,00), epoch_from(1993,07,01,12,00,00)},
{1, epoch_from(1994,06,30,12,00,00), epoch_from(1994,07,01,12,00,00)},
{1, epoch_from(1995,12,31,12,00,00), epoch_from(1996,01,01,12,00,00)},
{1, epoch_from(1997,06,30,12,00,00), epoch_from(1997,07,01,12,00,00)},
{1, epoch_from(1998,12,31,12,00,00), epoch_from(1999,01,01,12,00,00)},
{1, epoch_from(2005,12,31,12,00,00), epoch_from(2006,01,01,12,00,00)},
{1, epoch_from(2008,12,31,12,00,00), epoch_from(2009,01,01,12,00,00)},
{1, epoch_from(2012,06,30,12,00,00), epoch_from(2012,07,01,12,00,00)}
};
for (auto tzname : {"right/UTC", "right/America/New_York", "right/Australia/Melbourne"}) {
DYNAMIC_SECTION(tzname) {
setenv("TZ", tzname, 1);
panda::time::tzset();
::tzset();
if (tzlocal()->name != tzname) {
WARN("SKIPPED, leap zone " << tzname << " not found in system");
continue;
}
for (auto& dia : dias) {
INFO("step=" << dia.step << ", from=" << dia.from << ", till=" << dia.till);
CHECK(test_localtime(dia.step, dia.from, dia.till));
CHECK(test_timelocal(dia.step, dia.from, dia.till));
}
// random check
CHECK(test_localtime(0, 1500000000, 5000000));
CHECK(test_timelocal(0, 120, 1000000));
}
}
unsetenv("TZ");
tzdir(old);
}
static void test_all_zones (int part) {
const int parts = 9;
X64ONLY;
auto old = tzdir();
use_system_timezones();
// many OS have bugs in localtime/timelocal implementations which prevent them from working correctly with listed time zones in our time periods
std::unordered_set<string> buggy_zones {
"America/Anchorage", "Australia/Lord_Howe", "America/Scoresbysund", "America/Nome", "Asia/Choibalsan", "Asia/Ust-Nera",
"Asia/Tehran", "posix/Iran", "posix/Asia/Tehran", "Iran"
};
auto tzlist = available_timezones();
std::sort(tzlist.begin(), tzlist.end(), [](auto a, auto b) { return a < b; });
for (size_t i = part-1; i < tzlist.size(); i += parts) {
auto tzname = tzlist[i];
if (buggy_zones.count(tzname)) continue;
if (tzname.find("posix") == 0) continue;
bool leapzone = tzname.find("right") == 0;
setenv("TZ", tzname.c_str(), 1);
panda::time::tzset();
::tzset();
DYNAMIC_SECTION(tzname) {
// check past
CHECK(test_localtime(3599, epoch_from(1980), epoch_from(1986)));
CHECK(test_timelocal(3599, epoch_from(1980), epoch_from(1986)));
// check transitions
CHECK(test_localtime(3599, epoch_from(2000), epoch_from(2006)));
CHECK(test_timelocal(3599, epoch_from(2000), epoch_from(2006)));
// check near future
CHECK(test_localtime(3599, epoch_from(2016), epoch_from(2022)));
CHECK(test_timelocal(3599, epoch_from(2016), epoch_from(2022)));
// check far future
if (!leapzone) { // skip testing future in leap second zones (OS has bugs)
CHECK(test_localtime(3599, epoch_from(2060), epoch_from(2066)));
CHECK(test_timelocal(3599, epoch_from(2060), epoch_from(2066)));
}
}
}
unsetenv("TZ");
tzdir(old);
}
TEST_CASE("full-zones-1", "[.]") { test_all_zones(1); }
TEST_CASE("full-zones-2", "[.]") { test_all_zones(2); }
TEST_CASE("full-zones-3", "[.]") { test_all_zones(3); }
TEST_CASE("full-zones-4", "[.]") { test_all_zones(4); }
TEST_CASE("full-zones-5", "[.]") { test_all_zones(5); }
TEST_CASE("full-zones-6", "[.]") { test_all_zones(6); }
TEST_CASE("full-zones-7", "[.]") { test_all_zones(7); }
TEST_CASE("full-zones-8", "[.]") { test_all_zones(8); }
TEST_CASE("full-zones-9", "[.]") { test_all_zones(9); }