From 0bc5384b8090bd9ca2646a84803269bafd2d211a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:35:59 +0000 Subject: [PATCH 1/4] Initial plan From 6ec27c20a41e80a01beac4a488b6cc422c48a09c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:40:29 +0000 Subject: [PATCH 2/4] Use UTC (Z) for timestamps to avoid ambiguity Co-authored-by: keighrim <9062727+keighrim@users.noreply.github.com> --- VERSION | 1 + mmif/serialize/mmif.py | 4 ++-- mmif/serialize/model.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..4feaf3b9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0.dev1 diff --git a/mmif/serialize/mmif.py b/mmif/serialize/mmif.py index 68478c7f..245c96aa 100644 --- a/mmif/serialize/mmif.py +++ b/mmif/serialize/mmif.py @@ -14,7 +14,7 @@ import math import warnings from collections import defaultdict -from datetime import datetime +from datetime import datetime, timezone from typing import Any, List, Union, Optional, Dict, cast, Iterator import jsonschema.validators @@ -433,7 +433,7 @@ def new_view(self) -> View: """ new_view = View() new_view.id = self.new_view_id() - new_view.metadata.timestamp = datetime.now() + new_view.metadata.timestamp = datetime.now(timezone.utc) self.add_view(new_view) return new_view diff --git a/mmif/serialize/model.py b/mmif/serialize/model.py index 1bec7b29..6f26b8eb 100644 --- a/mmif/serialize/model.py +++ b/mmif/serialize/model.py @@ -402,7 +402,9 @@ def default(self, obj: 'MmifObject'): if hasattr(obj, '_serialize'): return obj._serialize() elif hasattr(obj, 'isoformat'): # for datetime objects - return obj.isoformat() + iso_str = obj.isoformat() + # Use 'Z' suffix for UTC timestamps instead of '+00:00' + return iso_str.replace('+00:00', 'Z') elif hasattr(obj, '__str__'): return str(obj) else: From 9881e4014f5f283c90c953afbbb909423abd40a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:43:29 +0000 Subject: [PATCH 3/4] Add test for UTC timestamp with Z suffix Co-authored-by: keighrim <9062727+keighrim@users.noreply.github.com> --- VERSION | 1 - tests/test_serialize.py | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index 4feaf3b9..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.0.dev1 diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 9e857a00..11b06d87 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -608,6 +608,33 @@ def test_get_label(self): a = v.new_annotation(AnnotationTypes.BoundingBox) _ = a._get_label() + def test_timestamp_uses_utc_with_z_suffix(self): + """Test that timestamps are in UTC with 'Z' suffix to avoid ambiguity""" + from datetime import timezone + mmif_str = ''' + { + "metadata": {"mmif": "http://mmif.clams.ai/1.0.0"}, + "documents": [], + "views": [] + }''' + mmif_obj = Mmif(mmif_str) + + # Create a new view using new_view() - should set UTC timestamp + new_view = mmif_obj.new_view() + new_view.metadata.app = "http://test.app" + + # Verify the timestamp is timezone-aware and uses UTC + self.assertIsNotNone(new_view.metadata.timestamp) + self.assertIsNotNone(new_view.metadata.timestamp.tzinfo) + self.assertEqual(new_view.metadata.timestamp.tzinfo, timezone.utc) + + # Verify serialization uses 'Z' suffix instead of '+00:00' + serialized = mmif_obj.serialize() + self.assertIn('"timestamp":', serialized) + # The timestamp should end with Z (not +00:00) + self.assertIn('Z"', serialized) + self.assertNotIn('+00:00', serialized) + def test_get_anchor_point(self): mmif = Mmif(validate=False) v1 = mmif.new_view() From b1eca88a512b7c274d54e1b9f7a93b62ff06a282 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:45:23 +0000 Subject: [PATCH 4/4] Improve encoder to only use Z for explicit UTC timezone Co-authored-by: keighrim <9062727+keighrim@users.noreply.github.com> --- mmif/serialize/model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mmif/serialize/model.py b/mmif/serialize/model.py index 6f26b8eb..f7c56b3f 100644 --- a/mmif/serialize/model.py +++ b/mmif/serialize/model.py @@ -23,7 +23,7 @@ import json import warnings -from datetime import datetime +from datetime import datetime, timezone from typing import Union, Any, Dict, Optional, TypeVar, Generic, Generator, Iterator, Type, Set, ClassVar, List T = TypeVar('T') @@ -404,7 +404,10 @@ def default(self, obj: 'MmifObject'): elif hasattr(obj, 'isoformat'): # for datetime objects iso_str = obj.isoformat() # Use 'Z' suffix for UTC timestamps instead of '+00:00' - return iso_str.replace('+00:00', 'Z') + # Only replace if the datetime is explicitly in UTC timezone + if hasattr(obj, 'tzinfo') and obj.tzinfo == timezone.utc: + return iso_str.replace('+00:00', 'Z') + return iso_str elif hasattr(obj, '__str__'): return str(obj) else: