@@ -526,6 +526,7 @@ static const char *getoffset(const char *str, long *offset) {
526
526
}
527
527
528
528
enum RuleType {
529
+ TZFILE, // mlibc-internal rule type for TZ files
529
530
JULIAN_DAY, // Jn = Julian day
530
531
DAY_OF_YEAR, // n = day of year
531
532
MONTH_NTH_DAY_OF_WEEK, // Mm.n.d = month, week, day of week
@@ -762,7 +763,7 @@ struct tzfile {
762
763
uint32_t tzh_charcnt;
763
764
};
764
765
765
- static bool parse_tzfile (const char *tz) {
766
+ frg::string<MemoryAllocator> parse_tzfile_path (const char *tz) {
766
767
// POSIX defines :*characters* as a valid but implementation-defined format.
767
768
// This was originally introduced as a way to support geographical
768
769
// timezones in the format :Area/Location, but the colon was dropped in POSIX.
@@ -794,13 +795,19 @@ static bool parse_tzfile(const char *tz) {
794
795
path += tz;
795
796
}
796
797
798
+ return path;
799
+ }
800
+
801
+ static bool parse_tzfile (const char *tz) {
802
+ frg::string<MemoryAllocator> path = parse_tzfile_path (tz);
803
+
797
804
// Check if file exists, otherwise fallback to the default.
798
805
if (!mlibc::sys_stat) {
799
806
MLIBC_MISSING_SYSDEP ();
800
807
__ensure (!" cannot proceed without sys_stat" );
801
808
}
802
809
struct stat info;
803
- if (mlibc::sys_stat (mlibc::fsfd_target::path, -1 , path.data (), 0 , &info))
810
+ if (mlibc::sys_stat (mlibc::fsfd_target::path, -1 , path.data (), 0 , &info))
804
811
return true ;
805
812
806
813
// FIXME: Make this fallible so the above check is not needed.
@@ -816,13 +823,13 @@ static bool parse_tzfile(const char *tz) {
816
823
tzfile_time.tzh_typecnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_typecnt );
817
824
tzfile_time.tzh_charcnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_charcnt );
818
825
819
- if (tzfile_time.magic [0 ] != ' T' || tzfile_time.magic [1 ] != ' Z' || tzfile_time.magic [2 ] != ' i'
826
+ if (tzfile_time.magic [0 ] != ' T' || tzfile_time.magic [1 ] != ' Z' || tzfile_time.magic [2 ] != ' i'
820
827
|| tzfile_time.magic [3 ] != ' f' ) {
821
828
mlibc::infoLogger () << " mlibc: " << path << " is not a valid TZinfo file" << frg::endlog;
822
829
return true ;
823
830
}
824
831
825
- if (tzfile_time.version != ' \0 ' && tzfile_time.version != ' 2' && tzfile_time.version != ' 3' ) {
832
+ if (tzfile_time.version != ' \0 ' && tzfile_time.version != ' 2' && tzfile_time.version != ' 3' ) {
826
833
mlibc::infoLogger () << " mlibc: " << path << " has an invalid TZinfo version"
827
834
<< frg::endlog;
828
835
return true ;
@@ -836,6 +843,8 @@ static bool parse_tzfile(const char *tz) {
836
843
+ tzfile_time.tzh_timecnt * sizeof (int32_t )
837
844
+ tzfile_time.tzh_timecnt * sizeof (uint8_t )
838
845
+ tzfile_time.tzh_typecnt * sizeof (struct ttinfo );
846
+ bool found_std = false ;
847
+ bool found_dst = false ;
839
848
// start from the last ttinfo entry, this matches the behaviour of glibc and musl
840
849
for (int i = tzfile_time.tzh_typecnt ; i > 0 ; i--) {
841
850
ttinfo time_info;
@@ -844,17 +853,24 @@ static bool parse_tzfile(const char *tz) {
844
853
+ tzfile_time.tzh_timecnt * sizeof (uint8_t )
845
854
+ i * sizeof (ttinfo), sizeof (ttinfo));
846
855
time_info.tt_gmtoff = mlibc::bit_util<uint32_t >::byteswap (time_info.tt_gmtoff );
847
- if (!time_info.tt_isdst && !tzname[ 0 ] ) {
856
+ if (!time_info.tt_isdst && !found_std ) {
848
857
tzname[0 ] = abbrevs + time_info.tt_abbrind ;
849
858
timezone = -time_info.tt_gmtoff ;
859
+ found_std = true ;
850
860
}
851
- if (time_info.tt_isdst && !tzname[ 1 ] ) {
861
+ if (time_info.tt_isdst && !found_dst ) {
852
862
tzname[1 ] = abbrevs + time_info.tt_abbrind ;
853
863
timezone = -time_info.tt_gmtoff ;
854
864
daylight = 1 ;
865
+ found_dst = true ;
855
866
}
867
+ if (found_std && found_dst)
868
+ break ;
856
869
}
857
870
871
+ rules[0 ].type = TZFILE;
872
+ rules[1 ].type = TZFILE;
873
+
858
874
return false ;
859
875
}
860
876
@@ -1003,21 +1019,191 @@ void yearday_from_date(unsigned int year, unsigned int month, unsigned int day,
1003
1019
*yday = n1 - (n2 * n3) + day - 30 ;
1004
1020
}
1005
1021
1022
+ static bool is_leap_year (int year) {
1023
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0 ));
1024
+ }
1025
+
1026
+ // Given a rule and a year, compute the time of the transition in seconds since the epoch.
1027
+ // TODO: Take into account the time of day when the transition occurs
1028
+ time_t time_from_rule (const Rule &rule, int year) {
1029
+ if (rule.type == JULIAN_DAY) {
1030
+ // Jn: Julian day, ignoring Feb 29
1031
+ uint16_t day = rule.day - 1 ;
1032
+ if (is_leap_year (year) && day >= 60 )
1033
+ day = rule.day ;
1034
+
1035
+ struct tm t = {};
1036
+ t.tm_year = year - 1900 ;
1037
+ t.tm_yday = day;
1038
+ return mktime (&t);
1039
+ } else if (rule.type == DAY_OF_YEAR) {
1040
+ // n: zero-based day of year, including Feb 29 in leap years
1041
+ struct tm t = {};
1042
+ t.tm_year = year - 1900 ;
1043
+ t.tm_yday = rule.day ;
1044
+ return mktime (&t);
1045
+ } else if (rule.type == MONTH_NTH_DAY_OF_WEEK) {
1046
+ // Mm.n.d: Month, week, weekday (month 1-12, week 1-5, weekday 0=Sun)
1047
+
1048
+ // Find the first day of the month
1049
+ struct tm t = {};
1050
+ t.tm_year = year - 1900 ;
1051
+ t.tm_mon = rule.month - 1 ;
1052
+ t.tm_mday = 1 ;
1053
+ mktime (&t);
1054
+
1055
+ int first_wday = t.tm_wday ;
1056
+ int day = 1 + ((7 + rule.day - first_wday) % 7 ) + (rule.week - 1 ) * 7 ;
1057
+ // If week==5, but that day is past the end of the month, go back by 7 days
1058
+ t.tm_mday = day;
1059
+ mktime (&t);
1060
+ if (rule.week == 5 && t.tm_mon != rule.month - 1 )
1061
+ day -= 7 ;
1062
+
1063
+ t.tm_year = year - 1900 ;
1064
+ t.tm_mon = rule.month - 1 ;
1065
+ t.tm_mday = day;
1066
+ t.tm_hour = 0 ;
1067
+ t.tm_min = 0 ;
1068
+ t.tm_sec = 0 ;
1069
+ return mktime (&t);
1070
+ } else {
1071
+ __ensure (!" Invalid rule type" );
1072
+ __builtin_unreachable ();
1073
+ }
1074
+ }
1075
+
1076
+ // Assumes TZ environment variable rules are used, not TZFILE.
1077
+ bool is_in_dst (time_t unix_gmt) {
1078
+ if (rules[0 ].type == TZFILE)
1079
+ __ensure (!" is_in_dst() called with invalid rules" );
1080
+
1081
+ int year;
1082
+ unsigned int _month;
1083
+ unsigned int _day;
1084
+ civil_from_days (unix_gmt / (60 * 60 * 24 ), &year, &_month, &_day);
1085
+
1086
+ // Get the start and end transition days of the year
1087
+ int start_time = time_from_rule (rules[0 ], year);
1088
+ int end_time = time_from_rule (rules[1 ], year);
1089
+
1090
+ // Check if the unix_gmt falls within the DST period
1091
+ if (start_time <= end_time) {
1092
+ return unix_gmt >= start_time && unix_gmt < end_time;
1093
+ } else {
1094
+ // DST period wraps around the year end
1095
+ return unix_gmt >= start_time || unix_gmt < end_time;
1096
+ }
1097
+ }
1098
+
1099
+ int unix_local_from_gmt_tzfile (time_t unix_gmt, time_t *offset, bool *dst, char **tm_zone) {
1100
+ const char *tz = getenv (" TZ" );
1101
+
1102
+ if (!tz || *tz == ' \0 ' )
1103
+ tz = " /etc/localtime" ;
1104
+
1105
+ frg::string<MemoryAllocator> path = parse_tzfile_path (tz);
1106
+
1107
+ // Check if file exists
1108
+ if (!mlibc::sys_stat) {
1109
+ MLIBC_MISSING_SYSDEP ();
1110
+ __ensure (!" cannot proceed without sys_stat" );
1111
+ }
1112
+ struct stat info;
1113
+ if (mlibc::sys_stat (mlibc::fsfd_target::path, -1 , path.data (), 0 , &info))
1114
+ return -1 ;
1115
+
1116
+ // FIXME: Make this fallible so the above check is not needed.
1117
+ file_window window {path.data ()};
1118
+
1119
+ // TODO(geert): we can probably cache this somehow
1120
+ tzfile tzfile_time;
1121
+ memcpy (&tzfile_time, reinterpret_cast <char *>(window.get ()), sizeof (tzfile));
1122
+ tzfile_time.tzh_ttisgmtcnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_ttisgmtcnt );
1123
+ tzfile_time.tzh_ttisstdcnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_ttisstdcnt );
1124
+ tzfile_time.tzh_leapcnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_leapcnt );
1125
+ tzfile_time.tzh_timecnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_timecnt );
1126
+ tzfile_time.tzh_typecnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_typecnt );
1127
+ tzfile_time.tzh_charcnt = mlibc::bit_util<uint32_t >::byteswap (tzfile_time.tzh_charcnt );
1128
+
1129
+ if (tzfile_time.magic [0 ] != ' T' || tzfile_time.magic [1 ] != ' Z' || tzfile_time.magic [2 ] != ' i'
1130
+ || tzfile_time.magic [3 ] != ' f' ) {
1131
+ mlibc::infoLogger () << " mlibc: " << path << " is not a valid TZinfo file" << frg::endlog;
1132
+ return -1 ;
1133
+ }
1134
+
1135
+ if (tzfile_time.version != ' \0 ' && tzfile_time.version != ' 2' && tzfile_time.version != ' 3' ) {
1136
+ mlibc::infoLogger () << " mlibc: " << path << " has an invalid TZinfo version"
1137
+ << frg::endlog;
1138
+ return -1 ;
1139
+ }
1140
+
1141
+ int index = -1 ;
1142
+ for (size_t i = 0 ; i < tzfile_time.tzh_timecnt ; i++) {
1143
+ int32_t ttime;
1144
+ memcpy (&ttime, reinterpret_cast <char *>(window.get ()) + sizeof (tzfile)
1145
+ + i * sizeof (int32_t ), sizeof (int32_t ));
1146
+ ttime = mlibc::bit_util<uint32_t >::byteswap (ttime);
1147
+ // If we are before the first transition, the format dicates that
1148
+ // the first ttinfo entry should be used (and not the ttinfo entry pointed
1149
+ // to by the first transition time).
1150
+ if (i && ttime > unix_gmt) {
1151
+ index = i - 1 ;
1152
+ break ;
1153
+ }
1154
+ }
1155
+
1156
+ // The format dictates that if no transition is applicable,
1157
+ // the first entry in the file is chosen.
1158
+ uint8_t ttinfo_index = 0 ;
1159
+ if (index >= 0 ) {
1160
+ memcpy (&ttinfo_index, reinterpret_cast <char *>(window.get ()) + sizeof (tzfile)
1161
+ + tzfile_time.tzh_timecnt * sizeof (int32_t )
1162
+ + index * sizeof (uint8_t ), sizeof (uint8_t ));
1163
+ }
1164
+
1165
+ // There should be at least one entry in the ttinfo table.
1166
+ // TODO: If there is not, we might want to fall back to UTC, no DST (?).
1167
+ __ensure (tzfile_time.tzh_typecnt );
1168
+
1169
+ ttinfo time_info;
1170
+ memcpy (&time_info, reinterpret_cast <char *>(window.get ()) + sizeof (tzfile)
1171
+ + tzfile_time.tzh_timecnt * sizeof (int32_t )
1172
+ + tzfile_time.tzh_timecnt * sizeof (uint8_t )
1173
+ + ttinfo_index * sizeof (ttinfo), sizeof (ttinfo));
1174
+ time_info.tt_gmtoff = mlibc::bit_util<uint32_t >::byteswap (time_info.tt_gmtoff );
1175
+
1176
+ char *abbrevs = reinterpret_cast <char *>(window.get ()) + sizeof (tzfile)
1177
+ + tzfile_time.tzh_timecnt * sizeof (int32_t )
1178
+ + tzfile_time.tzh_timecnt * sizeof (uint8_t )
1179
+ + tzfile_time.tzh_typecnt * sizeof (struct ttinfo );
1180
+
1181
+ *offset = time_info.tt_gmtoff ;
1182
+ *dst = time_info.tt_isdst ;
1183
+ *tm_zone = abbrevs + time_info.tt_abbrind ;
1184
+ return 0 ;
1185
+ }
1186
+
1006
1187
// Looks up the local time rules for a given
1007
1188
// UNIX GMT timestamp (seconds since 1970 GMT, ignoring leap seconds).
1008
1189
// This function assumes the __time_lock has been taken
1009
1190
int unix_local_from_gmt (time_t unix_gmt, time_t *offset, bool *dst, char **tm_zone) {
1010
1191
do_tzset ();
1011
1192
1012
- if (!daylight) {
1013
- *offset = -timezone;
1014
- *dst = false ;
1015
- *tm_zone = tzname[0 ];
1193
+ if (daylight && rules[0 ].type == TZFILE)
1194
+ return unix_local_from_gmt_tzfile (unix_gmt, offset, dst, tm_zone);
1195
+
1196
+ if (daylight && is_in_dst (unix_gmt)) {
1197
+ *offset = tt_infos[1 ].tt_gmtoff ;
1198
+ *dst = true ;
1199
+ *tm_zone = tzname[1 ];
1016
1200
return 0 ;
1017
1201
}
1018
1202
1019
- mlibc::infoLogger () << " mlibc: TODO support DST" << frg::endlog;
1020
- return -1 ;
1203
+ *offset = -timezone;
1204
+ *dst = false ;
1205
+ *tm_zone = tzname[0 ];
1206
+ return 0 ;
1021
1207
}
1022
1208
1023
1209
} // anonymous namespace
0 commit comments