Last active
September 15, 2025 02:17
-
-
Save mscalora/838664c850c661099e2e0c3f6ed41c68 to your computer and use it in GitHub Desktop.
A very small library of north american centric date/time utilities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define ATLANTIC_TZ -5 | |
| #define EASTERN_TZ -5 | |
| #define CENTRAL_TZ -6 | |
| #define MOUNTAIN_TZ -7 | |
| #define PACIFIC_TZ -8 | |
| #define ALASKA_TZ -9 | |
| #define HAWAII_TZ -11 | |
| struct DateTime { | |
| int year; | |
| int month; | |
| int day; | |
| int hour; | |
| int hour12; | |
| int min; | |
| int sec; | |
| bool pm; | |
| }; | |
| bool is_leap(int year) { | |
| return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); | |
| } | |
| int day_of_week(int year, int month, int day) { | |
| if (month < 3) { | |
| month += 12; | |
| year -= 1; | |
| } | |
| int y = year % 100; | |
| int c = year / 100; | |
| int day_of_week_val = (day + (13 * (month + 1)) / 5 + y + y / 4 + c / 4 + 5 * c) % 7; | |
| return (day_of_week_val + 6) % 7; | |
| } | |
| int nth_weekday_date(int year, int month, int n, int weekday) { | |
| int first_day_weekday = day_of_week(year, month, 1); | |
| int diff = weekday - first_day_weekday; | |
| if (diff < 0) { | |
| diff += 7; | |
| } | |
| return 1 + diff + (n - 1) * 7; | |
| } | |
| bool is_daylight_savings(int year, int month, int day) { | |
| if (month < 3 || month > 11) { | |
| return false; | |
| } | |
| if (month > 3 && month < 11) { | |
| return true; | |
| } | |
| if (month == 3) { | |
| int dst_start_day = nth_weekday_date(year, 3, 2, 0); | |
| return day >= dst_start_day; | |
| } | |
| if (month == 11) { | |
| int dst_end_day = nth_weekday_date(year, 11, 1, 0); | |
| return day < dst_end_day; | |
| } | |
| return false; | |
| } | |
| void date_from_epoch(int64_t epoch_seconds, int& year, int& month, int& day) { | |
| const int64_t SECONDS_IN_DAY = 86400; | |
| int64_t total_days = epoch_seconds / SECONDS_IN_DAY; | |
| year = 1970; | |
| int64_t days_in_year; | |
| while (true) { | |
| days_in_year = is_leap(year) ? 366 : 365; | |
| if (total_days < days_in_year) { | |
| break; | |
| } | |
| total_days -= days_in_year; | |
| year++; | |
| } | |
| int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | |
| if (is_leap(year)) { | |
| days_in_month[1] = 29; // Adjust for February in a leap year | |
| } | |
| month = 1; | |
| for (; month <= 12; ++month) { | |
| if (total_days < days_in_month[month - 1]) { | |
| break; | |
| } | |
| total_days -= days_in_month[month - 1]; | |
| } | |
| day = static_cast<int>(total_days) + 1; | |
| } | |
| void datetime_from_epoch(int64_t epoch_seconds, DateTime& dt) { | |
| const int64_t SECONDS_IN_DAY = 86400; | |
| int64_t total_days = epoch_seconds / SECONDS_IN_DAY; | |
| unsigned long secs = epoch_seconds % SECONDS_IN_DAY; | |
| int year = 1970; | |
| int64_t days_in_year; | |
| while (true) { | |
| days_in_year = is_leap(year) ? 366 : 365; | |
| if (total_days < days_in_year) { | |
| break; | |
| } | |
| total_days -= days_in_year; | |
| year++; | |
| } | |
| int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | |
| if (is_leap(year)) { | |
| days_in_month[1] = 29; // Adjust for February in a leap year | |
| } | |
| int month = 1; | |
| for (; month <= 12; ++month) { | |
| if (total_days < days_in_month[month - 1]) { | |
| break; | |
| } | |
| total_days -= days_in_month[month - 1]; | |
| } | |
| dt.year = year; | |
| dt.month = month; | |
| dt.day = static_cast<int>(total_days) + 1; | |
| dt.hour = secs / (60*60); | |
| dt.min = secs % (60*60) / 60; | |
| dt.sec = secs % 60; | |
| dt.pm = dt.hour>=12; | |
| dt.hour12 = dt.hour; | |
| if (dt.hour==0) { | |
| dt.hour12 = 12; | |
| } else if (dt.hour>12) { | |
| dt.hour12 -= 12; | |
| } | |
| } | |
| int tz_offset(unsigned long gmtEpoch, int tz = MOUNTAIN_TZ, bool dst = true) { | |
| int year, month, day, offset = tz*60*60; | |
| date_from_epoch(gmtEpoch + offset, year, month, day); | |
| if (dst && is_daylight_savings(year, month, day)) { | |
| offset += 60*60; | |
| } | |
| return offset; | |
| } | |
| String formatted_date_time(unsigned long epoch, bool date = true, bool time = true, bool time12 = true, bool sec = false) { | |
| char buf[30]; | |
| int count = 0; | |
| DateTime dt; | |
| buf[count] = 0; | |
| datetime_from_epoch(epoch, dt); | |
| if (date) { | |
| count += snprintf(buf+count, sizeof(buf) - count, "%4d/%02d/%02d", dt.year, dt.month, dt.day); | |
| } | |
| if (date && time) { | |
| buf[count++] = ' '; | |
| buf[count] = 0; | |
| } | |
| if (time) { | |
| count += snprintf(buf+count, sizeof(buf) - count, "%d:%02d", time12 ? dt.hour12 : dt.hour, dt.min); | |
| } | |
| if (time && sec) { | |
| count += snprintf(buf+count, sizeof(buf) - count, ":%02d", dt.sec); | |
| } | |
| if (time && time12) { | |
| count += snprintf(buf+count, sizeof(buf) - count, "%s", dt.pm ? "pm" : "am"); | |
| } | |
| return String(buf); | |
| } | |
| String formatted_date(unsigned long epoch) { | |
| return formatted_date_time(epoch, true, false); | |
| } | |
| String formatted_time(unsigned long epoch, bool time12 = true, bool sec = false) { | |
| return formatted_date_time(epoch, false, true, time12, sec); | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment