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