diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d2c20c..9542d18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable(smart_alarm smart_alarm.cpp ui.cpp fonts/Font5x7FixedMono.c alarm.cpp + dateutils.cpp ) pico_set_program_name(smart_alarm "smart_alarm") diff --git a/alarm.cpp b/alarm.cpp index f2dce61..c182184 100644 --- a/alarm.cpp +++ b/alarm.cpp @@ -4,6 +4,8 @@ #include #include "alarm.h" +#include "hardware/rtc.h" +#include "pico/util/datetime.h" alarm::alarm(uint8_t hours, uint8_t minutes, const std::array &days_enabled, bool every_other_week, bool even_week){ @@ -122,4 +124,145 @@ void alarm::set_even_week(bool even_week) { } } +#include "pico/util/datetime.h" + +datetime_t alarm::get_next_ring_time() const { + if (!enabled()) { + datetime_t invalid = {0}; + return invalid; + } + + datetime_t now; + rtc_get_datetime(&now); + + datetime_t candidate = now; + candidate.hour = hours(); + candidate.min = minutes(); + candidate.sec = 0; + + auto days = days_enabled(); + bool any_day_enabled = false; + for (uint8_t i = 0; i < 7; i++) { + if (days[i]) { + any_day_enabled = true; + break; + } + } + + if (!any_day_enabled) { + if (candidate.hour < now.hour || + (candidate.hour == now.hour && candidate.min <= now.min)) { + datetime_t invalid = {0}; + return invalid; + } + return candidate; + } + + // Optimized ISO 8601 week calculation + auto get_iso_week_number = [](const datetime_t& dt) -> uint8_t { + uint16_t ordinal = dt.day; + const uint8_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + for (uint8_t m = 1; m < dt.month; m++) { + ordinal += days_in_month[m]; + if (m == 2 && (dt.year % 4 == 0 && (dt.year % 100 != 0 || dt.year % 400 == 0))) { + ordinal++; + } + } + + uint8_t iso_weekday = (dt.dotw == 0) ? 7 : dt.dotw; + uint8_t week = (ordinal - iso_weekday + 10) / 7; + + if (week == 0) { + // use int16_t insted of uint16_t to avoid warning + int16_t prev_year = dt.year - 1; + bool leap = (prev_year % 4 == 0 && (prev_year % 100 != 0 || prev_year % 400 == 0)); + + datetime_t prev_jan1 = {prev_year, 1, 1, 0, 0, 0, 0}; + uint8_t jan1_weekday = (prev_jan1.dotw == 0) ? 7 : prev_jan1.dotw; + + if (jan1_weekday == 4 || jan1_weekday == 5 || (leap && jan1_weekday == 3)) { + return 53; + } + return 52; + } + + if (week == 53) { + bool leap = (dt.year % 4 == 0 && (dt.year % 100 != 0 || dt.year % 400 == 0)); + uint16_t year_length = leap ? 366 : 365; + + uint16_t remaining_days = year_length - ordinal; + uint8_t dec31_weekday = (iso_weekday + remaining_days) % 7; + if (dec31_weekday == 0) dec31_weekday = 7; + + if (dec31_weekday <= 3) { + return 1; + } + } + + return week; + }; + + for (uint8_t days_ahead = 0; days_ahead < 14; days_ahead++) { + datetime_t test_date = now; + + test_date.day += days_ahead; + + const uint8_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint8_t max_days = days_in_month[test_date.month]; + if (test_date.month == 2 && (test_date.year % 4 == 0 && + (test_date.year % 100 != 0 || test_date.year % 400 == 0))) { + max_days = 29; + } + + while (test_date.day > max_days) { + test_date.day -= max_days; + test_date.month++; + if (test_date.month > 12) { + test_date.month = 1; + test_date.year++; + } + max_days = days_in_month[test_date.month]; + if (test_date.month == 2 && (test_date.year % 4 == 0 && + (test_date.year % 100 != 0 || test_date.year % 400 == 0))) { + max_days = 29; + } + } + + uint8_t dotw = (now.dotw + days_ahead) % 7; + test_date.dotw = dotw; + + if (!days[dotw]) continue; + + test_date.hour = hours(); + test_date.min = minutes(); + test_date.sec = 0; + + if (days_ahead == 0) { + if (test_date.hour < now.hour || + (test_date.hour == now.hour && test_date.min <= now.min)) { + continue; + } + } + + if (every_other_week()) { + uint8_t current_week = get_iso_week_number(now); + uint8_t test_week = get_iso_week_number(test_date); + + bool is_even_week = (test_week % 2 == 0); + + if (even_week() != is_even_week) { + continue; + } + } + + return test_date; + } + + datetime_t invalid = {0}; + return invalid; +} + + + std::vector alarms; \ No newline at end of file diff --git a/alarm.h b/alarm.h index 1c90454..948356f 100644 --- a/alarm.h +++ b/alarm.h @@ -4,6 +4,8 @@ #include #include +#include "pico/types.h" + struct alarm { private: @@ -19,6 +21,7 @@ public: [[nodiscard]] bool every_other_week() const; [[nodiscard]] bool even_week() const; [[nodiscard]] bool enabled() const; + [[nodiscard]] datetime_t get_next_ring_time() const; // Enable/disable methods void enable(); diff --git a/dateutils.cpp b/dateutils.cpp new file mode 100644 index 0000000..1b6b645 --- /dev/null +++ b/dateutils.cpp @@ -0,0 +1,33 @@ +#include "dateutils.h" + +#include "pico/types.h" + + +datetime_compare_res compare_datetime(const datetime_t *dt1, const datetime_t *dt2) { + // Compare year first + if (dt1->year > dt2->year) return BIGGER_THAN; + if (dt1->year < dt2->year) return SMALLER_THAN; + + // Years are equal, compare month + if (dt1->month > dt2->month) return BIGGER_THAN; + if (dt1->month < dt2->month) return SMALLER_THAN; + + // Months are equal, compare day + if (dt1->day > dt2->day) return BIGGER_THAN; + if (dt1->day < dt2->day) return SMALLER_THAN; + + // Days are equal, compare hour + if (dt1->hour > dt2->hour) return BIGGER_THAN; + if (dt1->hour < dt2->hour) return SMALLER_THAN; + + // Hours are equal, compare minute + if (dt1->min > dt2->min) return BIGGER_THAN; + if (dt1->min < dt2->min) return SMALLER_THAN; + + // Minutes are equal, compare second + if (dt1->sec > dt2->sec) return BIGGER_THAN; + if (dt1->sec < dt2->sec) return SMALLER_THAN; + + // All fields are equal + return EQUALS; +} \ No newline at end of file diff --git a/dateutils.h b/dateutils.h new file mode 100644 index 0000000..48a37aa --- /dev/null +++ b/dateutils.h @@ -0,0 +1,11 @@ +#ifndef SMART_ALARM_DATEUTILS_H +#define SMART_ALARM_DATEUTILS_H +#include "pico/types.h" + +enum datetime_compare_res { + EQUALS, + BIGGER_THAN, + SMALLER_THAN, +}; +datetime_compare_res compare_datetime(const datetime_t *dt1, const datetime_t *dt2); +#endif //SMART_ALARM_DATEUTILS_H \ No newline at end of file