Skip to content

Commit 7c7ffc8

Browse files
consider case where datetime was parsed from the beginning (was never a string)
1 parent e68ee93 commit 7c7ffc8

File tree

5 files changed

+68
-63
lines changed

5 files changed

+68
-63
lines changed

models/ConfigData.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
MetadataConfig,
1010
ResourceConfigTemplate,
1111
)
12-
from .top_level.utils import datetime_to_string
12+
from ..utils.helper_functions import datetime_to_string
1313
from .top_level.utils import InlineList
1414
from .top_level.providers import ProviderTemplate
1515
from .top_level.providers.records import ProviderTypes

models/top_level/utils.py

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from datetime import datetime, timezone
22
from enum import Enum
3-
import re
43

54
STRING_SEPARATOR = " | "
65

@@ -51,63 +50,3 @@ def bbox_from_list(raw_bbox_list: list):
5150
)
5251

5352
return InlineList(list_bbox_val)
54-
55-
56-
def to_iso8601(dt: datetime) -> str:
57-
"""
58-
Convert datetime to UTC ISO 8601 string, for both naive and aware datetimes.
59-
"""
60-
if dt.tzinfo is None:
61-
# Treat naive datetime as UTC
62-
dt = dt.replace(tzinfo=timezone.utc)
63-
else:
64-
# Convert to UTC
65-
dt = dt.astimezone(timezone.utc)
66-
67-
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
68-
69-
70-
def datetime_to_string(data: datetime):
71-
# normalize to UTC and format with Z
72-
if data.tzinfo is None:
73-
data = data.replace(tzinfo=timezone.utc)
74-
else:
75-
data = data.astimezone(timezone.utc)
76-
return data.strftime("%Y-%m-%dT%H:%M:%SZ")
77-
78-
79-
def datetime_from_string(value: str) -> datetime | None:
80-
"""
81-
Parse common ISO8601 datetime strings and return a timezone-aware datetime.
82-
Accepts:
83-
- 2025-12-17T12:34:56Z
84-
- 2025-12-17T12:34:56+02:00
85-
- 2025-12-17T12:34:56+0200
86-
If no timezone is present, returns a UTC-aware datetime (assumption).
87-
Returns None if parsing fails.
88-
"""
89-
if not isinstance(value, str):
90-
return None
91-
92-
s = value.strip()
93-
# quick normalization: trailing Z -> +00:00
94-
if s.endswith("Z"):
95-
s = s[:-1] + "+00:00"
96-
97-
# normalize +0200 -> +02:00
98-
s = re.sub(r"([+-]\d{2})(\d{2})$", r"\1:\2", s)
99-
100-
# Try stdlib first (requires offset with colon to return aware dt)
101-
try:
102-
dt = datetime.fromisoformat(s)
103-
except Exception:
104-
dt = None
105-
106-
if dt is None:
107-
return None
108-
109-
# If dt is naive, assume UTC
110-
if dt.tzinfo is None:
111-
dt = dt.replace(tzinfo=timezone.utc)
112-
113-
return dt

models/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from .top_level.utils import (
88
InlineList,
99
get_enum_value_from_string,
10-
datetime_from_string,
1110
)
11+
from ..utils.helper_functions import datetime_from_string
1212

1313

1414
def update_dataclass_from_dict(

utils/data_diff.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from datetime import datetime
12
from typing import Any
23

4+
from .helper_functions import datetime_to_string
5+
36

47
def diff_yaml_dict(obj1: dict, obj2: dict) -> dict:
58
"""Returns all added, removed or changed elements between 2 dictionaries."""
@@ -75,6 +78,14 @@ def diff_obj(obj1: Any, obj2: Any, diff: dict, path: str = "") -> dict:
7578
else:
7679
if obj1 != obj2:
7780

81+
# ignore the case where incoming datetime was never a string
82+
if (
83+
isinstance(obj1, datetime)
84+
and isinstance(obj2, str)
85+
and datetime_to_string(obj1) == obj2
86+
):
87+
return diff
88+
7889
# ignore the case where dates came from 'requests' in +00:00 format
7990
if (
8091
type(obj1) == type(obj2) == str

utils/helper_functions.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from datetime import datetime, timezone
2+
import re
3+
4+
5+
def datetime_to_string(data: datetime):
6+
# normalize to UTC and format with Z
7+
if data.tzinfo is None:
8+
data = data.replace(tzinfo=timezone.utc)
9+
else:
10+
data = data.astimezone(timezone.utc)
11+
return data.strftime("%Y-%m-%dT%H:%M:%SZ")
12+
13+
14+
def datetime_from_string(value: str) -> datetime | None:
15+
"""
16+
Parse common ISO8601 datetime strings and return a timezone-aware datetime.
17+
Accepts:
18+
- 2025-12-17T12:34:56Z
19+
- 2025-12-17T12:34:56+02:00
20+
- 2025-12-17T12:34:56+0200
21+
If no timezone is present, returns a UTC-aware datetime (assumption).
22+
Returns None if parsing fails.
23+
"""
24+
25+
if isinstance(value, datetime):
26+
# If timezone-naive, assume UTC
27+
if value.tzinfo is None:
28+
value = value.replace(tzinfo=timezone.utc)
29+
return value
30+
31+
if not isinstance(value, str):
32+
return None
33+
34+
s = value.strip()
35+
# trailing Z -> +00:00
36+
if s.endswith("Z"):
37+
s = s[:-1] + "+00:00"
38+
39+
# normalize +0200 -> +02:00
40+
s = re.sub(r"([+-]\d{2})(\d{2})$", r"\1:\2", s)
41+
42+
# Try stdlib (requires offset with colon to return aware dt)
43+
try:
44+
dt = datetime.fromisoformat(s)
45+
except Exception:
46+
dt = None
47+
48+
if dt is None:
49+
return None
50+
51+
# If dt is naive, assume UTC
52+
if dt.tzinfo is None:
53+
dt = dt.replace(tzinfo=timezone.utc)
54+
55+
return dt

0 commit comments

Comments
 (0)