This commit is contained in:
J. Nick Koston
2026-01-29 21:59:46 -06:00
parent a1cdfe71de
commit fc951baebc

View File

@@ -17,6 +17,23 @@ static uint32_t parse_uint(const char *&p) {
bool is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } bool is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }
// Extract just the year from a UTC epoch (faster than full epoch_to_tm_utc)
static int epoch_to_year(time_t epoch) {
int64_t days = epoch / 86400;
if (epoch < 0 && epoch % 86400 != 0)
days--;
int year = 1970;
while (days >= (is_leap_year(year) ? 366 : 365)) {
days -= is_leap_year(year) ? 366 : 365;
year++;
}
while (days < 0) {
year--;
days += is_leap_year(year) ? 366 : 365;
}
return year;
}
int days_in_month(int year, int month) { int days_in_month(int year, int month) {
static const int DAYS_PER_MONTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static const int DAYS_PER_MONTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && is_leap_year(year)) if (month == 2 && is_leap_year(year))
@@ -198,9 +215,7 @@ void day_of_year_to_month_day(int day_of_year, int year, int &out_month, int &ou
} }
bool parse_dst_rule(const char *&p, DSTRule &rule) { bool parse_dst_rule(const char *&p, DSTRule &rule) {
// Initialize defaults rule = {}; // Zero initialize
rule = {};
rule.time_seconds = 2 * 3600; // Default 02:00
if (*p == 'M' || *p == 'm') { if (*p == 'M' || *p == 'm') {
// M format: Mm.w.d (month.week.day) // M format: Mm.w.d (month.week.day)
@@ -281,9 +296,7 @@ bool parse_posix_tz(const char *tz_string, ParsedTimezone &result) {
// Check for DST name // Check for DST name
if (!*p) { if (!*p) {
// No DST return true; // No DST
result.has_dst = false;
return true;
} }
// If next char is comma, there's no DST name but there are rules (invalid) // If next char is comma, there's no DST name but there are rules (invalid)
@@ -292,9 +305,7 @@ bool parse_posix_tz(const char *tz_string, ParsedTimezone &result) {
} }
if (!internal::skip_tz_name(p)) { if (!internal::skip_tz_name(p)) {
// No valid DST name, no DST return true; // No valid DST name, no DST
result.has_dst = false;
return true;
} }
// We have a DST name // We have a DST name
@@ -363,19 +374,18 @@ time_t calculate_dst_transition(int year, const DSTRule &rule, int32_t base_offs
break; break;
} }
// Build the tm struct for this date at the transition time // Calculate days from epoch to this date
struct tm transition_tm = {}; int64_t days = 0;
transition_tm.tm_year = year - 1900; for (int y = 1970; y < year; y++) {
transition_tm.tm_mon = month - 1; days += internal::is_leap_year(y) ? 366 : 365;
transition_tm.tm_mday = day; }
transition_tm.tm_hour = rule.time_seconds / 3600; for (int m = 1; m < month; m++) {
transition_tm.tm_min = (rule.time_seconds % 3600) / 60; days += internal::days_in_month(year, m);
transition_tm.tm_sec = rule.time_seconds % 60; }
days += day - 1;
// Convert to UTC epoch, then add the base offset // Convert to epoch and add transition time and base offset
// (transition times are specified in local time before the transition) return days * 86400 + rule.time_seconds + base_offset_seconds;
time_t local_epoch = internal::tm_to_epoch_utc(&transition_tm);
return local_epoch + base_offset_seconds;
} }
bool is_in_dst(time_t utc_epoch, const ParsedTimezone &tz) { bool is_in_dst(time_t utc_epoch, const ParsedTimezone &tz) {
@@ -383,10 +393,7 @@ bool is_in_dst(time_t utc_epoch, const ParsedTimezone &tz) {
return false; return false;
} }
// Get the year from the UTC epoch int year = internal::epoch_to_year(utc_epoch);
struct tm utc_tm;
internal::epoch_to_tm_utc(utc_epoch, &utc_tm);
int year = utc_tm.tm_year + 1900;
// Calculate DST start and end for this year // Calculate DST start and end for this year
// DST start transition happens in standard time // DST start transition happens in standard time