Skip to content

Commit 5a2cadf

Browse files
committed
Rework datetime handling
To properly convert from protobuf to datetime, one has to explicitly handle the nanoseconds like so: ```python # Add microseconds and add nanoseconds converted to microseconds microseconds = int(ts.nanos / 1000) return datetime.fromtimestamp(ts.seconds + microseconds * 1e-6, tz=tz) ``` We implemented a convert function that does exactly this, but we have not been using it consistently yet. This commit replaces the last occurances. With that in mind, this commit changes: * Consistently use the conversion function from the common repository Fix start_time conversion imprecision bug * Use shorter notations to set timezones * Raise on invalid timezone in create() Signed-off-by: Mathias L. Baumann <[email protected]>
1 parent 1c132ca commit 5a2cadf

File tree

3 files changed

+24
-26
lines changed

3 files changed

+24
-26
lines changed

src/frequenz/client/dispatch/_client.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from frequenz.api.dispatch.v1.dispatch_pb2 import (
2020
TimeIntervalFilter as PBTimeIntervalFilter,
2121
)
22-
from google.protobuf.timestamp_pb2 import Timestamp
22+
23+
from frequenz.client.base.conversion import to_timestamp
2324

2425
from ._internal_types import DispatchCreateRequest
2526
from .types import (
@@ -86,14 +87,6 @@ async def list(
8687
"""
8788
time_interval = None
8889

89-
def to_timestamp(dt: datetime | None) -> Timestamp | None:
90-
if dt is None:
91-
return None
92-
93-
ts = Timestamp()
94-
ts.FromDatetime(dt)
95-
return ts
96-
9790
if start_from or start_to or end_from or end_to:
9891
time_interval = PBTimeIntervalFilter(
9992
start_from=to_timestamp(start_from),
@@ -147,9 +140,13 @@ async def create(
147140
Raises:
148141
ValueError: If start_time is in the past.
149142
"""
150-
if start_time <= datetime.now().astimezone(start_time.tzinfo):
143+
if start_time <= datetime.now(tz=start_time.tzinfo):
151144
raise ValueError("start_time must not be in the past")
152145

146+
# Raise if it's not UTC
147+
if start_time.tzinfo is None or start_time.tzinfo.utcoffset(start_time) is None:
148+
raise ValueError("start_time must be timezone aware")
149+
153150
request = DispatchCreateRequest(
154151
microgrid_id=microgrid_id,
155152
type=_type,
@@ -195,7 +192,7 @@ async def update(
195192
case "type":
196193
raise ValueError("Updating type is not supported")
197194
case "start_time":
198-
msg.update.start_time.FromDatetime(val)
195+
msg.update.start_time.CopyFrom(to_timestamp(val))
199196
case "duration":
200197
msg.update.duration = int(val.total_seconds())
201198
case "selector":

src/frequenz/client/dispatch/_internal_types.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
from dataclasses import dataclass
8-
from datetime import datetime, timedelta, timezone
8+
from datetime import datetime, timedelta
99
from typing import Any
1010

1111
# pylint: disable=no-name-in-module
@@ -16,6 +16,8 @@
1616
# pylint: enable=no-name-in-module
1717
from google.protobuf.json_format import MessageToDict
1818

19+
from frequenz.client.base.conversion import to_datetime, to_timestamp
20+
1921
from .types import (
2022
ComponentSelector,
2123
RecurrenceRule,
@@ -79,7 +81,7 @@ def from_protobuf(
7981
return DispatchCreateRequest(
8082
microgrid_id=pb_object.microgrid_id,
8183
type=pb_object.type,
82-
start_time=pb_object.start_time.ToDatetime().replace(tzinfo=timezone.utc),
84+
start_time=to_datetime(pb_object.start_time),
8385
duration=timedelta(seconds=pb_object.duration),
8486
selector=component_selector_from_protobuf(pb_object.selector),
8587
is_active=pb_object.is_active,
@@ -98,7 +100,7 @@ def to_protobuf(self) -> PBDispatchCreateRequest:
98100

99101
pb_request.microgrid_id = self.microgrid_id
100102
pb_request.type = self.type
101-
pb_request.start_time.FromDatetime(self.start_time)
103+
pb_request.start_time.CopyFrom(to_timestamp(self.start_time))
102104
pb_request.duration = int(self.duration.total_seconds())
103105
pb_request.selector.CopyFrom(component_selector_to_protobuf(self.selector))
104106
pb_request.is_active = self.is_active

src/frequenz/client/dispatch/types.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
from dataclasses import dataclass, field
8-
from datetime import datetime, timedelta, timezone
8+
from datetime import datetime, timedelta
99
from enum import IntEnum
1010
from typing import Any
1111

@@ -17,6 +17,8 @@
1717
from frequenz.api.dispatch.v1.dispatch_pb2 import RecurrenceRule as PBRecurrenceRule
1818
from google.protobuf.json_format import MessageToDict
1919

20+
from frequenz.client.base.conversion import to_datetime, to_timestamp
21+
2022
# pylint: enable=no-name-in-module
2123
from frequenz.client.common.microgrid.components import ComponentCategory
2224

@@ -125,10 +127,7 @@ def from_protobuf(cls, pb_criteria: PBRecurrenceRule.EndCriteria) -> "EndCriteri
125127
case "count":
126128
instance.count = pb_criteria.count
127129
case "until":
128-
instance.until = pb_criteria.until.ToDatetime().replace(
129-
tzinfo=timezone.utc
130-
)
131-
130+
instance.until = to_datetime(pb_criteria.until)
132131
return instance
133132

134133
def to_protobuf(self) -> PBRecurrenceRule.EndCriteria:
@@ -142,7 +141,7 @@ def to_protobuf(self) -> PBRecurrenceRule.EndCriteria:
142141
if self.count is not None:
143142
pb_criteria.count = self.count
144143
elif self.until is not None:
145-
pb_criteria.until.FromDatetime(self.until)
144+
pb_criteria.until.CopyFrom(to_timestamp(self.until))
146145

147146
return pb_criteria
148147

@@ -306,9 +305,9 @@ def from_protobuf(cls, pb_object: PBDispatch) -> "Dispatch":
306305
id=pb_object.id,
307306
microgrid_id=pb_object.microgrid_id,
308307
type=pb_object.type,
309-
create_time=pb_object.create_time.ToDatetime().replace(tzinfo=timezone.utc),
310-
update_time=pb_object.update_time.ToDatetime().replace(tzinfo=timezone.utc),
311-
start_time=pb_object.start_time.ToDatetime().replace(tzinfo=timezone.utc),
308+
create_time=to_datetime(pb_object.create_time),
309+
update_time=to_datetime(pb_object.update_time),
310+
start_time=to_datetime(pb_object.start_time),
312311
duration=timedelta(seconds=pb_object.duration),
313312
selector=component_selector_from_protobuf(pb_object.selector),
314313
active=pb_object.is_active,
@@ -328,9 +327,9 @@ def to_protobuf(self) -> PBDispatch:
328327
pb_dispatch.id = self.id
329328
pb_dispatch.microgrid_id = self.microgrid_id
330329
pb_dispatch.type = self.type
331-
pb_dispatch.create_time.FromDatetime(self.create_time)
332-
pb_dispatch.update_time.FromDatetime(self.update_time)
333-
pb_dispatch.start_time.FromDatetime(self.start_time)
330+
pb_dispatch.create_time.CopyFrom(to_timestamp(self.create_time))
331+
pb_dispatch.update_time.CopyFrom(to_timestamp(self.update_time))
332+
pb_dispatch.start_time.CopyFrom(to_timestamp(self.start_time))
334333
pb_dispatch.duration = int(self.duration.total_seconds())
335334
pb_dispatch.selector.CopyFrom(component_selector_to_protobuf(self.selector))
336335
pb_dispatch.is_active = self.active

0 commit comments

Comments
 (0)