diff --git a/micropython/README b/micropython/README new file mode 100644 index 0000000..871b20a --- /dev/null +++ b/micropython/README @@ -0,0 +1,5 @@ + +To run tests locally use: + +`MICROPYPATH=.frozen:. micropython test/test_ProviderClock.py` + diff --git a/micropython/provider/ProviderNewYearCountdown.py b/micropython/provider/ProviderNewYearCountdown.py index 652c4e7..c03f7ce 100644 --- a/micropython/provider/ProviderNewYearCountdown.py +++ b/micropython/provider/ProviderNewYearCountdown.py @@ -1,25 +1,47 @@ -from typing import Union, Tuple +from typing import Callable, Union, Tuple from provider.Clock import Clock from provider.Provider import Provider +# Number of days in each month for a common year (non-leap year) +DAYS_IN_MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +DAYS_IN_MONTHS_LEAP_YEAR = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + +def _days_to_new_year(year: int, month: int, day: int) -> int: + is_leap_year = (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) + days_in_months = DAYS_IN_MONTHS_LEAP_YEAR if is_leap_year else DAYS_IN_MONTHS + + days_passed = sum(days_in_months[:month - 1]) + day + total_days_in_year = 366 if is_leap_year else 365 + + return total_days_in_year - days_passed + class ProviderNewYearCountdown(Provider): - def __init__(self, timezone: str): - self.timezone = timezone + def __init__(self, timezone: str, clock_now: Callable = None): + self.clock_now = clock_now if clock_now else lambda: Clock.now(timezone) def get_word(self, word: str, display: Display) -> Tuple[str, Union[int, None]]: - now = Clock.now(self.timezone) + now = self.clock_now() if now.month == 1 and now.day == 1: # Jan 1st - return now.strftime("!HAPPY NEWYEAR %Y!"), None + return now.strftime("!HAPPY NEW" "YEAR %Y!"), None - next_interval_ms = (60 - now.second) * 1000 + if now.month == 12 and now.day == 31 and now.hour == 23 and now.minute == 59: # last minute + return f"BYE {now.year:04}: {now.hour:02}:{now.minute:02}:{now.second:02}", 1 - delta_hours = 23 - now.hour - delta_minutes = 59 - now.minute + if now.month == 12 and now.day == 31: # last day + next_interval_ms = (60 - now.second) * 1000 + delta_hours = 23 - now.hour + delta_minutes = 59 - now.minute - if delta_minutes % 2 == 0: - return f"{now.year+1:04} IN {delta_hours:02}:{delta_minutes:02} ", next_interval_ms - else: - return f"COUNT DOWN {delta_hours:02}:{delta_minutes:02} ", next_interval_ms + if delta_minutes % 2 == 0: + return f"{now.year+1:04} SOON: {delta_hours:2}H {delta_minutes:02}M", next_interval_ms + else: + return f" NEW YEAR IN {delta_hours:2}H {delta_minutes:02}M", next_interval_ms + + next_interval_ms = ((59 - now.minute) * 60 + (60 - now.second)) * 1000 + delta_days = _days_to_new_year(now.year, now.month, now.day) + delta_hours = 23 - now.hour + return f" {now.year+1:04} IN {delta_days:3}D {delta_hours:02}H", next_interval_ms diff --git a/micropython/test/test_ProviderNewYearCountdown.py b/micropython/test/test_ProviderNewYearCountdown.py new file mode 100644 index 0000000..8563f32 --- /dev/null +++ b/micropython/test/test_ProviderNewYearCountdown.py @@ -0,0 +1,61 @@ +import unittest + +from provider.Clock import Clock +from provider.ProviderNewYearCountdown import ProviderNewYearCountdown + + +class DisplayMock: + def display_length(self) -> int: + return 20 + + +class PythonNewYearCountdownTest(unittest.TestCase): + def setUp(self): + self.display = DisplayMock() + self.provider = ProviderNewYearCountdown("Europe/Stockholm", lambda: self.mock_now) + + def test_dec31_even_minute(self): + self.mock_now = Clock(2024, 12, 31, 22, 0) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, " NEW YEAR " "IN 1H 59M") + self.assertEqual(interval_ms, 60000) + + def test_dec31_odd_minute(self): + self.mock_now = Clock(2024, 12, 31, 22, 1) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, "2025 SOON:" " 1H 58M") + self.assertEqual(interval_ms, 60000) + + def test_last_minute(self): + self.mock_now = Clock(2024, 12, 31, 23, 59, 30) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, "BYE 2024: " " 23:59:30") + self.assertEqual(interval_ms, 1) + + def test_new_year(self): + self.mock_now = Clock(2025, 1, 1, 1, 1) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, "!HAPPY NEW" "YEAR 2025!") + self.assertIsNone(interval_ms) + + def test_jan2(self): + self.mock_now = Clock(2025, 1, 2, 1, 1) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, " 2026 IN " " 363D 22H") + self.assertEqual(interval_ms, 3540000) + + def test_jan2_leap(self): + self.mock_now = Clock(2028, 1, 2, 1, 1) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, " 2029 IN " " 364D 22H") + self.assertEqual(interval_ms, 3540000) + + def test_dec30(self): + self.mock_now = Clock(2025, 12, 30, 1, 1) + word, interval_ms = self.provider.get_word(None, self.display) + self.assertEqual(word, " 2026 IN " " 1D 22H") + self.assertEqual(interval_ms, 3540000) + + +if __name__ == '__main__': + unittest.main() diff --git a/micropython/www/index.html b/micropython/www/index.html index bc0d9c5..d1306a4 100644 --- a/micropython/www/index.html +++ b/micropython/www/index.html @@ -143,7 +143,7 @@ ADL NYC
- New Year Countdown + NYE Countdown
Art