diff --git a/src/frequenz/channels/utils/timer.py b/src/frequenz/channels/utils/timer.py index 258f0caa..1a115456 100644 --- a/src/frequenz/channels/utils/timer.py +++ b/src/frequenz/channels/utils/timer.py @@ -4,7 +4,7 @@ """A timer receiver that returns the timestamp every `interval`.""" import asyncio -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional from frequenz.channels.base_classes import Receiver @@ -15,6 +15,8 @@ class Timer(Receiver[datetime]): Primarily for use with [Select][frequenz.channels.Select]. + The timestamp generated is a timezone-aware datetime using UTC as timezone. + Example: When you want something to happen with a fixed period: @@ -59,11 +61,11 @@ def __init__(self, interval: float) -> None: """ self._stopped = False self._interval = timedelta(seconds=interval) - self._next_msg_time = datetime.now() + self._interval + self._next_msg_time = datetime.now(timezone.utc) + self._interval def reset(self) -> None: """Reset the timer to start timing from `now`.""" - self._next_msg_time = datetime.now() + self._interval + self._next_msg_time = datetime.now(timezone.utc) + self._interval def stop(self) -> None: """Stop the timer. @@ -75,20 +77,20 @@ def stop(self) -> None: self._stopped = True async def receive(self) -> Optional[datetime]: - """Return the current time once the next tick is due. + """Return the current time (in UTC) once the next tick is due. Returns: - The time of the next tick or `None` if + The time of the next tick in UTC or `None` if [stop()][frequenz.channels.Timer.stop] has been called on the timer. """ if self._stopped: return None - now = datetime.now() + now = datetime.now(timezone.utc) diff = self._next_msg_time - now while diff.total_seconds() > 0: await asyncio.sleep(diff.total_seconds()) - now = datetime.now() + now = datetime.now(timezone.utc) diff = self._next_msg_time - now self._next_msg_time = now + self._interval diff --git a/tests/utils/test_timer.py b/tests/utils/test_timer.py index 9a88fe21..9fb3d3d6 100644 --- a/tests/utils/test_timer.py +++ b/tests/utils/test_timer.py @@ -6,7 +6,7 @@ import asyncio import logging from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timezone from typing import Optional from frequenz.channels import Anycast, Select, Sender, Timer @@ -30,13 +30,13 @@ class _TestCase: ] fail_count = 0 for test_case in test_cases: - start = datetime.now() + start = datetime.now(timezone.utc) count = 0 async for _ in Timer(test_case.delta): count += 1 if count >= test_case.count: break - actual_duration = (datetime.now() - start).total_seconds() + actual_duration = (datetime.now(timezone.utc) - start).total_seconds() expected_duration = test_case.delta * test_case.count tolerance = expected_duration * 0.1 @@ -72,7 +72,7 @@ async def send(ch1: Sender[int]) -> None: senders = asyncio.create_task(send(chan1.get_sender())) select = Select(msg=chan1.get_receiver(), timer=timer) - start_ts = datetime.now() + start_ts = datetime.now(timezone.utc) stop_ts: Optional[datetime] = None while await select.ready(): if select.msg: