Skip to content

Commit 0ca5ae9

Browse files
Implementation for to-zoned-date-time generic
1 parent 150cba6 commit 0ca5ae9

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

src/rune/runtime/utils.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import logging
44
import keyword
55
import inspect
6+
import re
67
from enum import Enum
78
from typing import Callable, Any
9+
from zoneinfo import ZoneInfo
10+
from datetime import datetime
811

912
__all__ = [
1013
'if_cond_fn', 'Multiprop', 'rune_any_elements', 'rune_get_only_element',
1114
'rune_filter', 'rune_all_elements', 'rune_contains', 'rune_disjoint',
1215
'rune_join', 'rune_flatten_list', 'rune_resolve_attr',
1316
'rune_resolve_deep_attr', 'rune_count', 'rune_attr_exists',
1417
'_get_rune_object', 'rune_set_attr', 'rune_add_attr',
15-
'rune_check_cardinality', 'rune_str', 'rune_check_one_of'
18+
'rune_check_cardinality', 'rune_str', 'rune_check_one_of','rune_zoned_date_time'
1619
]
1720

1821

@@ -133,6 +136,47 @@ def rune_str(x: Any) -> str:
133136
return str(x)
134137

135138

139+
def rune_zoned_date_time(x: str):
140+
"""
141+
Parse a datetime string with optional offset and/or named time zone
142+
"""
143+
#Separate str in parts (date, time , offset, zone)
144+
parts = x.strip().split()
145+
146+
# Try parsing last part as a zone name
147+
possible_tz = parts[-1]
148+
try:
149+
zone = ZoneInfo(possible_tz)
150+
parts = parts[:-1] # Remove the zone name from the string
151+
except:
152+
zone = None
153+
154+
# Datetime object with date, time and offset
155+
cleaned_input = " ".join(parts)
156+
157+
# Parse datetime
158+
try:
159+
dt = datetime.strptime(cleaned_input, "%Y-%m-%d %H:%M:%S %z")
160+
has_offset = True
161+
except ValueError:
162+
dt = datetime.strptime(cleaned_input, "%Y-%m-%d %H:%M:%S")
163+
has_offset = False
164+
165+
# Apply zone if valid
166+
if zone:
167+
if has_offset:
168+
input_offset = dt.utcoffset()
169+
zone_offset = dt.replace(tzinfo=zone).utcoffset()
170+
if input_offset != zone_offset:
171+
raise ValueError(
172+
f"Offset {input_offset} does not match zone '{zone.key}' ({zone_offset})"
173+
)
174+
dt = dt.replace(tzinfo=zone)
175+
176+
return dt
177+
178+
179+
136180
def _get_rune_object(base_model: str, attribute: str, value: Any) -> Any:
137181
model_class = globals()[base_model]
138182
instance_kwargs = {attribute: value}

0 commit comments

Comments
 (0)