749
geo_name[0] = '\0';
750
}
751
752
-static int map_windows_tz_to_ioanna(char *out, char *win_id, char *geo_name) {
752
+static int map_windows_tz_to_iana(char *out, char *win_id, char *geo_name) {
753
if (*win_id == '\0')
754
return -1;
755
758
return -1;
759
760
char buffer[CONFIG_FILE_LINE_MAX + 1];
761
+ char win_id_match[512];
762
bool copied = 0;
763
+
764
+ snprintfz(win_id_match, sizeof(win_id_match), "\"%s\"", win_id);
765
+
766
while (fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
767
buffer[CONFIG_FILE_LINE_MAX] = '\0';
768
765
- char *s = strstr(buffer, win_id);
769
+ char *s = strstr(buffer, win_id_match);
770
if (!s) {
771
if (!copied)
772
continue;
797
}
798
#endif
799
796
-void get_system_timezone(void)
797
-{
798
- char buffer[FILENAME_MAX + 1] = "";
800
+// Detect the current IANA timezone name from the system.
801
+// Returns a pointer into the provided buffer, or NULL if detection fails.
802
+const char *detect_system_timezone_name(char *buffer, size_t buffer_size) {
803
const char *timezone = NULL;
800
- const char *tz = NULL;
804
+
805
#ifdef OS_WINDOWS
806
char geo_name[128];
807
char win_zone[256];
808
get_timezone_win_id(win_zone, 256);
809
get_win_geoiso(geo_name, 128);
806
- if (!map_windows_tz_to_ioanna(buffer, win_zone, geo_name))
810
+ if (!map_windows_tz_to_iana(buffer, win_zone, geo_name))
811
timezone = buffer;
812
#else
809
- // avoid flood calls to stat(/etc/localtime)
810
- // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
811
- tz = getenv("TZ");
812
- if (!tz || !*tz)
813
- setenv("TZ", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "TZ", ":/etc/localtime"), 0);
814
-#endif
813
+ // read the /etc/localtime symlink first — this is the authoritative source
814
+ // on modern Linux (timedatectl always updates it, but /etc/timezone can lag)
815
+ {
816
+ ssize_t ret = readlink("/etc/localtime", buffer, buffer_size - 1);
817
+ if (ret > 0) {
818
+ buffer[ret] = '\0';
819
816
- ssize_t ret;
820
+ const char *cmp = "/usr/share/zoneinfo/";
821
+ size_t cmp_len = strlen(cmp);
822
818
- // use the TZ variable
819
- if (tz && *tz && *tz != ':') {
820
- timezone = tz;
821
- netdata_log_info("TIMEZONE: using TZ variable '%s'", timezone);
823
+ char *s = strstr(buffer, cmp);
824
+ if (s && s[cmp_len])
825
+ timezone = &s[cmp_len];
826
+ }
827
}
828
824
- // use the contents of /etc/timezone
825
- if (!timezone && !read_txt_file("/etc/timezone", buffer, sizeof(buffer))) {
829
+ // fall back to /etc/timezone (Debian/Ubuntu)
830
+ if (!timezone && !read_txt_file("/etc/timezone", buffer, buffer_size)) {
831
timezone = buffer;
827
- netdata_log_info("TIMEZONE: using the contents of /etc/timezone");
832
}
833
+#endif
834
830
- // read the link /etc/localtime
831
- if (!timezone) {
832
- ret = readlink("/etc/localtime", buffer, FILENAME_MAX);
835
+ if (timezone && *timezone) {
836
+ // sanitize in-place: keep only alnum, '_', '/', '-', '+'
837
+ char *d = buffer;
838
+ const char *src = timezone;
839
+ const char *end = buffer + buffer_size - 1;
840
+ while (*src && d < end) {
841
+ if (isalnum((uint8_t)*src) || *src == '_' || *src == '/' || *src == '-' || *src == '+')
842
+ *d++ = *src;
843
+ src++;
844
+ }
845
+ *d = '\0';
846
+ timezone = buffer;
847
+ }
848
834
- if (ret > 0) {
835
- buffer[ret] = '\0';
849
+ return (timezone && *timezone) ? timezone : NULL;
850
+}
851
837
- char *cmp = "/usr/share/zoneinfo/";
838
- size_t cmp_len = strlen(cmp);
852
+// Set at startup: true when the user explicitly set "timezone" in netdata.conf.
853
+static bool timezone_user_configured = false;
854
840
- char *s = strstr(buffer, cmp);
841
- if (s && s[cmp_len]) {
842
- timezone = &s[cmp_len];
843
- netdata_log_info("TIMEZONE: using the link of /etc/localtime: '%s'", timezone);
844
- }
845
- } else
846
- buffer[0] = '\0';
855
+// True when the timezone name came from a proper source (config,
856
+// /etc/localtime, /etc/timezone, TZ env var) rather than from the strftime("%Z")
857
+// fallback which only produces bare abbreviations like "CEST" or "PST".
858
+static bool timezone_is_tzdb_name = false;
859
+
860
+bool system_timezone_is_user_configured(void) {
861
+ return timezone_user_configured;
862
+}
863
+
864
+bool system_timezone_is_tzdb_name(void) {
865
+ return timezone_is_tzdb_name;
866
+}
867
+
868
+void get_system_timezone(void)
869
+{
870
+ char buffer[FILENAME_MAX + 1] = "";
871
+ const char *timezone = NULL;
872
+
873
+#ifndef OS_WINDOWS
874
+ // avoid flood calls to stat(/etc/localtime)
875
+ // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
876
+ const char *tz = getenv("TZ");
877
+ if (!tz || !*tz) {
878
+ setenv("TZ", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "TZ", ":/etc/localtime"), 1);
879
+ tz = getenv("TZ");
880
}
881
849
- // find the timezone from strftime()
882
+ // use the TZ variable if it's an explicit IANA name (not a path starting with ':')
883
+ if (tz && *tz && *tz != ':') {
884
+ timezone = tz;
885
+ timezone_is_tzdb_name = true;
886
+ netdata_log_info("TIMEZONE: using TZ variable '%s'", timezone);
887
+ }
888
+#endif
889
+
890
+ // Detect from system sources (/etc/localtime symlink, /etc/timezone, Windows API)
891
+ if (!timezone) {
892
+ timezone = detect_system_timezone_name(buffer, sizeof(buffer));
893
+ if (timezone) {
894
+ timezone_is_tzdb_name = true;
895
+ netdata_log_info("TIMEZONE: detected '%s'", timezone);
896
+ }
897
+ }
898
+
899
+ // Last resort: use strftime %Z (gives abbreviation, not IANA name).
900
+ // timezone_is_tzdb_name stays false so refresh_system_timezone() won't
901
+ // try to resolve it via tzalloc() or the tzfile parser.
902
if (!timezone) {
903
time_t t;
904
struct tm *tmp, tmbuf;
907
tmp = localtime_r(&t, &tmbuf);
908
909
if (tmp != NULL) {
858
- if (strftime(buffer, FILENAME_MAX, "%Z", tmp) == 0)
859
- buffer[0] = '\0';
860
- else {
910
+ if (strftime(buffer, FILENAME_MAX, "%Z", tmp) != 0) {
911
buffer[FILENAME_MAX] = '\0';
912
timezone = buffer;
913
netdata_log_info("TIMEZONE: using strftime(): '%s'", timezone);
915
}
916
}
917
868
- if (timezone && *timezone) {
869
- // make sure it does not have illegal characters
870
- // netdata_log_info("TIMEZONE: fixing '%s'", timezone);
918
+ if (!timezone || !*timezone)
919
+ timezone = "unknown";
920
872
- size_t len = strlen(timezone);
873
- char tmp[len + 1];
874
- char *d = tmp;
875
- *d = '\0';
921
+ // Check if the user explicitly set "timezone" in netdata.conf BEFORE
922
+ // inicfg_get auto-populates it with the detected default.
923
+ timezone_user_configured = inicfg_exists(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone");
924
+
925
+ // inicfg_get returns a config-system-owned pointer, stable for the process lifetime
926
+ const char *configured_tz = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", timezone);
927
+
928
+ // Treat "timezone =" (empty value) as not user-configured,
929
+ // and fall back to the auto-detected timezone.
930
+ if (timezone_user_configured && (!configured_tz || !*configured_tz)) {
931
+ timezone_user_configured = false;
932
+ configured_tz = timezone;
933
+ }
934
+
935
+ // If the user explicitly configured a timezone, treat it as a valid tzdb name
936
+ // (the user is responsible for providing a valid value).
937
+ if (timezone_user_configured)
938
+ timezone_is_tzdb_name = true;
939
+
940
+ // Compute abbreviation and UTC offset, then set all three atomically
941
+ refresh_system_timezone(configured_tz, timezone_is_tzdb_name);
942
+}
943
+
944
+static inline uint32_t tzif_read_be32(const unsigned char *src) {
945
+ return ((uint32_t)src[0] << 24) |
946
+ ((uint32_t)src[1] << 16) |
947
+ ((uint32_t)src[2] << 8) |
948
+ (uint32_t)src[3];
949
+}
950
+
951
+static inline int64_t tzif_read_be64(const unsigned char *src) {
952
+ return ((int64_t)(uint64_t)src[0] << 56) |
953
+ ((int64_t)(uint64_t)src[1] << 48) |
954
+ ((int64_t)(uint64_t)src[2] << 40) |
955
+ ((int64_t)(uint64_t)src[3] << 32) |
956
+ ((int64_t)(uint64_t)src[4] << 24) |
957
+ ((int64_t)(uint64_t)src[5] << 16) |
958
+ ((int64_t)(uint64_t)src[6] << 8) |
959
+ (int64_t)(uint64_t)src[7];
960
+}
961
+
962
+static bool timezone_name_is_safe_tzdb_path(const char *timezone) {
963
+ if (!timezone || !*timezone || *timezone == '/')
964
+ return false;
965
+
966
+ for (const char *p = timezone; *p; p++) {
967
+ if (!(isalnum((uint8_t)*p) || *p == '_' || *p == '/' || *p == '-' || *p == '+'))
968
+ return false;
969
+ }
970
+
971
+ return true;
972
+}
973
877
- while (*timezone) {
878
- if (isalnum((uint8_t)*timezone) || *timezone == '_' || *timezone == '/')
879
- *d++ = *timezone++;
880
- else
881
- timezone++;
974
+static bool timezone_info_from_tm(struct tm *tmp, char *abbrev, size_t abbrev_size, int32_t *offset) {
975
+ if (!tmp)
976
+ return false;
977
+
978
+ int32_t new_offset = 0;
979
+ char offset_str[16];
980
+
981
+ if (strftime(abbrev, abbrev_size, "%Z", tmp) == 0)
982
+ strncpyz(abbrev, "UTC", abbrev_size - 1);
983
+
984
+ if (strftime(offset_str, sizeof(offset_str), "%z", tmp) != 0) {
985
+ char sign = offset_str[0] == '-' || offset_str[0] == '+' ? offset_str[0] : '+';
986
+ int hours = (isdigit((uint8_t)offset_str[1]) ? (offset_str[1] - '0') : 0) * 10 +
987
+ (isdigit((uint8_t)offset_str[2]) ? (offset_str[2] - '0') : 0);
988
+ int minutes = (isdigit((uint8_t)offset_str[3]) ? (offset_str[3] - '0') : 0) * 10 +
989
+ (isdigit((uint8_t)offset_str[4]) ? (offset_str[4] - '0') : 0);
990
+
991
+ new_offset = (hours * 3600) + (minutes * 60);
992
+ if (sign == '-')
993
+ new_offset = -new_offset;
994
+ }
995
+
996
+ *offset = new_offset;
997
+ return true;
998
+}
999
+
1000
+static bool current_process_timezone_info(time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) {
1001
+ struct tm *tmp, tmbuf;
1002
+ tmp = localtime_r(&t, &tmbuf);
1003
+ return timezone_info_from_tm(tmp, abbrev, abbrev_size, offset);
1004
+}
1005
+
1006
+#ifndef OS_WINDOWS
1007
+struct tzif_header {
1008
+ char magic[4];
1009
+ char version;
1010
+ char reserved[15];
1011
+ unsigned char ttisgmtcnt[4];
1012
+ unsigned char ttisstdcnt[4];
1013
+ unsigned char leapcnt[4];
1014
+ unsigned char timecnt[4];
1015
+ unsigned char typecnt[4];
1016
+ unsigned char charcnt[4];
1017
+};
1018
+
1019
+struct tzif_type {
1020
+ int32_t gmtoff;
1021
+ uint8_t isdst;
1022
+ uint8_t abbrind;
1023
+};
1024
+
1025
+static bool tzif_skip_bytes(FILE *fp, size_t bytes) {
1026
+ char buffer[256];
1027
+ while (bytes) {
1028
+ size_t chunk = bytes > sizeof(buffer) ? sizeof(buffer) : bytes;
1029
+ if (fread(buffer, 1, chunk, fp) != chunk)
1030
+ return false;
1031
+ bytes -= chunk;
1032
+ }
1033
+ return true;
1034
+}
1035
+
1036
+// RFC 8536 sane upper bounds — real tzfiles are far smaller,
1037
+// but we allow headroom for unusual / future data.
1038
+#define TZIF_MAX_TIMECNT 2048
1039
+#define TZIF_MAX_TYPECNT 256
1040
+#define TZIF_MAX_CHARCNT 2048
1041
+#define TZIF_MAX_LEAPCNT 50
1042
+#define TZIF_MAX_ISSTDCNT 256
1043
+#define TZIF_MAX_ISGMTCNT 256
1044
+
1045
+static bool tzif_validate_header_counts(uint32_t timecnt, uint32_t typecnt, uint32_t charcnt,
1046
+ uint32_t leapcnt, uint32_t ttisstdcnt, uint32_t ttisgmtcnt) {
1047
+ if (timecnt > TZIF_MAX_TIMECNT || typecnt > TZIF_MAX_TYPECNT ||
1048
+ charcnt > TZIF_MAX_CHARCNT || leapcnt > TZIF_MAX_LEAPCNT ||
1049
+ ttisstdcnt > TZIF_MAX_ISSTDCNT || ttisgmtcnt > TZIF_MAX_ISGMTCNT)
1050
+ return false;
1051
+
1052
+ // ttisstdcnt and ttisgmtcnt must be 0 or equal to typecnt per RFC 8536
1053
+ if ((ttisstdcnt != 0 && ttisstdcnt != typecnt) ||
1054
+ (ttisgmtcnt != 0 && ttisgmtcnt != typecnt))
1055
+ return false;
1056
+
1057
+ return true;
1058
+}
1059
+
1060
+static bool tzif_skip_block(FILE *fp, const struct tzif_header *hdr, size_t time_size) {
1061
+ uint32_t ttisgmtcnt = tzif_read_be32(hdr->ttisgmtcnt);
1062
+ uint32_t ttisstdcnt = tzif_read_be32(hdr->ttisstdcnt);
1063
+ uint32_t leapcnt = tzif_read_be32(hdr->leapcnt);
1064
+ uint32_t timecnt = tzif_read_be32(hdr->timecnt);
1065
+ uint32_t typecnt = tzif_read_be32(hdr->typecnt);
1066
+ uint32_t charcnt = tzif_read_be32(hdr->charcnt);
1067
+
1068
+ if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt))
1069
+ return false;
1070
+
1071
+ size_t bytes = (size_t)timecnt * time_size +
1072
+ (size_t)timecnt +
1073
+ (size_t)typecnt * 6 +
1074
+ (size_t)charcnt +
1075
+ (size_t)leapcnt * (time_size + 4) +
1076
+ (size_t)ttisstdcnt +
1077
+ (size_t)ttisgmtcnt;
1078
+
1079
+ return tzif_skip_bytes(fp, bytes);
1080
+}
1081
+
1082
+static int tzif_default_type_index(const struct tzif_type *types, uint32_t typecnt) {
1083
+ if (!types || !typecnt)
1084
+ return -1;
1085
+
1086
+ for (uint32_t i = 0; i < typecnt; i++) {
1087
+ if (!types[i].isdst)
1088
+ return (int)i;
1089
+ }
1090
+
1091
+ return 0;
1092
+}
1093
+
1094
+static bool timezone_info_from_tzfile(const char *timezone, time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) {
1095
+ if (!timezone_name_is_safe_tzdb_path(timezone))
1096
+ return false;
1097
+
1098
+ const char *tzdir = getenv("TZDIR");
1099
+ if (!tzdir || !*tzdir)
1100
+ tzdir = "/usr/share/zoneinfo";
1101
+
1102
+ char path[FILENAME_MAX + 1];
1103
+ snprintfz(path, sizeof(path), "%s/%s", tzdir, timezone);
1104
+
1105
+ FILE *fp = fopen(path, "rb");
1106
+ if (!fp)
1107
+ return false;
1108
+
1109
+ bool ok = false;
1110
+ struct tzif_header hdr;
1111
+
1112
+ if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr))
1113
+ goto cleanup;
1114
+
1115
+ if (memcmp(hdr.magic, "TZif", 4) != 0)
1116
+ goto cleanup;
1117
+
1118
+ if (hdr.version >= '2') {
1119
+ if (!tzif_skip_block(fp, &hdr, 4))
1120
+ goto cleanup;
1121
+
1122
+ if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr))
1123
+ goto cleanup;
1124
+
1125
+ if (memcmp(hdr.magic, "TZif", 4) != 0)
1126
+ goto cleanup;
1127
+ }
1128
+
1129
+ uint32_t timecnt = tzif_read_be32(hdr.timecnt);
1130
+ uint32_t typecnt = tzif_read_be32(hdr.typecnt);
1131
+ uint32_t charcnt = tzif_read_be32(hdr.charcnt);
1132
+ uint32_t leapcnt = tzif_read_be32(hdr.leapcnt);
1133
+ uint32_t ttisstdcnt = tzif_read_be32(hdr.ttisstdcnt);
1134
+ uint32_t ttisgmtcnt = tzif_read_be32(hdr.ttisgmtcnt);
1135
+ size_t time_size = (hdr.version >= '2') ? 8 : 4;
1136
+
1137
+ if (!typecnt || !charcnt)
1138
+ goto cleanup;
1139
+
1140
+ if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt))
1141
+ goto cleanup;
1142
+
1143
+ int64_t *transition_times = callocz(timecnt ? timecnt : 1, sizeof(*transition_times));
1144
+ uint8_t *transition_types = callocz(timecnt ? timecnt : 1, sizeof(*transition_types));
1145
+ struct tzif_type *types = callocz(typecnt, sizeof(*types));
1146
+ char *abbrs = callocz(charcnt + 1, sizeof(*abbrs));
1147
+
1148
+ unsigned char timebuf[8];
1149
+ unsigned char typebuf[6];
1150
+
1151
+ for (uint32_t i = 0; i < timecnt; i++) {
1152
+ if (fread(timebuf, 1, time_size, fp) != time_size)
1153
+ goto free_and_cleanup;
1154
+ transition_times[i] = (time_size == 8) ? tzif_read_be64(timebuf) : (int32_t)tzif_read_be32(timebuf);
1155
+ }
1156
+
1157
+ if (timecnt && fread(transition_types, 1, timecnt, fp) != timecnt)
1158
+ goto free_and_cleanup;
1159
+
1160
+ // validate all transition type indices before using them
1161
+ for (uint32_t i = 0; i < timecnt; i++) {
1162
+ if (transition_types[i] >= typecnt)
1163
+ goto free_and_cleanup;
1164
+ }
1165
+
1166
+ for (uint32_t i = 0; i < typecnt; i++) {
1167
+ if (fread(typebuf, 1, sizeof(typebuf), fp) != sizeof(typebuf))
1168
+ goto free_and_cleanup;
1169
+
1170
+ types[i].gmtoff = (int32_t)tzif_read_be32(typebuf);
1171
+ types[i].isdst = typebuf[4];
1172
+ types[i].abbrind = typebuf[5];
1173
+ }
1174
+
1175
+ if (fread(abbrs, 1, charcnt, fp) != charcnt)
1176
+ goto free_and_cleanup;
1177
+ abbrs[charcnt] = '\0';
1178
+
1179
+ int type_index = -1;
1180
+ if (timecnt == 0) {
1181
+ type_index = tzif_default_type_index(types, typecnt);
1182
+ } else {
1183
+ for (uint32_t i = 0; i < timecnt; i++) {
1184
+ if ((int64_t)t < transition_times[i])
1185
+ break;
1186
+ type_index = transition_types[i];
1187
}
883
- *d = '\0';
884
- strncpyz(buffer, tmp, len);
885
- timezone = buffer;
886
- netdata_log_info("TIMEZONE: fixed as '%s'", timezone);
1188
+
1189
+ if (type_index < 0)
1190
+ type_index = tzif_default_type_index(types, typecnt);
1191
}
1192
889
- if (!timezone || !*timezone)
890
- timezone = "unknown";
1193
+ if (type_index < 0 || (uint32_t)type_index >= typecnt)
1194
+ goto free_and_cleanup;
1195
892
- netdata_configured_timezone = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", timezone);
1196
+ if (types[type_index].abbrind >= charcnt)
1197
+ goto free_and_cleanup;
1198
894
- //get the utc offset, and the timezone as returned by strftime
895
- //will be sent to the cloud
896
- //Note: This will need an agent restart to get new offset on time change (dst, etc).
897
- {
898
- time_t t;
899
- struct tm *tmp, tmbuf;
900
- char zone[FILENAME_MAX + 1];
901
- char sign[2], hh[3], mm[3];
1199
+ const char *tz_abbrev = &abbrs[types[type_index].abbrind];
1200
+ if (!*tz_abbrev)
1201
+ tz_abbrev = "UTC";
1202
903
- t = now_realtime_sec();
904
- tmp = localtime_r(&t, &tmbuf);
1203
+ strncpyz(abbrev, tz_abbrev, abbrev_size - 1);
1204
+ *offset = types[type_index].gmtoff;
1205
+ ok = true;
1206
906
- if (tmp != NULL) {
907
- if (strftime(zone, FILENAME_MAX, "%Z", tmp) == 0) {
908
- netdata_configured_abbrev_timezone = strdupz("UTC");
909
- } else
910
- netdata_configured_abbrev_timezone = strdupz(zone);
911
-
912
- if (strftime(zone, FILENAME_MAX, "%z", tmp) == 0) {
913
- netdata_configured_utc_offset = 0;
914
- } else {
915
- sign[0] = zone[0] == '-' || zone[0] == '+' ? zone[0] : '0';
916
- sign[1] = '\0';
917
- hh[0] = isdigit((uint8_t)zone[1]) ? zone[1] : '0';
918
- hh[1] = isdigit((uint8_t)zone[2]) ? zone[2] : '0';
919
- hh[2] = '\0';
920
- mm[0] = isdigit((uint8_t)zone[3]) ? zone[3] : '0';
921
- mm[1] = isdigit((uint8_t)zone[4]) ? zone[4] : '0';
922
- mm[2] = '\0';
923
-
924
- netdata_configured_utc_offset = (str2i(hh) * 3600) + (str2i(mm) * 60);
925
- netdata_configured_utc_offset =
926
- sign[0] == '-' ? -netdata_configured_utc_offset : netdata_configured_utc_offset;
1207
+free_and_cleanup:
1208
+ freez(abbrs);
1209
+ freez(types);
1210
+ freez(transition_types);
1211
+ freez(transition_times);
1212
+
1213
+cleanup:
1214
+ fclose(fp);
1215
+ return ok;
1216
+}
1217
+#endif
1218
+
1219
+void refresh_system_timezone(const char *timezone, bool is_tzdb_name) {
1220
+ time_t t;
1221
+ char abbrev[64];
1222
+ const char *new_abbrev = "UTC";
1223
+ int32_t new_offset = 0;
1224
+
1225
+ // Update global flag when we have confirmed tzdb knowledge.
1226
+ // When is_tzdb_name is false, preserve the existing global flag — a prior
1227
+ // successful detection may have already promoted it to true.
1228
+ if (is_tzdb_name)
1229
+ timezone_is_tzdb_name = true;
1230
+
1231
+ t = now_realtime_sec();
1232
+ bool ok = false;
1233
+
1234
+#if defined(HAVE_TZALLOC) && defined(HAVE_LOCALTIME_RZ) && defined(HAVE_TZFREE)
1235
+ if (is_tzdb_name) {
1236
+ timezone_t tz = tzalloc(timezone);
1237
+ if (tz) {
1238
+ struct tm *tmp, tmbuf;
1239
+ tmp = localtime_rz(tz, &t, &tmbuf);
1240
+ ok = timezone_info_from_tm(tmp, abbrev, sizeof(abbrev), &new_offset);
1241
+ tzfree(tz);
1242
+ }
1243
+ }
1244
+#elif !defined(OS_WINDOWS)
1245
+ if (is_tzdb_name)
1246
+ ok = timezone_info_from_tzfile(timezone, t, abbrev, sizeof(abbrev), &new_offset);
1247
+#endif
1248
+
1249
+ if (!ok)
1250
+ ok = current_process_timezone_info(t, abbrev, sizeof(abbrev), &new_offset);
1251
+
1252
+ if (ok && *abbrev)
1253
+ new_abbrev = abbrev;
1254
+
1255
+ // Atomically update the system timezone triplet
1256
+ system_tz_set(timezone, new_abbrev, new_offset);
1257
+
1258
+ // Update localhost if it exists
1259
+ if (localhost) {
1260
+ if (rrdhost_update_timezone(localhost, timezone, new_abbrev, new_offset)) {
1261
+ // Timezone changed — update the two labels directly, persist, and notify.
1262
+ if (localhost->rrdlabels) {
1263
+ rrdlabels_add(localhost->rrdlabels, "_timezone", timezone, RRDLABEL_SRC_AUTO);
1264
+ rrdlabels_add(localhost->rrdlabels, "_abbrev_timezone", new_abbrev, RRDLABEL_SRC_AUTO);
1265
+ rrdhost_flag_set(localhost, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
1266
+ stream_send_host_labels(localhost);
1267
}
928
- } else {
929
- netdata_configured_abbrev_timezone = strdupz("UTC");
930
- netdata_configured_utc_offset = 0;
1268
+ aclk_queue_node_info(localhost, false);
1269
}
1270
}
1271
}