Skip to content

Commit ad2c82f

Browse files
committed
idk ill rewrite history before this is merged
1 parent cc8f337 commit ad2c82f

File tree

1 file changed

+198
-12
lines changed

1 file changed

+198
-12
lines changed

options/ansi/generic/time.cpp

Lines changed: 198 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ static const char *getoffset(const char *str, long *offset) {
526526
}
527527

528528
enum RuleType {
529+
TZFILE, // mlibc-internal rule type for TZ files
529530
JULIAN_DAY, // Jn = Julian day
530531
DAY_OF_YEAR, // n = day of year
531532
MONTH_NTH_DAY_OF_WEEK, // Mm.n.d = month, week, day of week
@@ -762,7 +763,7 @@ struct tzfile {
762763
uint32_t tzh_charcnt;
763764
};
764765

765-
static bool parse_tzfile(const char *tz) {
766+
frg::string<MemoryAllocator> parse_tzfile_path(const char *tz) {
766767
// POSIX defines :*characters* as a valid but implementation-defined format.
767768
// This was originally introduced as a way to support geographical
768769
// timezones in the format :Area/Location, but the colon was dropped in POSIX.
@@ -794,13 +795,19 @@ static bool parse_tzfile(const char *tz) {
794795
path += tz;
795796
}
796797

798+
return path;
799+
}
800+
801+
static bool parse_tzfile(const char *tz) {
802+
frg::string<MemoryAllocator> path = parse_tzfile_path(tz);
803+
797804
// Check if file exists, otherwise fallback to the default.
798805
if (!mlibc::sys_stat) {
799806
MLIBC_MISSING_SYSDEP();
800807
__ensure(!"cannot proceed without sys_stat");
801808
}
802809
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))
804811
return true;
805812

806813
// FIXME: Make this fallible so the above check is not needed.
@@ -816,13 +823,13 @@ static bool parse_tzfile(const char *tz) {
816823
tzfile_time.tzh_typecnt = mlibc::bit_util<uint32_t>::byteswap(tzfile_time.tzh_typecnt);
817824
tzfile_time.tzh_charcnt = mlibc::bit_util<uint32_t>::byteswap(tzfile_time.tzh_charcnt);
818825

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'
820827
|| tzfile_time.magic[3] != 'f') {
821828
mlibc::infoLogger() << "mlibc: " << path << " is not a valid TZinfo file" << frg::endlog;
822829
return true;
823830
}
824831

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') {
826833
mlibc::infoLogger() << "mlibc: " << path << " has an invalid TZinfo version"
827834
<< frg::endlog;
828835
return true;
@@ -836,6 +843,8 @@ static bool parse_tzfile(const char *tz) {
836843
+ tzfile_time.tzh_timecnt * sizeof(int32_t)
837844
+ tzfile_time.tzh_timecnt * sizeof(uint8_t)
838845
+ tzfile_time.tzh_typecnt * sizeof(struct ttinfo);
846+
bool found_std = false;
847+
bool found_dst = false;
839848
// start from the last ttinfo entry, this matches the behaviour of glibc and musl
840849
for (int i = tzfile_time.tzh_typecnt; i > 0; i--) {
841850
ttinfo time_info;
@@ -844,17 +853,24 @@ static bool parse_tzfile(const char *tz) {
844853
+ tzfile_time.tzh_timecnt * sizeof(uint8_t)
845854
+ i * sizeof(ttinfo), sizeof(ttinfo));
846855
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) {
848857
tzname[0] = abbrevs + time_info.tt_abbrind;
849858
timezone = -time_info.tt_gmtoff;
859+
found_std = true;
850860
}
851-
if (time_info.tt_isdst && !tzname[1]) {
861+
if (time_info.tt_isdst && !found_dst) {
852862
tzname[1] = abbrevs + time_info.tt_abbrind;
853863
timezone = -time_info.tt_gmtoff;
854864
daylight = 1;
865+
found_dst = true;
855866
}
867+
if (found_std && found_dst)
868+
break;
856869
}
857870

871+
rules[0].type = TZFILE;
872+
rules[1].type = TZFILE;
873+
858874
return false;
859875
}
860876

@@ -1003,21 +1019,191 @@ void yearday_from_date(unsigned int year, unsigned int month, unsigned int day,
10031019
*yday = n1 - (n2 * n3) + day - 30;
10041020
}
10051021

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+
10061187
// Looks up the local time rules for a given
10071188
// UNIX GMT timestamp (seconds since 1970 GMT, ignoring leap seconds).
10081189
// This function assumes the __time_lock has been taken
10091190
int unix_local_from_gmt(time_t unix_gmt, time_t *offset, bool *dst, char **tm_zone) {
10101191
do_tzset();
10111192

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];
10161200
return 0;
10171201
}
10181202

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;
10211207
}
10221208

10231209
} //anonymous namespace

0 commit comments

Comments
 (0)