#include /* 1. Convert UTC stm to utime_t 2. Determine UTC offset from rules; add to utime_t 3. Convert utime_t to local time 4. Make decisions based on local time */ /* The 'stm'/'utime_t' epoch extends from STM_EPOCH_YEAR to STM_EPOCH_YEAR + 2^32-1 seconds, meaning from 2000 to approximately 2136. Certain code below depends on the shortness of the epoch and its start year. For instance, only one exception to the 'year mod 4' leap year rule is needed, the year 2100. */ #ifdef AVR #include #else #define PROGMEM #define pgm_read_word(x) (*(x)) #endif #define STM_EPOCH_YEAR (UINT16_C(2000)) typedef uint32_t utime_t; typedef struct { uint8_t stm_sec, stm_min, stm_hour, stm_mon, stm_mday, stm_wday, stm_year; } stm; #define SECONDSPERMINUTE (60u) #define SECONDSPERHOUR (UINT16_C(60)*60u) #define SECONDSPERDAY (UINT16_C(24)*60u*60u) #define SECONDSPERNONLEAPYEAR (UINT32_C(365)*24u*60u*60u) static uint8_t leapdays(uint8_t year, uint8_t mon) { uint8_t r = year / 4 + 1; if(year >= 100) r--; if(year % 4 == 0 && mon < 2) r--; return r; } static const uint16_t _days_before_mon[13] PROGMEM = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; static uint16_t days(uint8_t year, uint8_t mon, uint8_t day) { return pgm_read_word(&_days_before_mon[mon]) + day - 1; } utime_t stm2utime(stm *t) { utime_t r = t->stm_year * SECONDSPERNONLEAPYEAR; r += leapdays(t->stm_year, t->stm_mon) * SECONDSPERDAY; r += days(t->stm_year, t->stm_mon, t->stm_mday) * SECONDSPERDAY; r += t->stm_hour * SECONDSPERHOUR; r += t->stm_min * SECONDSPERMINUTE; r += t->stm_sec; return r; } #define DI4Y UINT16_C(1461) #define DI100Y UINT16_C(36524) void utime2stm(stm *t, utime_t u) { int8_t year; uint8_t n1, n4, n100, mon, _isly; int16_t yday, preceding; t->stm_sec = u % 60; u /= 60; t->stm_min = u % 60; u /= 60; t->stm_hour = u % 24; u /= 24; // at this point, u contains days t->stm_wday = (u+6) % 7; if(u < 366) { _isly = 1; t->stm_year = year = 0; yday = u; goto find_day; } u = u - 366; // this algorithm wants to start at 1-1-(2)001 n100 = (u > DI100Y); if(n100) u -= DI100Y; n4 = (u / DI4Y); u = u % DI4Y; n1 = u / 365; u = u % 365; year = n100 ? 100 : 0 + n4*4 + n1; if(n1 == 4) { t->stm_year = year; t->stm_mon = 11; t->stm_mday = 31; return; } t->stm_year = year + 1; yday = u; _isly = n1 == 3 && n4 != 24; find_day: mon = ((yday + 50) >> 5) - 1; again: preceding = pgm_read_word(&_days_before_mon[mon]) + (mon > 1 && _isly); if(preceding > yday) { mon--; goto again; } t->stm_mon = mon; t->stm_mday = yday - preceding + 1; } #ifndef AVR #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif #include #include #include /* >>> datetime.datetime(2000,1,1) - datetime.datetime(1970,1,1) datetime.timedelta(10957) */ #define STM_EPOCH_IN_UNIX_EPOCH (10957*86400) static utime_t stm2utime_libc(stm *st) __attribute__((unused)); static utime_t stm2utime_libc(stm *st) { struct tm t = {0,}; t.tm_sec = st->stm_sec; t.tm_min = st->stm_min; t.tm_hour = st->stm_hour; t.tm_mon = st->stm_mon; t.tm_mday = st->stm_mday; t.tm_wday = st->stm_wday; t.tm_year = st->stm_year + STM_EPOCH_YEAR; time_t ut = timegm(&t); return ut - STM_EPOCH_IN_UNIX_EPOCH; } const char *daynames[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}; int main() { int d, fail=0; // test every day in the epoch and in the unix epoch // (this goes to at least 2038; further on platforms where time_t is // unsigned or 64 bits) for(d=0; d<49710; d++) { utime_t ut = d * SECONDSPERDAY, ut2; time_t t = ut + STM_EPOCH_IN_UNIX_EPOCH; struct tm tm; stm st, st2; if(t < 0) break; // 32-bit time_t gmtime_r(&t, &tm); st.stm_sec = tm.tm_sec; st.stm_min = tm.tm_min; st.stm_hour = tm.tm_hour; st.stm_mon = tm.tm_mon; st.stm_mday = tm.tm_mday; st.stm_wday = tm.tm_wday; st.stm_year = tm.tm_year - STM_EPOCH_YEAR + 1900; ut2 = stm2utime(&st); if(ut != ut2) { printf("%10d != %10d %2d:%02d:%02d %4d.%02d.%02d\n\n", ut, ut2, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday); fail++; } utime2stm(&st2, ut); if(memcmp(&st, &st2, sizeof(stm))) { printf("%10d %2d:%02d:%02d %3s %4d.%02d.%02d\n" " != %2d:%02d:%02d %3s %4d.%02d.%02d\n\n", ut, st.stm_hour, st.stm_min, st.stm_sec, daynames[st.stm_wday], st.stm_year+STM_EPOCH_YEAR, st.stm_mon+1, st.stm_mday, st2.stm_hour, st2.stm_min, st2.stm_sec, daynames[st2.stm_wday], st2.stm_year+STM_EPOCH_YEAR, st2.stm_mon+1, st2.stm_mday); fail++; } } // test every minute in the first day of the epoch for(d=0; d<86400; d++) { utime_t ut = d; time_t t = ut + STM_EPOCH_IN_UNIX_EPOCH; struct tm tm; stm st, st2; gmtime_r(&t, &tm); st.stm_sec = tm.tm_sec; st.stm_min = tm.tm_min; st.stm_hour = tm.tm_hour; st.stm_mon = tm.tm_mon; st.stm_mday = tm.tm_mday; st.stm_wday = tm.tm_wday; st.stm_year = tm.tm_year - STM_EPOCH_YEAR + 1900; utime2stm(&st2, ut); if(memcmp(&st, &st2, sizeof(stm))) { printf("%10d %2d:%02d:%02d %3s %4d.%02d.%02d\n" " != %2d:%02d:%02d %3s %4d.%02d.%02d\n\n", ut, st.stm_hour, st.stm_min, st.stm_sec, daynames[st.stm_wday], st.stm_year+STM_EPOCH_YEAR, st.stm_mon+1, st.stm_mday, st2.stm_hour, st2.stm_min, st2.stm_sec, daynames[st2.stm_wday], st2.stm_year+STM_EPOCH_YEAR, st2.stm_mon+1, st2.stm_mday); fail++; } } return fail != 0; } #endif