Skip to content

Use "aware" datetimes #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
The project has a new home!

https://frequenz-floss.github.io/frequenz-channels-python/

For now the documentation is pretty scarce but we will be improving it with
time.

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including if there are any depractions and what they should be replaced with -->
* You need to make sure to use [timezone-aware] `datetime` objects when using
the timestamp returned by [`Timer`], Otherwise you will get an exception.

## New Features

<!-- Here goes the main new features and examples or instructions on how to use them -->

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
* [`Timer`] now returns [timezone-aware] `datetime` objects using UTC as
timezone.


[`Timer`]: https://frequenz-floss.github.io/frequenz-channels-python/v0.11/reference/frequenz/channels/#frequenz.channels.Timer
[timezone-aware]: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects
20 changes: 13 additions & 7 deletions src/frequenz/channels/utils/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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.
Expand All @@ -75,20 +77,24 @@ 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.

Changelog:
* **v0.11.0:** Returns a timezone-aware datetime with UTC timezone
instead of a native datetime object.
"""
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
Expand Down
8 changes: 4 additions & 4 deletions tests/utils/test_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down