Skip to content

Commit 101d467

Browse files
authored
Merge pull request #1753 from weaviate/date-ms
handle date with nanoseconds
2 parents c15cf5a + 0e409b9 commit 101d467

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed

test/test_util.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
import uuid as uuid_lib
33
from copy import deepcopy
4+
from datetime import datetime, timedelta, timezone
45
from unittest.mock import Mock, patch
56

67
import pytest
@@ -9,6 +10,7 @@
910
from weaviate.exceptions import SchemaValidationException
1011
from weaviate.util import (
1112
MINIMUM_NO_WARNING_VERSION,
13+
_datetime_from_weaviate_str,
1214
_is_sub_schema,
1315
_sanitize_str,
1416
generate_uuid5,
@@ -431,6 +433,37 @@ def test_is_weaviate_too_old(version: str, too_old: bool):
431433
assert is_weaviate_too_old(version) is too_old
432434

433435

436+
@pytest.mark.parametrize(
437+
"input_str,expected",
438+
[
439+
# Test parsing with microseconds and Z timezone
440+
(
441+
"2023-01-15T14:30:45.123456Z",
442+
datetime(2023, 1, 15, 14, 30, 45, 123456, tzinfo=timezone.utc),
443+
),
444+
# Test parsing without microseconds
445+
(
446+
"2023-01-15T14:30:45Z",
447+
datetime(2023, 1, 15, 14, 30, 45, 0, tzinfo=timezone.utc),
448+
),
449+
# Test parsing with offset timezone
450+
(
451+
"2023-01-15T14:30:45.123456+02:00",
452+
datetime(2023, 1, 15, 14, 30, 45, 123456, tzinfo=timezone(timedelta(hours=2))),
453+
),
454+
# Test truncating excess microseconds
455+
(
456+
"2023-01-15T14:30:45.123456789Z",
457+
datetime(2023, 1, 15, 14, 30, 45, 123456, tzinfo=timezone.utc),
458+
),
459+
# Test handling year 0 (should return datetime.min)
460+
("0000-01-15T14:30:45.123456Z", datetime.min),
461+
],
462+
)
463+
def test_datetime_from_weaviate_str(input_str: str, expected: datetime) -> None:
464+
assert _datetime_from_weaviate_str(input_str) == expected
465+
466+
434467
@pytest.mark.parametrize(
435468
"current_version,latest_version,too_old",
436469
[

weaviate/collections/queries/base_executor.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,7 @@ def __deserialize_non_ref_prop(self, value: properties_pb2.Value) -> Any:
270270
if value.HasField("uuid_value"):
271271
return uuid_lib.UUID(value.uuid_value)
272272
if value.HasField("date_value"):
273-
try:
274-
return _datetime_from_weaviate_str(value.date_value)
275-
except ValueError as e:
276-
# note that the year 9999 is valid and does not need to be handled. for 5 digit years only the first
277-
# 4 digits are considered and it wrapps around
278-
if "year 0 is out of range" in str(e):
279-
_Warnings.datetime_year_zero(value.date_value)
280-
return datetime.datetime.min
281-
273+
return _datetime_from_weaviate_str(value.date_value)
282274
if value.HasField("string_value"):
283275
return str(value.string_value)
284276
if value.HasField("text_value"):

weaviate/util.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -716,16 +716,23 @@ def _datetime_to_string(value: TIME) -> str:
716716

717717

718718
def _datetime_from_weaviate_str(string: str) -> datetime.datetime:
719+
if string[-1] != "Z":
720+
string = "".join(string.rsplit(":", 1))
721+
722+
# Weaviate can return up to 9 digits for milliseconds, but Python datetime only supports 6 digits.
723+
string = re.sub(r"(?<=\.\d{6})\d+(?=[Z+-])", "", string)
724+
# pick format with or without microseconds
725+
date_format = "%Y-%m-%dT%H:%M:%S.%f%z" if "." in string else "%Y-%m-%dT%H:%M:%S%z"
726+
719727
try:
720-
return datetime.datetime.strptime(
721-
"".join(string.rsplit(":", 1) if string[-1] != "Z" else string),
722-
"%Y-%m-%dT%H:%M:%S.%f%z",
723-
)
724-
except ValueError: # if the string does not have microseconds
725-
return datetime.datetime.strptime(
726-
"".join(string.rsplit(":", 1) if string[-1] != "Z" else string),
727-
"%Y-%m-%dT%H:%M:%S%z",
728-
)
728+
return datetime.datetime.strptime(string, date_format)
729+
except ValueError as e:
730+
# note that the year 9999 is valid and does not need to be handled. for 5 digit years only the first
731+
# 4 digits are considered and it wrapps around
732+
if "year 0 is out of range" in str(e):
733+
_Warnings.datetime_year_zero(string)
734+
return datetime.datetime.min
735+
raise e
729736

730737

731738
class _WeaviateUUIDInt(uuid_lib.UUID):

0 commit comments

Comments
 (0)