Skip to content

Commit fc2eba2

Browse files
authored
Deserialize ISO-8601 dates ending in Z as UTC (#191)
1 parent 35a76a4 commit fc2eba2

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

src/electionguard/serializable.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dataclasses import dataclass
22
from datetime import datetime
3+
import re
34
from os import path
45
from typing import Any, cast, Type, TypeVar
56

@@ -236,4 +237,14 @@ def set_deserializers() -> None:
236237
NoneType,
237238
)
238239

239-
set_deserializer(lambda dt, cls, **_: datetime.fromisoformat(dt), datetime)
240+
set_deserializer(lambda dt, cls, **_: _deserialize_datetime(dt), datetime)
241+
242+
243+
def _deserialize_datetime(value: str) -> datetime:
244+
"""
245+
The `fromisoformat` function doesn't recognize the Z (Zulu) suffix
246+
to indicate UTC. For compatibility with more external clients, we
247+
should allow it.
248+
"""
249+
tz_corrected = re.sub("Z$", "+00:00", value)
250+
return datetime.fromisoformat(tz_corrected)

tests/test_serializable.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import dataclass
2+
from datetime import datetime, timezone
23
from unittest import TestCase
34
from typing import Any, List, Optional
45
from os import remove
@@ -30,21 +31,48 @@ class DataModel:
3031
test: int
3132
nested: NestedModel
3233
array: List[NestedModel]
34+
datetime: datetime
3335
from_json_file: Optional[Any] = None
3436

3537

3638
JSON_DATA: DataModel = DataModel(
37-
test=1, nested=NestedModel(test=1), array=[NestedModel(test=1)]
39+
test=1,
40+
nested=NestedModel(test=1),
41+
datetime=datetime(2020, 9, 28, 20, 11, 31, tzinfo=timezone.utc),
42+
array=[NestedModel(test=1)],
3843
)
39-
EXPECTED_JSON_STRING = '{"array": [{"test": 1}], "nested": {"test": 1}, "test": 1}'
44+
EXPECTED_JSON_STRING = '{"array": [{"test": 1}], "datetime": "2020-09-28T20:11:31+00:00", "nested": {"test": 1}, "test": 1}'
4045
EXPECTED_JSON_OBJECT = {
4146
"test": 1,
47+
"datetime": "2020-09-28T20:11:31+00:00",
4248
"nested": {"test": 1},
4349
"array": [{"test": 1}],
4450
}
4551

4652

4753
class TestSerializable(TestCase):
54+
def test_read_iso_date(self) -> None:
55+
# Arrange
56+
target_date = datetime(2020, 9, 28, 20, 11, 31, tzinfo=timezone.utc)
57+
representations = [
58+
# UTC
59+
'"2020-09-28T20:11:31+00:00"',
60+
'"2020-09-28T20:11:31.000+00:00"',
61+
'"2020-09-28T20:11:31.000Z"',
62+
'"2020-09-28T20:11:31Z"',
63+
# Other time zone
64+
'"2020-09-28T21:11:31+01:00"',
65+
'"2020-09-28T21:11:31.000+01:00"',
66+
]
67+
68+
# Act
69+
results = [read_json(value, datetime) for value in representations]
70+
71+
# Assert
72+
# expected_timestamp = target_date.timestamp()
73+
for result in results:
74+
self.assertEqual(target_date, result)
75+
4876
def test_read_and_write_json(self) -> None:
4977
# Act
5078
json_string = write_json(JSON_DATA)

0 commit comments

Comments
 (0)