diff --git a/src/ni/datastore/data/_grpc_conversion.py b/src/ni/datastore/data/_grpc_conversion.py index c87fbd8..5dd6c14 100644 --- a/src/ni/datastore/data/_grpc_conversion.py +++ b/src/ni/datastore/data/_grpc_conversion.py @@ -58,6 +58,11 @@ _logger = logging.getLogger(__name__) +def _ensure_utc(dt: ht.datetime) -> ht.datetime: + """Convert a datetime to UTC, treating naive datetimes as local time.""" + return dt.astimezone(std_datetime.timezone.utc) + + def populate_publish_condition_request_value( publish_request: PublishConditionRequest, value: object ) -> None: diff --git a/src/ni/datastore/data/_types/_step.py b/src/ni/datastore/data/_types/_step.py index 4bc598f..b2da8de 100644 --- a/src/ni/datastore/data/_types/_step.py +++ b/src/ni/datastore/data/_types/_step.py @@ -5,6 +5,7 @@ from typing import Mapping, MutableMapping import hightime as ht +from ni.datastore.data._grpc_conversion import _ensure_utc from ni.datastore.data._types._error_information import ErrorInformation from ni.datastore.data._types._outcome import Outcome from ni.datastore.metadata._grpc_conversion import ( @@ -147,12 +148,14 @@ def to_protobuf(self) -> StepProto: step_type=self.step_type, notes=self.notes, start_date_time=( - hightime_datetime_to_protobuf(self.start_date_time) + hightime_datetime_to_protobuf(_ensure_utc(self.start_date_time)) if self.start_date_time else None ), end_date_time=( - hightime_datetime_to_protobuf(self.end_date_time) if self.end_date_time else None + hightime_datetime_to_protobuf(_ensure_utc(self.end_date_time)) + if self.end_date_time + else None ), link=self.link, schema_id=self.schema_id, diff --git a/src/ni/datastore/data/_types/_test_result.py b/src/ni/datastore/data/_types/_test_result.py index 6bc9cda..6a41eef 100644 --- a/src/ni/datastore/data/_types/_test_result.py +++ b/src/ni/datastore/data/_types/_test_result.py @@ -5,6 +5,7 @@ from typing import Iterable, Mapping, MutableMapping, MutableSequence import hightime as ht +from ni.datastore.data._grpc_conversion import _ensure_utc from ni.datastore.data._types._error_information import ErrorInformation from ni.datastore.data._types._outcome import Outcome from ni.datastore.metadata._grpc_conversion import ( @@ -182,12 +183,14 @@ def to_protobuf(self) -> TestResultProto: test_adapter_ids=self.test_adapter_ids, name=self.name, start_date_time=( - hightime_datetime_to_protobuf(self.start_date_time) + hightime_datetime_to_protobuf(_ensure_utc(self.start_date_time)) if self.start_date_time else None ), end_date_time=( - hightime_datetime_to_protobuf(self.end_date_time) if self.end_date_time else None + hightime_datetime_to_protobuf(_ensure_utc(self.end_date_time)) + if self.end_date_time + else None ), outcome=self.outcome.to_protobuf(), link=self.link, diff --git a/tests/unit/data/test_create_metadata.py b/tests/unit/data/test_create_metadata.py index 04b5112..c29dde2 100644 --- a/tests/unit/data/test_create_metadata.py +++ b/tests/unit/data/test_create_metadata.py @@ -2,20 +2,24 @@ from __future__ import annotations +import datetime as std_datetime from typing import cast from unittest.mock import NonCallableMock +import hightime as ht from ni.datastore.data import ( DataStoreClient, Step, TestResult, ) +from ni.datastore.data._types._outcome import Outcome from ni.measurements.data.v1.data_store_service_pb2 import ( CreateStepRequest, CreateStepResponse, CreateTestResultRequest, CreateTestResultResponse, ) +from ni.protobuf.types.precision_timestamp_conversion import hightime_datetime_to_protobuf def test___create_step___calls_data_store_service_client( @@ -66,3 +70,77 @@ def test___create_test_result___calls_data_store_service_client( request = cast(CreateTestResultRequest, args[0]) assert request.test_result == test_result.to_protobuf() assert result == "response_id" + + +def test___test_result_to_protobuf___naive_datetime___converts_to_utc() -> None: + naive_start = ht.datetime(2024, 6, 15, 10, 30, 0) + naive_end = ht.datetime(2024, 6, 15, 11, 0, 0) + test_result = TestResult( + name="test", + start_date_time=naive_start, + end_date_time=naive_end, + outcome=Outcome.PASSED, + ) + + proto = test_result.to_protobuf() + + expected_start = hightime_datetime_to_protobuf( + naive_start.astimezone(std_datetime.timezone.utc) + ) + expected_end = hightime_datetime_to_protobuf(naive_end.astimezone(std_datetime.timezone.utc)) + assert proto.start_date_time == expected_start + assert proto.end_date_time == expected_end + + +def test___test_result_to_protobuf___utc_datetime___preserved() -> None: + utc_start = ht.datetime(2024, 6, 15, 10, 30, 0, tzinfo=std_datetime.timezone.utc) + utc_end = ht.datetime(2024, 6, 15, 11, 0, 0, tzinfo=std_datetime.timezone.utc) + test_result = TestResult( + name="test", + start_date_time=utc_start, + end_date_time=utc_end, + outcome=Outcome.PASSED, + ) + + proto = test_result.to_protobuf() + + expected_start = hightime_datetime_to_protobuf(utc_start) + expected_end = hightime_datetime_to_protobuf(utc_end) + assert proto.start_date_time == expected_start + assert proto.end_date_time == expected_end + + +def test___step_to_protobuf___naive_datetime___converts_to_utc() -> None: + naive_start = ht.datetime(2024, 6, 15, 10, 30, 0) + naive_end = ht.datetime(2024, 6, 15, 11, 0, 0) + step = Step( + name="step", + start_date_time=naive_start, + end_date_time=naive_end, + ) + + proto = step.to_protobuf() + + expected_start = hightime_datetime_to_protobuf( + naive_start.astimezone(std_datetime.timezone.utc) + ) + expected_end = hightime_datetime_to_protobuf(naive_end.astimezone(std_datetime.timezone.utc)) + assert proto.start_date_time == expected_start + assert proto.end_date_time == expected_end + + +def test___step_to_protobuf___utc_datetime___preserved() -> None: + utc_start = ht.datetime(2024, 6, 15, 10, 30, 0, tzinfo=std_datetime.timezone.utc) + utc_end = ht.datetime(2024, 6, 15, 11, 0, 0, tzinfo=std_datetime.timezone.utc) + step = Step( + name="step", + start_date_time=utc_start, + end_date_time=utc_end, + ) + + proto = step.to_protobuf() + + expected_start = hightime_datetime_to_protobuf(utc_start) + expected_end = hightime_datetime_to_protobuf(utc_end) + assert proto.start_date_time == expected_start + assert proto.end_date_time == expected_end