Skip to content

Commit 175850f

Browse files
authored
refactor(timezone): checkTime in dnode and mnode implementations and timetruncate logic for interval (3.3.6 branch) (#35058)
1 parent 9b9b92a commit 175850f

File tree

7 files changed

+319
-39
lines changed

7 files changed

+319
-39
lines changed

include/common/tmsg.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1973,7 +1973,14 @@ void tFreeSRetrieveFuncRsp(SRetrieveFuncRsp* pRsp);
19731973

19741974
typedef struct {
19751975
int32_t statusInterval;
1976-
int64_t checkTime; // 1970-01-01 00:00:00.000
1976+
/*
1977+
Local timezone UTC offset in seconds (east-positive, e.g. +28800 for
1978+
Asia/Shanghai). Derived from taosGetLocalTimezoneOffset() on each
1979+
status report. Paired with the timezone string in
1980+
mndCheckClusterCfgPara: a mismatch is reported only when both the
1981+
timezone string AND this offset differ.
1982+
*/
1983+
int64_t checkTime;
19771984
char timezone[TD_TIMEZONE_LEN]; // tsTimezone
19781985
char locale[TD_LOCALE_LEN]; // tsLocale
19791986
char charset[TD_LOCALE_LEN]; // tsCharset

source/common/src/ttime.c

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,33 @@ int64_t taosTimeGetIntervalEnd(int64_t intervalStart, const SInterval* pInterval
876876
1;
877877
}
878878

879+
/*
880+
getTZOffsetAtTicks - return the east-positive UTC offset (in ticks) that is
881+
in effect at the given timestamp.
882+
883+
Unlike taosGetTZOffsetSeconds() which queries the offset for "now", this
884+
function converts `ticks` to local time via taosLocalTime() and then
885+
derives the offset as (taosTimeGm(local) - t_sec), so it correctly
886+
resolves DST for the *target* instant.
887+
888+
On conversion failure 0 is returned (UTC fallback).
889+
*/
890+
static int64_t getTZOffsetAtTicks(int64_t ticks, int32_t precision, timezone_t tz) {
891+
int64_t factor = TSDB_TICK_PER_SECOND(precision);
892+
int64_t t_sec_ticks = ticks / factor;
893+
if (ticks < 0 && ticks % factor != 0) {
894+
t_sec_ticks -= 1;
895+
}
896+
time_t t_sec = (time_t)t_sec_ticks;
897+
struct tm tm_local;
898+
if (taosLocalTime(&t_sec, &tm_local, NULL, 0, tz) == NULL) {
899+
uWarn("%s failed to convert ticks:%" PRId64 " to local time, code:%d",
900+
__FUNCTION__, ticks, ERRNO);
901+
return 0;
902+
}
903+
return (int64_t)(taosTimeGm(&tm_local) - t_sec) * factor;
904+
}
905+
879906
int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
880907
if (pInterval->sliding == 0) {
881908
return ts;
@@ -917,10 +944,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
917944
if (IS_CALENDAR_TIME_DURATION(pInterval->intervalUnit)) {
918945
int64_t news = (ts / pInterval->sliding) * pInterval->sliding;
919946
if (pInterval->slidingUnit == 'd' || pInterval->slidingUnit == 'w') {
920-
#if defined(WINDOWS) && _MSC_VER >= 1900
921-
int64_t timezone = _timezone;
922-
#endif
923-
news += (int64_t)(timezone * TSDB_TICK_PER_SECOND(precision));
947+
news -= getTZOffsetAtTicks(news, precision, pInterval->timezone);
924948
}
925949

926950
start = news;
@@ -952,18 +976,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
952976
start = (delta / pInterval->sliding + factor) * pInterval->sliding;
953977

954978
if (pInterval->intervalUnit == 'd' || pInterval->intervalUnit == 'w') {
955-
/*
956-
* here we revised the start time of day according to the local time zone,
957-
* but in case of DST, the start time of one day need to be dynamically decided.
958-
*/
959-
// todo refactor to extract function that is available for Linux/Windows/Mac platform
960-
#if defined(WINDOWS) && _MSC_VER >= 1900
961-
// see
962-
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/daylight-dstbias-timezone-and-tzname?view=vs-2019
963-
int64_t timezone = _timezone;
964-
#endif
965-
966-
start += (int64_t)(timezone * TSDB_TICK_PER_SECOND(precision));
979+
start -= getTZOffsetAtTicks(start, precision, pInterval->timezone);
967980
}
968981

969982
int64_t end = 0;

source/common/test/commonTests.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,4 +1106,147 @@ TEST(testCase, function_taosTimeTruncate) {
11061106
ASSERT_LE(res, 1633450000000);
11071107
}
11081108

1109+
/*
1110+
* Test: day-interval alignment under DST timezone (America/New_York).
1111+
*
1112+
* America/New_York has two UTC offsets:
1113+
* Winter (EST): UTC-5 (-18000s)
1114+
* Summer (EDT): UTC-4 (-14400s)
1115+
*
1116+
* For INTERVAL(1d) TIMEZONE('America/New_York'):
1117+
* - A winter timestamp should align to midnight EST = 05:00 UTC
1118+
* - A summer timestamp should align to midnight EDT = 04:00 UTC
1119+
*
1120+
* Historical BUG being tested: taosTimeTruncate internally calls
1121+
* taosGetTZOffsetSeconds() which returns the offset for "now" (the
1122+
* current wall-clock time), not the offset for the target timestamp.
1123+
* So if "now" is summer, a winter timestamp gets the EDT offset,
1124+
* making the day boundary 04:00 UTC instead of the correct
1125+
* 05:00 UTC — off by 1 hour.
1126+
*/
1127+
#ifndef WINDOWS
1128+
// RAII guard to restore global timezone even if test assertions fail mid-way.
1129+
struct TzRestoreGuard {
1130+
~TzRestoreGuard() { taosSetGlobalTimezone("Asia/Shanghai"); }
1131+
};
1132+
1133+
TEST(testCase, taosTimeTruncate_DST_day_interval) {
1134+
TzRestoreGuard tzGuard; // restores Asia/Shanghai on scope exit
1135+
1136+
// Setup: create a timezone object for America/New_York
1137+
timezone_t ny = tzalloc("America/New_York");
1138+
ASSERT_NE(ny, nullptr);
1139+
1140+
// Set global timezone too (for consistency)
1141+
ASSERT_EQ(taosSetGlobalTimezone("America/New_York"), TSDB_CODE_SUCCESS);
1142+
1143+
// -- Prepare timestamps (millisecond precision, TSDB_TIME_PRECISION_MILLI=0) --
1144+
//
1145+
// Winter: 2024-01-15 15:30:00 UTC = 2024-01-15 10:30:00 EST
1146+
// epoch_ms = 1705329000000
1147+
// Correct day start = 2024-01-15 00:00:00 EST = 2024-01-15 05:00:00 UTC
1148+
// = 1705294800000 ms
1149+
//
1150+
// Summer: 2024-07-15 15:30:00 UTC = 2024-07-15 11:30:00 EDT
1151+
// epoch_ms = 1721057400000
1152+
// Correct day start = 2024-07-15 00:00:00 EDT = 2024-07-15 04:00:00 UTC
1153+
// = 1721016000000 ms
1154+
1155+
const int64_t winter_ts_ms = 1705329000000LL; // 2024-01-15 15:30:00 UTC
1156+
const int64_t winter_day_start_ms = 1705294800000LL; // 2024-01-15 05:00:00 UTC (midnight EST)
1157+
1158+
const int64_t summer_ts_ms = 1721057400000LL; // 2024-07-15 15:30:00 UTC
1159+
const int64_t summer_day_start_ms = 1721016000000LL; // 2024-07-15 04:00:00 UTC (midnight EDT)
1160+
1161+
// Verify expected timestamps with taosLocalTime
1162+
{
1163+
time_t t;
1164+
struct tm tm_val;
1165+
1166+
// Verify winter day start is indeed midnight EST
1167+
t = (time_t)(winter_day_start_ms / 1000);
1168+
ASSERT_NE(taosLocalTime(&t, &tm_val, NULL, 0, ny), nullptr);
1169+
ASSERT_EQ(tm_val.tm_hour, 0) << "winter day start should be midnight local";
1170+
ASSERT_EQ(tm_val.tm_min, 0);
1171+
ASSERT_EQ(tm_val.tm_sec, 0);
1172+
ASSERT_EQ(tm_val.tm_mon + 1, 1); // January
1173+
ASSERT_EQ(tm_val.tm_mday, 15);
1174+
1175+
// Verify summer day start is indeed midnight EDT
1176+
t = (time_t)(summer_day_start_ms / 1000);
1177+
ASSERT_NE(taosLocalTime(&t, &tm_val, NULL, 0, ny), nullptr);
1178+
ASSERT_EQ(tm_val.tm_hour, 0) << "summer day start should be midnight local";
1179+
ASSERT_EQ(tm_val.tm_min, 0);
1180+
ASSERT_EQ(tm_val.tm_sec, 0);
1181+
ASSERT_EQ(tm_val.tm_mon + 1, 7); // July
1182+
ASSERT_EQ(tm_val.tm_mday, 15);
1183+
}
1184+
1185+
// Build SInterval for INTERVAL(1d)
1186+
const int64_t one_day_ms = 86400LL * 1000;
1187+
SInterval interval = {};
1188+
interval.timezone = ny;
1189+
interval.intervalUnit = 'd';
1190+
interval.slidingUnit = 'd';
1191+
interval.offsetUnit = 0;
1192+
interval.precision = TSDB_TIME_PRECISION_MILLI;
1193+
interval.interval = one_day_ms;
1194+
interval.sliding = one_day_ms;
1195+
interval.offset = 0;
1196+
interval.timeRange.skey = INT64_MIN;
1197+
interval.timeRange.ekey = INT64_MAX;
1198+
1199+
// -- Test winter timestamp --
1200+
int64_t winter_result = taosTimeTruncate(winter_ts_ms, &interval);
1201+
1202+
// Convert result to local time to show what we got
1203+
{
1204+
time_t t = (time_t)(winter_result / 1000);
1205+
struct tm tm_val;
1206+
taosLocalTime(&t, &tm_val, NULL, 0, ny);
1207+
std::cout << "Winter ts truncated to: "
1208+
<< (1900 + tm_val.tm_year) << "-"
1209+
<< (tm_val.tm_mon + 1) << "-" << tm_val.tm_mday
1210+
<< " " << tm_val.tm_hour << ":" << tm_val.tm_min << ":" << tm_val.tm_sec
1211+
<< " (epoch_ms=" << winter_result << ")" << std::endl;
1212+
std::cout << "Expected: epoch_ms=" << winter_day_start_ms << std::endl;
1213+
}
1214+
1215+
EXPECT_EQ(winter_result, winter_day_start_ms)
1216+
<< "Winter day boundary should be midnight EST (05:00 UTC). "
1217+
"If this fails, taosTimeTruncate used the current DST offset "
1218+
"instead of the winter offset.";
1219+
1220+
// -- Test summer timestamp --
1221+
int64_t summer_result = taosTimeTruncate(summer_ts_ms, &interval);
1222+
1223+
{
1224+
time_t t = (time_t)(summer_result / 1000);
1225+
struct tm tm_val;
1226+
taosLocalTime(&t, &tm_val, NULL, 0, ny);
1227+
std::cout << "Summer ts truncated to: "
1228+
<< (1900 + tm_val.tm_year) << "-"
1229+
<< (tm_val.tm_mon + 1) << "-" << tm_val.tm_mday
1230+
<< " " << tm_val.tm_hour << ":" << tm_val.tm_min << ":" << tm_val.tm_sec
1231+
<< " (epoch_ms=" << summer_result << ")" << std::endl;
1232+
std::cout << "Expected: epoch_ms=" << summer_day_start_ms << std::endl;
1233+
}
1234+
1235+
EXPECT_EQ(summer_result, summer_day_start_ms)
1236+
<< "Summer day boundary should be midnight EDT (04:00 UTC). "
1237+
"If this fails, taosTimeTruncate used the current DST offset "
1238+
"instead of the summer offset.";
1239+
1240+
// After the fix, BOTH seasons should align correctly.
1241+
bool both_correct = (winter_result == winter_day_start_ms) &&
1242+
(summer_result == summer_day_start_ms);
1243+
ASSERT_TRUE(both_correct)
1244+
<< "Both winter and summer day boundaries should be correct "
1245+
"now that taosTimeTruncate uses per-timestamp DST resolution.";
1246+
1247+
tzfree(ny);
1248+
// TzRestoreGuard destructor handles taosSetGlobalTimezone("Asia/Shanghai")
1249+
}
1250+
#endif
1251+
11091252
#pragma GCC diagnostic pop

source/dnode/mgmt/mgmt_dnode/src/dmHandle.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ void dmSendStatusReq(SDnodeMgmt *pMgmt) {
196196

197197
req.clusterCfg.statusInterval = tsStatusInterval;
198198
req.clusterCfg.statusIntervalMs = tsStatusIntervalMs;
199-
req.clusterCfg.checkTime = 0;
200199
req.clusterCfg.ttlChangeOnWrite = tsTtlChangeOnWrite;
201200
req.clusterCfg.enableWhiteList = tsEnableWhiteList ? 1 : 0;
202201
req.clusterCfg.encryptionKeyStat = tsEncryptionKeyStat;
@@ -206,12 +205,14 @@ void dmSendStatusReq(SDnodeMgmt *pMgmt) {
206205
req.clusterCfg.monitorParas.tsSlowLogScope = tsSlowLogScope;
207206
req.clusterCfg.monitorParas.tsSlowLogMaxLen = tsSlowLogMaxLen;
208207
req.clusterCfg.monitorParas.tsSlowLogThreshold = tsSlowLogThreshold;
209-
tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN);
210-
char timestr[32] = "1970-01-01 00:00:00.00";
211-
if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, NULL) !=
212-
0) {
213-
dError("failed to parse time since %s", tstrerror(code));
208+
req.clusterCfg.checkTime = (int64_t)taosGetLocalTimezoneOffset();
209+
if (code != 0) {
210+
dError("failed to get local timezone offset, since %s", tstrerror(code));
211+
(void)taosThreadMutexUnlock(&pMgmt->pData->statusInfolock);
212+
return;
214213
}
214+
215+
tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN);
215216
memcpy(req.clusterCfg.timezone, tsTimezoneStr, TD_TIMEZONE_LEN);
216217
memcpy(req.clusterCfg.locale, tsLocale, TD_LOCALE_LEN);
217218
memcpy(req.clusterCfg.charset, tsCharset, TD_LOCALE_LEN);

source/dnode/mnode/impl/inc/mndInt.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ typedef struct SMnode {
126126
bool restored;
127127
bool deploy;
128128
char *path;
129-
int64_t checkTime;
130129
SyncIndex applied;
131130
SSdb *pSdb;
132131
SArray *pSteps;

0 commit comments

Comments
 (0)