Skip to content

Commit 7cd0a8c

Browse files
committed
fix: timezone handling in logging, so local timezone is always used
1 parent a2606fd commit 7cd0a8c

File tree

2 files changed

+42
-23
lines changed

2 files changed

+42
-23
lines changed

manual/english/Server_settings/Searchd.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,16 @@ expansion_phrase_warning = 1
507507

508508
This setting specifies whether timed grouping in API and SQL will be calculated in the local timezone or in UTC. It is optional, with a default value of 0 (meaning 'local timezone').
509509

510-
By default, all 'group by time' expressions (like group by day, week, month, and year in API, also group by day, month, year, yearmonth, yearmonthday in SQL) are done using local time. For example, if you have documents with attributes timed `13:00 utc` and `15:00 utc`, in the case of grouping, they both will fall into facility groups according to your local timezone setting. If you live in `utc`, it will be one day, but if you live in `utc+10`, then these documents will be matched into different `group by day` facility groups (since 13:00 utc in UTC+10 timezone is 23:00 local time, but 15:00 is 01:00 of the next day). Sometimes such behavior is unacceptable, and it is desirable to make time grouping not dependent on timezone. You can run the server with a defined global TZ environment variable, but it will affect not only grouping but also timestamping in the logs, which may be undesirable as well. Switching 'on' this option (either in config or using [SET global](../Server_settings/Setting_variables_online.md#SET) statement in SQL) will cause all time grouping expressions to be calculated in UTC, leaving the rest of time-depentend functions (i.e. logging of the server) in local TZ.
510+
By default, all 'group by time' expressions (like group by day, week, month, and year in API, also group by day, month, year, yearmonth, yearmonthday in SQL) are done using local time. For example, if you have documents with attributes timed `13:00 utc` and `15:00 utc`, in the case of grouping, they both will fall into facility groups according to your local timezone setting. If you live in `utc`, it will be one day, but if you live in `utc+10`, then these documents will be matched into different `group by day` facility groups (since 13:00 utc in UTC+10 timezone is 23:00 local time, but 15:00 is 01:00 of the next day). Sometimes such behavior is unacceptable, and it is desirable to make time grouping not dependent on timezone. You can run the server with a defined global TZ environment variable to affect time functions, but note that logging always uses the actual system local timezone (from `/etc/localtime`) and is not affected by TZ. Switching 'on' this option (either in config or using [SET global](../Server_settings/Setting_variables_online.md#SET) statement in SQL) will cause all time grouping expressions to be calculated in UTC, leaving the rest of time-dependent functions (i.e. logging of the server) in local TZ.
511511

512512

513513
### timezone
514514

515515
This setting specifies the timezone to be used by date/time-related functions. By default, the local timezone is used, but you can specify a different timezone in IANA format (e.g., `Europe/Amsterdam`).
516516

517-
Note that this setting has no impact on logging, which always operates in the local timezone.
517+
Note that this setting has no impact on logging, which always operates in the actual system local timezone (from `/etc/localtime`), regardless of this setting or the TZ environment variable.
518+
519+
The TZ environment variable works similarly to this setting: if set, it affects date/time-related functions (like `NOW()`, `CURTIME()`, etc.), but does not affect logging. Logging always uses the actual system local timezone to ensure timestamps reflect the real local time.
518520

519521
Also, note that if `grouping_in_utc` is used, the 'group by time' function will still use UTC, while other date/time-related functions will use the specified timezone. Overall, it is not recommended to mix `grouping_in_utc` and `timezone`.
520522

src/datetime.cpp

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,10 @@ static CSphString DetermineLocalTimeZoneName ( CSphString & sWarning )
3434
if ( szTZDefault )
3535
sTimeZoneFile = szTZDefault;
3636

37-
const char * szTZ = getenv("TZ");
38-
if ( szTZ )
39-
{
40-
sPrefix.SetSprintf ( "%s TZ='%s'", sPrefix.cstr(), szTZ );
41-
42-
if ( *szTZ==':' )
43-
++szTZ;
44-
45-
if ( *szTZ )
46-
sTimeZoneFile = szTZ;
47-
}
48-
else
49-
sPrefix.SetSprintf ( "%s '%s'", sPrefix.cstr(), sTimeZoneFile.cstr() );
37+
// Note: We intentionally ignore TZ environment variable here because
38+
// logging should always use the actual system local timezone, not TZ
39+
// TZ is meant for application-level timezone overrides, not system timezone
40+
sPrefix.SetSprintf ( "%s '%s'", sPrefix.cstr(), sTimeZoneFile.cstr() );
5041

5142
CSphString sTimeZoneDir = "/usr/share/zoneinfo/";
5243
const char * szTZDIR = getenv("TZDIR");
@@ -116,6 +107,9 @@ static void SetTimeZoneLocal ( StrVec_t & dWarnings )
116107
CSphString sDirName;
117108
sDirName.SetSprintf ( "%s/tzdata", GET_FULL_SHARE_DIR() );
118109

110+
// Save TZ env var before we potentially unset it
111+
const char * szTZ = getenv("TZ");
112+
119113
#if _WIN32
120114
_putenv_s ( "TZDIR", sDirName.cstr() );
121115

@@ -129,17 +123,40 @@ static void SetTimeZoneLocal ( StrVec_t & dWarnings )
129123

130124
sZone = FixupZoneName(sZone);
131125

132-
setenv ( "TZDIR", sDirName.cstr(), 1 );
126+
// Always use cctz::local_time_zone() to get the actual system local timezone
127+
// Temporarily unset TZ to ensure we get the actual system local timezone, not TZ
128+
// This ensures logging always uses the real local timezone regardless of TZ env var
129+
if ( szTZ )
130+
unsetenv("TZ");
131+
g_hTimeZoneLocal = cctz::local_time_zone();
132+
if ( szTZ )
133+
setenv("TZ", szTZ, 1);
134+
#endif
133135

134-
if ( !cctz::load_time_zone ( sZone.cstr(), &g_hTimeZoneLocal ) )
136+
// For time functions: use TZ if set, otherwise use local timezone
137+
// For logging: always use local timezone (g_hTimeZoneLocal)
138+
setenv ( "TZDIR", sDirName.cstr(), 1 );
139+
if ( szTZ && *szTZ )
135140
{
136-
sWarning.SetSprintf ( "Unable to load local time zone '%s' from '%s' dir", sZone.cstr(), sDirName.cstr() );
137-
dWarnings.Add(sWarning);
138-
g_hTimeZoneLocal = g_hTimeZoneUTC;
141+
// Skip leading colon if present
142+
const char * szTZName = szTZ;
143+
if ( *szTZName == ':' )
144+
++szTZName;
145+
if ( *szTZName && cctz::load_time_zone ( szTZName, &g_hTimeZone ) )
146+
{
147+
// Successfully loaded TZ timezone for time functions
148+
}
149+
else
150+
{
151+
// TZ set but invalid, fall back to local timezone
152+
g_hTimeZone = g_hTimeZoneLocal;
153+
}
154+
}
155+
else
156+
{
157+
// No TZ set, use local timezone for time functions
158+
g_hTimeZone = g_hTimeZoneLocal;
139159
}
140-
#endif
141-
142-
g_hTimeZone = g_hTimeZoneLocal;
143160
CheckForUTC();
144161
}
145162

0 commit comments

Comments
 (0)