3#ifdef USE_TIME_TIMEZONE
13static ParsedTimezone global_tz_{};
23static uint32_t parse_uint(
const char *&p) {
25 while (std::isdigit(
static_cast<unsigned char>(*p))) {
26 value = value * 10 + (*p -
'0');
38static constexpr int count_leap_years_up_to(
int year) {
return year / 4 -
year / 100 +
year / 400; }
46static inline int64_t days_to_year_start(
int year) {
55static int days_to_year(int64_t &
days) {
57 int64_t year_start = days_to_year_start(
year);
58 while (
days < year_start) {
60 year_start = days_to_year_start(
year);
62 while (
days >= year_start + days_in_year(
year)) {
63 year_start += days_in_year(
year);
71static int epoch_to_year(time_t epoch) {
75 return days_to_year(
days);
101 int h = (
day + (13 * (
month + 1)) / 5 + k + k / 4 +
j / 4 - 2 *
j) % 7;
103 return ((
h + 6) % 7);
147 while (*p && *p !=
'>') {
158 const char *start = p;
159 while (*p && std::isalpha(
static_cast<unsigned char>(*p))) {
162 return (p - start) >= 3;
170 }
else if (*p ==
'+') {
174 int hours = parse_uint(p);
180 minutes = parse_uint(p);
183 seconds = parse_uint(p);
187 return sign * (hours * 3600 + minutes * 60 + seconds);
191static void parse_transition_time(
const char *&p,
DSTRule &
rule) {
203 int remaining = julian_day;
208 if (remaining <=
dim) {
221 int remaining = day_of_year;
226 if (remaining < days_this_month) {
230 remaining -= days_this_month;
242 if (*p ==
'M' || *p ==
'm') {
265 }
else if (*p ==
'J' || *p ==
'j') {
274 }
else if (std::isdigit(
static_cast<unsigned char>(*p))) {
287 parse_transition_time(p,
rule);
311 int first_occurrence = 1 + days_until_first;
316 day = first_occurrence;
359 int year = internal::epoch_to_year(utc_epoch);
383 if (!tz_string || !*tz_string) {
387 const char *p = tz_string;
401 if (!*p || (!std::isdigit(
static_cast<unsigned char>(*p)) && *p !=
'+' && *p !=
'-')) {
418 if (!std::isalpha(
static_cast<unsigned char>(*p)) && *p !=
'<') {
427 if (*p && *p !=
',' && (std::isdigit(
static_cast<unsigned char>(*p)) || *p ==
'+' || *p ==
'-')) {
456 int32_t display = -posix_offset;
457 char sign = display >= 0 ?
'+' :
'-';
460 int h = display / 3600;
461 int m = (display % 3600) / 60;
462 snprintf(buf, buf_size,
"%c%02d%02d", sign,
h,
m);
475 time_t local_epoch = utc_epoch - offset;
478 out_tm->tm_isdst = in_dst ? 1 : 0;
492extern "C" struct tm *
localtime_r(
const time_t *timer,
struct tm *result) {
493 if (timer ==
nullptr || result ==
nullptr) {
503 static struct tm localtime_buf;
int day_of_week(int year, int month, int day)
Calculate day of week for any date (0 = Sunday) Uses a simplified algorithm that works for years 1970...
int days_in_month(int year, int month)
Get the number of days in a month.
int32_t parse_offset(const char *&p)
Parse an offset in format [-]hh[:mm[:ss]].
constexpr int LEAP_YEARS_BEFORE_EPOCH
time_t const DSTRule & rule
bool parse_dst_rule(const char *&p, DSTRule &rule)
Parse a DST rule in format Mm.w.d[/time], Jn[/time], or n[/time].
constexpr int SECONDS_PER_DAY
bool skip_tz_name(const char *&p)
Skip a timezone name (letters or <...> quoted format)
void julian_to_month_day(int julian_day, int &month, int &day)
Convert Julian day (J format, 1-365 not counting Feb 29) to month/day.
void day_of_year_to_month_day(int day_of_year, int year, int &month, int &day)
Convert day of year (plain format, 0-365 counting Feb 29) to month/day.
bool is_leap_year(int year)
Check if a year is a leap year.
time_t const DSTRule int32_t base_offset_seconds
constexpr int DAYS_PER_YEAR
time_t calculate_dst_transition(int year, const DSTRule &rule, int32_t base_offset_seconds)
Calculate the epoch timestamp for a DST transition in a given year.
void epoch_to_tm_utc(time_t epoch, struct tm *out_tm)
Convert epoch to year/month/day/hour/min/sec (UTC)
void set_global_tz(const ParsedTimezone &tz)
Set the global timezone used by epoch_to_local_tm() when called without a timezone.
bool is_in_dst(time_t utc_epoch, const ParsedTimezone &tz)
Check if a given UTC epoch falls within DST for the parsed timezone.
const ParsedTimezone & get_global_tz()
Get the global timezone.
ESPTime __attribute__((noinline)) RealTimeClock
bool parse_posix_tz(const char *tz_string, ParsedTimezone &result)
Parse a POSIX TZ string into a ParsedTimezone struct.
@ JULIAN_NO_LEAP
J format: Jn (day 1-365, Feb 29 not counted)
@ NONE
No DST rule (used to indicate no DST)
@ DAY_OF_YEAR
Plain number: n (day 0-365, Feb 29 counted in leap years)
@ MONTH_WEEK_DAY
M format: Mm.w.d (e.g., M3.2.0 = 2nd Sunday of March)
bool const ParsedTimezone & tz
bool epoch_to_local_tm(time_t utc_epoch, const ParsedTimezone &tz, struct tm *out_tm)
Convert a UTC epoch to local time using the parsed timezone.
void format_designation(int32_t posix_offset, char *buf, size_t buf_size)
Format a POSIX offset as "+HHMM"/"-HHMM" into buf (must be >= 6 bytes).
struct tm * localtime_r(const time_t *timer, struct tm *result)
struct tm * localtime(const time_t *timer)
Rule for DST transition (packed for 32-bit: 12 bytes)
uint16_t day
Day of year (for JULIAN_NO_LEAP and DAY_OF_YEAR)
DSTRuleType type
Type of rule.
uint8_t week
Week 1-5, 5 = last (for MONTH_WEEK_DAY)
int32_t time_seconds
Seconds after midnight (default 7200 = 2:00 AM)
uint8_t day_of_week
Day 0-6, 0 = Sunday (for MONTH_WEEK_DAY)
uint8_t month
Month 1-12 (for MONTH_WEEK_DAY)
Parsed POSIX timezone information (packed for 32-bit: 32 bytes)
bool has_dst() const
Check if this timezone has DST rules.
DSTRule dst_end
When DST ends.
DSTRule dst_start
When DST starts.
int32_t dst_offset_seconds
DST offset from UTC in seconds.
int32_t std_offset_seconds
Standard time offset from UTC in seconds (positive = west)