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..95fdc28c 100644 --- a/mmif/serialize/model.py +++ b/mmif/serialize/model.py @@ -402,7 +402,10 @@ def default(self, obj: 'MmifObject'): if hasattr(obj, '_serialize'): return obj._serialize() elif hasattr(obj, 'isoformat'): # for datetime objects - return obj.isoformat() + s = obj.isoformat() + if s.endswith('+00:00'): + s = s[:-6] + 'Z' + return s elif hasattr(obj, '__str__'): return str(obj) else: diff --git a/tests/test_serialize.py b/tests/test_serialize.py index 9e857a00..f5b0846f 100644 --- a/tests/test_serialize.py +++ b/tests/test_serialize.py @@ -608,6 +608,25 @@ 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_obj = Mmif(validate=False) + + 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 = json.loads(mmif_obj.serialize()) + ts = serialized['views'][0]['metadata']['timestamp'] + self.assertTrue(ts.endswith('Z')) + self.assertNotIn('+00:00', ts) + def test_get_anchor_point(self): mmif = Mmif(validate=False) v1 = mmif.new_view()