/* * Copyright (c) 2019 TAOS Data, Inc. * * This program is free software: you can use, redistribute, and/or modify * it under the terms of the GNU Affero General Public License, version 3 * or later ("AGPL"), as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wwrite-strings" #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wformat" #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" #pragma GCC diagnostic ignored "-Wpointer-arith" #include "os.h" #include "tlog.h" TEST(osTimeTests, taosLocalTime) { // Test 1: Test when both timep and result are not NULL time_t timep = 1617531000; // 2021-04-04 18:10:00 struct tm result; struct tm *local_time = taosLocalTime(&timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 121); ASSERT_EQ(local_time->tm_mon, 3); ASSERT_EQ(local_time->tm_mday, 4); ASSERT_EQ(local_time->tm_hour, 18); ASSERT_EQ(local_time->tm_min, 10); ASSERT_EQ(local_time->tm_sec, 00); // Test 2: Test when timep is NULL local_time = taosLocalTime(NULL, &result, NULL, 0, NULL); ASSERT_EQ(local_time, nullptr); // Test 4: Test when timep is negative on Windows #ifdef WINDOWS time_t pos_timep = 1609459200; // 2021-01-01 08:00:00 local_time = taosLocalTime(&pos_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 121); ASSERT_EQ(local_time->tm_mon, 0); ASSERT_EQ(local_time->tm_mday, 1); ASSERT_EQ(local_time->tm_hour, 8); ASSERT_EQ(local_time->tm_min, 0); ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep = -1617531000; // 1918-09-29 21:50:00 local_time = taosLocalTime(&neg_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 18); ASSERT_EQ(local_time->tm_mon, 8); ASSERT_EQ(local_time->tm_mday, 29); ASSERT_EQ(local_time->tm_hour, 21); ASSERT_EQ(local_time->tm_min, 50); ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep2 = -315619200; // 1960-01-01 08:00:00 local_time = taosLocalTime(&neg_timep2, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 60); ASSERT_EQ(local_time->tm_mon, 0); ASSERT_EQ(local_time->tm_mday, 1); ASSERT_EQ(local_time->tm_hour, 8); ASSERT_EQ(local_time->tm_min, 0); ASSERT_EQ(local_time->tm_sec, 0); time_t zero_timep = 0; // 1970-01-01 08:00:00 local_time = taosLocalTime(&zero_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 70); ASSERT_EQ(local_time->tm_mon, 0); ASSERT_EQ(local_time->tm_mday, 1); ASSERT_EQ(local_time->tm_hour, 8); ASSERT_EQ(local_time->tm_min, 0); ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep3 = -78115158887; local_time = taosLocalTime(&neg_timep3, &result, NULL, 0, NULL); ASSERT_EQ(local_time, nullptr); #endif } TEST(osTimeTests, taosGmTimeR) { // Test 1: Test when both timep and result are not NULL time_t timep = 1617531000; // 2021-04-04 18:10:00 struct tm tmInfo; ASSERT_NE(taosGmTimeR(&timep, &tmInfo), nullptr); char buf[128]; taosStrfTime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tmInfo); ASSERT_STREQ(buf, "2021-04-04T10:10:00"); } TEST(osTimeTests, taosTimeGm) { char *timestr= "2021-04-04T18:10:00"; struct tm tm = {0}; taosStrpTime(timestr, "%Y-%m-%dT%H:%M:%S", &tm); int64_t seconds = taosTimeGm(&tm); ASSERT_EQ(seconds, 1617559800); } TEST(osTimeTests, taosMktime) { char *timestr= "2021-04-04T18:10:00"; struct tm tm = {0}; taosStrpTime(timestr, "%Y-%m-%dT%H:%M:%S", &tm); time_t seconds = taosMktime(&tm, NULL); ASSERT_EQ(seconds, 1617531000); } #ifdef WINDOWS TEST(osTimeTests, windowsGlobalTimezoneOffset) { ASSERT_EQ(taosSetGlobalTimezone("UTC-8"), 0); ASSERT_EQ(getWindowsTimezoneOffset(), -TdEastZone8); int32_t code = 0; ASSERT_EQ(taosGetLocalTimezoneOffset(&code), TdEastZone8); ASSERT_EQ(taosSetGlobalTimezone("UTC"), 0); ASSERT_EQ(getWindowsTimezoneOffset(), 0); ASSERT_EQ(taosGetLocalTimezoneOffset(&code), 0); // Restore the default expected by existing Windows time tests. ASSERT_EQ(taosSetGlobalTimezone("UTC-8"), 0); } TEST(osTimeTests, windowsInitTimezoneKeepsConfiguredTZ) { // Simulate user config timezone = UTC before initTimezoneInfo() runs. ASSERT_EQ(taosSetGlobalTimezone("UTC"), 0); ASSERT_EQ(getWindowsTimezoneOffset(), 0); ASSERT_EQ(initTimezoneInfo(), TSDB_CODE_SUCCESS); ASSERT_EQ(getWindowsTimezoneOffset(), 0); int32_t code = 0; ASSERT_EQ(taosGetLocalTimezoneOffset(&code), 0); // 1602172800 is 2020-10-08 16:00:00 UTC. time_t ts = 1602172800; struct tm tmVal; ASSERT_NE(taosLocalTime(&ts, &tmVal, NULL, 0, NULL), nullptr); ASSERT_EQ(tmVal.tm_year + 1900, 2020); ASSERT_EQ(tmVal.tm_mon + 1, 10); ASSERT_EQ(tmVal.tm_mday, 8); ASSERT_EQ(tmVal.tm_hour, 16); ASSERT_EQ(tmVal.tm_min, 0); ASSERT_EQ(tmVal.tm_sec, 0); // Restore the default expected by existing Windows time tests. ASSERT_EQ(taosSetGlobalTimezone("UTC-8"), 0); } TEST(osTimeTests, windowsInitTimezoneFromSystemZone) { // Simulate user not configuring timezone: clear TZ env var and call initTimezoneInfo(). // This tests that initTimezoneInfo correctly reads from Windows system timezone // using GetTimeZoneInformation and sets TZ environment variable. // Clear any pre-existing TZ SetEnvironmentVariableA("TZ", NULL); // Call initTimezoneInfo() - should read system timezone and set TZ env var ASSERT_EQ(initTimezoneInfo(), TSDB_CODE_SUCCESS); // Verify TZ is now set after initTimezoneInfo char tzEnv[128] = {0}; DWORD len = GetEnvironmentVariableA("TZ", tzEnv, sizeof(tzEnv)); ASSERT_GT(len, 0); // TZ should be non-empty // Verify that getWindowsTimezoneOffset returns a valid value int64_t offset = getWindowsTimezoneOffset(); uInfo("[test] System timezone offset = %lld seconds", offset); // Verify taosGetLocalTimezoneOffset is consistent int32_t code = 0; int32_t tz_offset = taosGetLocalTimezoneOffset(&code); uInfo("[test] taosGetLocalTimezoneOffset = %d seconds, code = %d", tz_offset, code); // Restore to a known state ASSERT_EQ(taosSetGlobalTimezone("UTC-8"), 0); } TEST(osTimeTests, windowsOffsetFallbackWhenTZUnset) { // Simulate early call path: time conversion happens before explicit init. SetEnvironmentVariableA("TZ", NULL); TIME_ZONE_INFORMATION tzi = {0}; DWORD tzType = GetTimeZoneInformation(&tzi); ASSERT_NE(tzType, TIME_ZONE_ID_INVALID); LONG minute_offset = tzi.Bias; if (tzType == TIME_ZONE_ID_DAYLIGHT) { minute_offset += tzi.DaylightBias; } else if (tzType == TIME_ZONE_ID_STANDARD) { minute_offset += tzi.StandardBias; } int64_t expected = (int64_t)minute_offset * 60; ASSERT_EQ(getWindowsTimezoneOffset(), expected); } #endif TEST(osTimeTests, invalidParameter) { void *retp = NULL; int32_t reti = 0; char buf[1024] = {0}; char fmt[1024] = {0}; struct tm tm = {0}; struct timeval tv = {0}; retp = taosStrpTime(buf, fmt, NULL); EXPECT_EQ(retp, nullptr); retp = taosStrpTime(NULL, fmt, &tm); EXPECT_EQ(retp, nullptr); retp = taosStrpTime(buf, NULL, &tm); EXPECT_EQ(retp, nullptr); reti = taosGetTimeOfDay(NULL); EXPECT_NE(reti, 0); reti = taosTime(NULL); EXPECT_NE(reti, 0); tm.tm_year = 2024; tm.tm_mon = 10; tm.tm_mday = 23; tm.tm_hour = 12; tm.tm_min = 1; tm.tm_sec = 0; tm.tm_isdst = -1; time_t rett = taosMktime(&tm, NULL); EXPECT_NE(rett, 0); retp = taosLocalTime(NULL, &tm, NULL, 0, NULL); EXPECT_EQ(retp, nullptr); retp = taosLocalTime(&rett, NULL, NULL, 0, NULL); EXPECT_EQ(retp, nullptr); reti = taosSetGlobalTimezone(NULL); EXPECT_NE(reti, 0); } #ifndef WINDOWS TEST(osTimeTests, truncateTimezoneStringRemovesLeadingSlash) { char tz[TD_TIMEZONE_LEN] = "/UTC"; truncateTimezoneString(tz); EXPECT_STREQ(tz, "UTC"); } #endif TEST(osTimeTests, user_mktime64) { int64_t reti = 0; reti = user_mktime64(2024, 10, 23, 12, 3, 2, 1); EXPECT_NE(reti, 0); reti = user_mktime64(2024, 1, 23, 12, 3, 2, 1); EXPECT_NE(reti, 0); } TEST(osTimeTests, taosLocalTimeBenchmark) { const int threads = 400; const int iters = 1000000; std::atomic ok{0}, err{0}; std::vector ths; char tsTimezoneStr[TD_TIMEZONE_LEN] = {0}; (void)initTimezoneInfo(); // Use a fixed timestamp to avoid extra syscalls in the hot loop time_t tp = taosGetTimestampSec(); auto start = std::chrono::steady_clock::now(); ths.reserve(threads); for (int i = 0; i < threads; ++i) { ths.emplace_back([&]() { struct tm tm1; for (int j = 0; j < iters; ++j) { if (taosLocalTime(&tp, &tm1, NULL, 0, NULL) != nullptr) { ok.fetch_add(1, std::memory_order_relaxed); } else { err.fetch_add(1, std::memory_order_relaxed); } } }); } for (auto &t : ths) t.join(); auto end = std::chrono::steady_clock::now(); auto ms = std::chrono::duration_cast(end - start).count(); const uint64_t total = static_cast(threads) * static_cast(iters); double qps = ms > 0 ? (double)total / ((double)ms / 1000.0) : 0.0; std::cout << "[taosLocalTime bench] threads=" << threads << " iters=" << iters << " total_calls=" << total << " ok=" << ok.load() << " err=" << err.load() << " elapsed_ms=" << ms << " throughput_calls_per_sec=" << qps << std::endl; // Ensure correctness: all calls should succeed EXPECT_EQ(ok.load(), total); EXPECT_EQ(err.load(), 0u); } #ifdef WINDOWS #else TEST(osTimeTests, tzConcurrencyBreakTest) { constexpr int kReaderThreads = 16; constexpr int kDurationSec = 3; ASSERT_EQ(initTimezoneInfo(), TSDB_CODE_SUCCESS); std::atomic stop{false}; std::atomic errors{0}; std::vector threads; time_t tp = taosGetTimestampSec(); threads.emplace_back([&]() { const char* tzs[] = { "UTC", "Asia/Shanghai", "America/New_York", "Europe/Berlin" }; int i = 0; while (!stop.load(std::memory_order_relaxed)) { setenv("TZ", tzs[i++ % 4], 1); tzset(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }); for (int i = 0; i < kReaderThreads; ++i) { threads.emplace_back([&]() { struct tm tm1; while (!stop.load(std::memory_order_relaxed)) { if (!taosLocalTime(&tp, &tm1, nullptr, 0, nullptr)) { errors.fetch_add(1, std::memory_order_relaxed); continue; } if (tm1.tm_sec < 0 || tm1.tm_sec > 60 || tm1.tm_min < 0 || tm1.tm_min > 59 || tm1.tm_hour < 0 || tm1.tm_hour > 23 || tm1.tm_mday < 1 || tm1.tm_mday > 31 || tm1.tm_mon < 0 || tm1.tm_mon > 11) { errors.fetch_add(1, std::memory_order_relaxed); } } }); } std::this_thread::sleep_for(std::chrono::seconds(kDurationSec)); stop.store(true, std::memory_order_relaxed); for (auto& thread : threads) { if (thread.joinable()) { thread.join(); } } std::cout << "Test completed with " << errors.load() << " errors" << std::endl; unsetenv("TZ"); tzset(); } #endif