Skip to content

Commit 779cc47

Browse files
committed
Enforce UTC validation for timestamps
Now timestamps have to be specified in UTC. If not, the client will raise an error. Converting timezones doesn't have to be part of the thin client and can be provided in a higher-level client if needed. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent c8f931e commit 779cc47

File tree

2 files changed

+46
-63
lines changed

2 files changed

+46
-63
lines changed

src/frequenz/client/electricity_trading/_types.py

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ class DeliveryPeriod:
386386
duration: DeliveryDuration
387387
"""The length of the delivery period."""
388388

389+
def __post_init__(self) -> None:
390+
"""Validate that the start timestamp is in UTC."""
391+
if self.start.tzinfo is None or self.start.tzinfo != timezone.utc:
392+
raise ValueError("Start timestamp must be in UTC timezone.")
389393

390394
def __eq__(
391395
self,
@@ -1037,11 +1041,11 @@ class Order: # pylint: disable=too-many-instance-attributes
10371041
def __post_init__(self) -> None:
10381042
"""Post initialization checks to ensure that all datetimes are UTC."""
10391043
if self.valid_until is not None:
1040-
if self.valid_until.tzinfo is None:
1044+
if (
1045+
self.valid_until.tzinfo is None
1046+
or self.valid_until.tzinfo != timezone.utc
1047+
):
10411048
raise ValueError("Valid until must be a UTC datetime.")
1042-
if self.valid_until.tzinfo != timezone.utc:
1043-
_logger.warning("Valid until is not a UTC datetime. Converting to UTC.")
1044-
self.valid_until = self.valid_until.astimezone(timezone.utc)
10451049

10461050
@classmethod
10471051
@from_pb
@@ -1210,11 +1214,11 @@ class Trade: # pylint: disable=too-many-instance-attributes
12101214

12111215
def __post_init__(self) -> None:
12121216
"""Post initialization checks to ensure that all datetimes are UTC."""
1213-
if self.execution_time.tzinfo is None:
1214-
raise ValueError("Execution time must have timezone information")
1215-
if self.execution_time.tzinfo != timezone.utc:
1216-
_logger.warning("Execution timenis not in UTC timezone. Converting to UTC.")
1217-
self.execution_time = self.execution_time.astimezone(timezone.utc)
1217+
if (
1218+
self.execution_time.tzinfo is None
1219+
or self.execution_time.tzinfo != timezone.utc
1220+
):
1221+
raise ValueError("Execution time must be a UTC datetime.")
12181222

12191223
@classmethod
12201224
@from_pb
@@ -1341,19 +1345,14 @@ def __post_init__(self) -> None:
13411345
ValueError: If create_time or modification_time do not have timezone information.
13421346
13431347
"""
1344-
if self.create_time.tzinfo is None:
1345-
raise ValueError("Create time must have timezone information")
1346-
if self.create_time.tzinfo != timezone.utc:
1347-
_logger.warning("Create time is not in UTC timezone. Converting to UTC.")
1348-
self.create_time = self.create_time.astimezone(timezone.utc)
1348+
if self.create_time.tzinfo is None or self.create_time.tzinfo != timezone.utc:
1349+
raise ValueError("Create time must be a UTC datetime.")
13491350

1350-
if self.modification_time.tzinfo is None:
1351-
raise ValueError("Modification time must have timezone information")
1352-
if self.modification_time.tzinfo != timezone.utc:
1353-
_logger.warning(
1354-
"Modification time is not in UTC timezone. Converting to UTC."
1355-
)
1356-
self.modification_time = self.modification_time.astimezone(timezone.utc)
1351+
if (
1352+
self.modification_time.tzinfo is None
1353+
or self.modification_time.tzinfo != timezone.utc
1354+
):
1355+
raise ValueError("Modification time must be a UTC datetime.")
13571356

13581357
@classmethod
13591358
@from_pb
@@ -1432,11 +1431,11 @@ class PublicTrade: # pylint: disable=too-many-instance-attributes
14321431

14331432
def __post_init__(self) -> None:
14341433
"""Post initialization checks to ensure that all datetimes are UTC."""
1435-
if self.execution_time.tzinfo is None:
1436-
raise ValueError("Execution time must have timezone information")
1437-
if self.execution_time.tzinfo != timezone.utc:
1438-
_logger.warning("Execution time is not in UTC timezone. Converting to UTC.")
1439-
self.execution_time = self.execution_time.astimezone(timezone.utc)
1434+
if (
1435+
self.execution_time.tzinfo is None
1436+
or self.execution_time.tzinfo != timezone.utc
1437+
):
1438+
raise ValueError("Execution time must be a UTC datetime.")
14401439

14411440
@classmethod
14421441
@from_pb
@@ -1973,12 +1972,10 @@ class UpdateOrder: # pylint: disable=too-many-instance-attributes
19731972

19741973
def __post_init__(self) -> None:
19751974
"""Post initialization checks to ensure that all datetimes are UTC."""
1976-
if self.valid_until is not None:
1977-
if self.valid_until.tzinfo is None:
1978-
raise ValueError("Valid until must be a UTC datetime.")
1979-
if self.valid_until.tzinfo != timezone.utc:
1980-
_logger.warning("Valid until is not a UTC datetime. Converting to UTC.")
1981-
self.valid_until = self.valid_until.astimezone(timezone.utc)
1975+
if self.valid_until is not None and (
1976+
self.valid_until.tzinfo is None or self.valid_until.tzinfo != timezone.utc
1977+
):
1978+
raise ValueError("Valid until must be a UTC datetime.")
19821979

19831980
@classmethod
19841981
@from_pb
@@ -2106,18 +2103,6 @@ class PublicOrder: # pylint: disable=too-many-instance-attributes
21062103
update_time: datetime
21072104
"""UTC Timestamp of the last update to the order."""
21082105

2109-
def __post_init__(self) -> None:
2110-
"""Post initialization checks to ensure that all datetimes are UTC."""
2111-
if self.delivery_period.start.tzinfo is None:
2112-
raise ValueError("Delivery period start must have timezone information")
2113-
if self.delivery_period.start.tzinfo != timezone.utc:
2114-
_logger.warning(
2115-
"Delivery period start is not in UTC timezone. Converting to UTC."
2116-
)
2117-
self.delivery_period.start = self.delivery_period.start.astimezone(
2118-
timezone.utc
2119-
)
2120-
21212106
@classmethod
21222107
@from_pb
21232108
def from_pb(

tests/test_types.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ def test_no_timezone_raises_value_error() -> None:
488488

489489

490490
def test_invalid_timezone() -> None:
491-
"""Test that non-UTC timezones are converted to UTC."""
491+
"""Test that non-UTC timezones raise a ValueError."""
492492
with pytest.raises(ValueError):
493493
DeliveryPeriod(
494494
start=datetime.now(timezone(timedelta(hours=1))),
@@ -612,25 +612,23 @@ def test_order_detail_no_timezone_error() -> None:
612612
)
613613

614614

615-
def test_order_detail_timezone_converted_to_utc() -> None:
616-
"""Test that an order detail with inputs with non-UTC timezone is converted to UTC."""
615+
def test_order_detail_non_utc_timezone() -> None:
616+
"""Test that an order detail with inputs with non-UTC timezone raises a ValueError."""
617617
start = datetime.now(timezone(timedelta(hours=1)))
618-
order_detail = OrderDetail(
619-
order_id=1,
620-
order=ORDER,
621-
state_detail=StateDetail(
622-
state=OrderState.ACTIVE,
623-
state_reason=StateReason.ADD,
624-
market_actor=MarketActor.USER,
625-
),
626-
open_quantity=Power(mw=Decimal("5.00")),
627-
filled_quantity=Power(mw=Decimal("0.00")),
628-
create_time=start,
629-
modification_time=start,
630-
)
631-
632-
assert order_detail.create_time.tzinfo == timezone.utc
633-
assert order_detail.modification_time.tzinfo == timezone.utc
618+
with pytest.raises(ValueError):
619+
OrderDetail(
620+
order_id=1,
621+
order=ORDER,
622+
state_detail=StateDetail(
623+
state=OrderState.ACTIVE,
624+
state_reason=StateReason.ADD,
625+
market_actor=MarketActor.USER,
626+
),
627+
open_quantity=Power(mw=Decimal("5.00")),
628+
filled_quantity=Power(mw=Decimal("0.00")),
629+
create_time=start,
630+
modification_time=start,
631+
)
634632

635633

636634
def test_gridpool_order_filter_to_pb() -> None:

0 commit comments

Comments
 (0)