Skip to content

Commit a86f629

Browse files
authored
refactor(timezone): checkTime in dnode and mnode implementations and timetruncate logic for interval (main branch) (#35057)
1 parent d10efc9 commit a86f629

7 files changed

Lines changed: 320 additions & 58 deletions

File tree

include/common/tmsg.h

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

27112711
typedef struct {
27122712
int32_t statusInterval;
2713-
int64_t checkTime; // 1970-01-01 00:00:00.000
2713+
/*
2714+
Local timezone UTC offset in seconds (east-positive, e.g. +28800 for
2715+
Asia/Shanghai). Derived from taosGetLocalTimezoneOffset() on each
2716+
status report. Paired with the timezone string in
2717+
mndCheckClusterCfgPara: a mismatch is reported only when both the
2718+
timezone string AND this offset differ.
2719+
*/
2720+
int64_t checkTime;
27142721
char timezone[TD_TIMEZONE_LEN]; // tsTimezone
27152722
char locale[TD_LOCALE_LEN]; // tsLocale
27162723
char charset[TD_LOCALE_LEN]; // tsCharset

source/common/src/ttime.c

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,33 @@ int64_t taosTimeGetIntervalEnd(int64_t intervalStart, const SInterval* pInterval
986986
1;
987987
}
988988

989+
/*
990+
getTZOffsetAtTicks - return the east-positive UTC offset (in ticks) that is
991+
in effect at the given timestamp.
992+
993+
Unlike taosGetTZOffsetSeconds() which queries the offset for "now", this
994+
function converts `ticks` to local time via taosLocalTime() and then
995+
derives the offset as (taosTimeGm(local) - t_sec), so it correctly
996+
resolves DST for the *target* instant.
997+
998+
On conversion failure 0 is returned (UTC fallback).
999+
*/
1000+
static int64_t getTZOffsetAtTicks(int64_t ticks, int32_t precision, timezone_t tz) {
1001+
int64_t factor = TSDB_TICK_PER_SECOND(precision);
1002+
int64_t t_sec_ticks = ticks / factor;
1003+
if (ticks < 0 && ticks % factor != 0) {
1004+
t_sec_ticks -= 1;
1005+
}
1006+
time_t t_sec = (time_t)t_sec_ticks;
1007+
struct tm tm_local;
1008+
if (taosLocalTime(&t_sec, &tm_local, NULL, 0, tz) == NULL) {
1009+
uWarn("%s failed to convert ticks:%" PRId64 " to local time, code:%d",
1010+
__FUNCTION__, ticks, ERRNO);
1011+
return 0;
1012+
}
1013+
return (int64_t)(taosTimeGm(&tm_local) - t_sec) * factor;
1014+
}
1015+
9891016
int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
9901017
if (ts <= INT64_MIN || ts >= INT64_MAX) {
9911018
return ts;
@@ -1030,20 +1057,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
10301057
if (IS_CALENDAR_TIME_DURATION(pInterval->intervalUnit)) {
10311058
int64_t news = (ts / pInterval->sliding) * pInterval->sliding;
10321059
if (pInterval->slidingUnit == 'd' || pInterval->slidingUnit == 'w') {
1033-
// taosGet*TimezoneOffset() returns east-positive (tm_gmtoff) values.
1034-
// The day/week anchor logic here expects west-positive offsets, so
1035-
// shift by subtracting the east-positive offset.
1036-
int64_t tz_offset = 0;
1037-
if (pInterval->timezone != NULL) {
1038-
// taosGetTZOffsetSeconds() returns east-positive for any timezone_t on all platforms.
1039-
int32_t code = 0;
1040-
tz_offset = taosGetTZOffsetSeconds(pInterval->timezone, &code);
1041-
} else {
1042-
// Use global configured timezone (also east-positive on all platforms).
1043-
int32_t code = 0;
1044-
tz_offset = taosGetLocalTimezoneOffset(&code);
1045-
}
1046-
news -= (int64_t)(tz_offset * TSDB_TICK_PER_SECOND(precision));
1060+
news -= getTZOffsetAtTicks(news, precision, pInterval->timezone);
10471061
}
10481062

10491063
start = news;
@@ -1073,27 +1087,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
10731087
start = (delta / pInterval->sliding) * pInterval->sliding;
10741088

10751089
if (pInterval->intervalUnit == 'd' || pInterval->intervalUnit == 'w') {
1076-
/*
1077-
* here we revised the start time of day according to the local time zone,
1078-
* but in case of DST, the start time of one day need to be dynamically decided.
1079-
*
1080-
* taosGet*TimezoneOffset() returns east-positive (tm_gmtoff) values.
1081-
* The day/week anchor logic here expects west-positive offsets, so
1082-
* shift by subtracting the east-positive offset.
1083-
*/
1084-
// Get timezone offset from pInterval->timezone or global config.
1085-
int64_t tz_offset = 0;
1086-
if (pInterval->timezone != NULL) {
1087-
// taosGetTZOffsetSeconds() returns east-positive for any timezone_t on all platforms.
1088-
int32_t code = 0;
1089-
tz_offset = taosGetTZOffsetSeconds(pInterval->timezone, &code);
1090-
} else {
1091-
// Use global configured timezone (also east-positive on all platforms).
1092-
int32_t code = 0;
1093-
tz_offset = taosGetLocalTimezoneOffset(&code);
1094-
}
1095-
1096-
start -= (int64_t)(tz_offset * TSDB_TICK_PER_SECOND(precision));
1090+
start -= getTZOffsetAtTicks(start, precision, pInterval->timezone);
10971091
}
10981092

10991093
int64_t end = 0;

source/common/test/commonTests.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,4 +1113,147 @@ TEST(testCase, function_taosTimeTruncate) {
11131113
ASSERT_LE(res, 1633450000000);
11141114
}
11151115

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

274274
req.clusterCfg.statusInterval = tsStatusInterval;
275275
req.clusterCfg.statusIntervalMs = tsStatusIntervalMs;
276-
req.clusterCfg.checkTime = 0;
277276
req.clusterCfg.ttlChangeOnWrite = tsTtlChangeOnWrite;
278277
req.clusterCfg.enableWhiteList = tsEnableWhiteList ? 1 : 0;
279278
req.clusterCfg.encryptionKeyStat = tsEncryptionKeyStat;
@@ -283,12 +282,14 @@ void dmSendStatusReq(SDnodeMgmt *pMgmt) {
283282
req.clusterCfg.monitorParas.tsSlowLogScope = tsSlowLogScope;
284283
req.clusterCfg.monitorParas.tsSlowLogMaxLen = tsSlowLogMaxLen;
285284
req.clusterCfg.monitorParas.tsSlowLogThreshold = tsSlowLogThreshold;
286-
tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN);
287-
char timestr[32] = "1970-01-01 00:00:00.00";
288-
if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, NULL) !=
289-
0) {
290-
dError("failed to parse time since %s", tstrerror(code));
285+
req.clusterCfg.checkTime = (int64_t)taosGetLocalTimezoneOffset(&code);
286+
if (code != 0) {
287+
dError("failed to get local timezone offset, since %s", tstrerror(code));
288+
(void)taosThreadMutexUnlock(&pMgmt->pData->statusInfolock);
289+
return;
291290
}
291+
292+
tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN);
292293
memcpy(req.clusterCfg.timezone, tsTimezoneStr, TD_TIMEZONE_LEN);
293294
memcpy(req.clusterCfg.locale, tsLocale, TD_LOCALE_LEN);
294295
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)