diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a2243ed..bbbc7484f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,10 @@ to include examples, links to docs, or any other relevant information. ### Fixed +- Encoding a datetime search attribute without a timezone now raises + `ValueError("Timezone must be present on all search attribute dates")` on + the typed path, matching the deprecated untyped encoder, instead of sending + a naive ISO string that the server rejects with `BadSearchAttributes`. - `GoogleAdkPlugin` now passes the optional `anthropic`, `litellm`, and `openai` SDKs through the workflow sandbox. - `contrib.deepagents`: prevent duplicate input messages after continue-as-new. diff --git a/temporalio/converter/_search_attributes.py b/temporalio/converter/_search_attributes.py index 4ec154d6f..4a8191bbd 100644 --- a/temporalio/converter/_search_attributes.py +++ b/temporalio/converter/_search_attributes.py @@ -61,6 +61,8 @@ def encode_typed_search_attribute_value( ) # datetime needs to be in isoformat if isinstance(value, datetime): + if value.tzinfo is None: + raise ValueError("Timezone must be present on all search attribute dates") value = value.isoformat() # We'll do an extra sanity check for keyword list and check every value if isinstance(value, Sequence): diff --git a/tests/test_converter.py b/tests/test_converter.py index 4c64e25ba..4d791c342 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -47,6 +47,7 @@ create_payload_validation_error, decode_search_attributes, encode_search_attribute_values, + encode_typed_search_attribute_value, value_to_type, ) from temporalio.exceptions import ( @@ -285,6 +286,18 @@ def test_encode_search_attribute_values(): encode_search_attribute_values(["foo", 123]) # type: ignore[arg-type] +def test_encode_typed_search_attribute_value_datetime_requires_timezone(): + key = temporalio.common.SearchAttributeKey.for_datetime("checkout_time") + with pytest.raises(ValueError, match="Timezone must be present"): + encode_typed_search_attribute_value( + key, datetime(2024, 7, 5, 15, 43, 7, 875302) + ) + payload = encode_typed_search_attribute_value( + key, datetime(2024, 7, 5, 15, 43, 7, 875302, tzinfo=timezone.utc) + ) + assert payload.metadata["type"] == b"Datetime" + + def test_decode_search_attributes(): """Tests decode from protobuf for python types"""