Skip to content

Commit 62d479b

Browse files
authored
fix: improve ISO datetime parsing (#404)
1 parent 755efa7 commit 62d479b

File tree

5 files changed

+35
-7
lines changed

5 files changed

+35
-7
lines changed

openviking/core/context.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Any, Dict, List, Optional
88
from uuid import uuid4
99

10-
from openviking.utils.time_utils import format_iso8601
10+
from openviking.utils.time_utils import format_iso8601, parse_iso_datetime
1111
from openviking_cli.session.user_id import UserIdentifier
1212

1313

@@ -199,12 +199,12 @@ def from_dict(cls, data: Dict[str, Any]) -> "Context":
199199
context_type=data.get("context_type"),
200200
category=data.get("category"),
201201
created_at=(
202-
datetime.fromisoformat(data["created_at"])
202+
parse_iso_datetime(data["created_at"])
203203
if isinstance(data.get("created_at"), str)
204204
else data.get("created_at")
205205
),
206206
updated_at=(
207-
datetime.fromisoformat(data["updated_at"])
207+
parse_iso_datetime(data["updated_at"])
208208
if isinstance(data.get("updated_at"), str)
209209
else data.get("updated_at")
210210
),

openviking/message/message.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import List, Literal, Optional
1212

1313
from openviking.message.part import ContextPart, Part, TextPart, ToolPart
14-
from openviking.utils.time_utils import format_iso8601
14+
from openviking.utils.time_utils import format_iso8601, parse_iso_datetime
1515

1616

1717
@dataclass
@@ -108,7 +108,7 @@ def from_dict(cls, data: dict) -> "Message":
108108
id=data["id"],
109109
role=data["role"],
110110
parts=parts,
111-
created_at=datetime.fromisoformat(data["created_at"]),
111+
created_at=parse_iso_datetime(data["created_at"]),
112112
)
113113

114114
@classmethod

openviking/retrieve/hierarchical_retriever.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from openviking.server.identity import RequestContext, Role
1717
from openviking.storage import VikingVectorIndexBackend
1818
from openviking.storage.viking_fs import get_viking_fs
19+
from openviking.utils.time_utils import parse_iso_datetime
1920
from openviking_cli.retrieve.types import (
2021
ContextType,
2122
MatchedContext,
@@ -398,7 +399,7 @@ async def _convert_to_matched_contexts(
398399
updated_at_raw = c.get("updated_at")
399400
if isinstance(updated_at_raw, str):
400401
try:
401-
updated_at_val = datetime.fromisoformat(updated_at_raw)
402+
updated_at_val = parse_iso_datetime(updated_at_raw)
402403
except (ValueError, TypeError):
403404
updated_at_val = None
404405
elif isinstance(updated_at_raw, datetime):

openviking/utils/time_utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ def parse_iso_datetime(value: str) -> datetime:
1212
where the fractional seconds exceed Python's 6-digit microsecond limit.
1313
This helper truncates the excess digits before parsing.
1414
"""
15-
return datetime.fromisoformat(_EXCESS_FRAC_RE.sub(r"\1", value))
15+
normalized = _EXCESS_FRAC_RE.sub(r"\1", value)
16+
if normalized.endswith("Z"):
17+
normalized = normalized[:-1] + "+00:00"
18+
return datetime.fromisoformat(normalized)
1619

1720

1821
def format_iso8601(dt: datetime) -> str:

tests/unit/test_time_utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from datetime import timezone
2+
3+
from openviking.core.context import Context
4+
from openviking.utils.time_utils import parse_iso_datetime
5+
6+
7+
def test_parse_iso_datetime_accepts_z_suffix():
8+
dt = parse_iso_datetime("2026-03-03T01:26:14.481Z")
9+
assert dt.tzinfo is not None
10+
assert dt.utcoffset() == timezone.utc.utcoffset(dt)
11+
12+
13+
def test_context_from_dict_accepts_z_timestamps():
14+
ctx = Context.from_dict(
15+
{
16+
"uri": "viking://user/default/memories/entities/mem_x.md",
17+
"created_at": "2026-03-03T01:26:14.481Z",
18+
"updated_at": "2026-03-03T01:27:14.481Z",
19+
"is_leaf": True,
20+
"context_type": "memory",
21+
}
22+
)
23+
assert ctx.created_at is not None
24+
assert ctx.updated_at is not None

0 commit comments

Comments
 (0)