You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/09/28 16:25:20 UTC

(tooling-trusted-releases) branch sbp updated: Preserve timezone information across models

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 191367f7 Preserve timezone information across models
191367f7 is described below

commit 191367f72cf7dd34b50288d4292fa7fbf15e6c23
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Mon Sep 28 17:24:28 2026 +0100

    Preserve timezone information across models
---
 atr/models/sql.py                  | 24 ++++++++++----------
 tests/unit/test_model_datetimes.py | 46 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 12 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index 193f785d..d70007d0 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -911,13 +911,13 @@ class Task(sqlmodel.SQLModel, table=True):
             self.status = TaskStatus(self.status)
 
         if isinstance(self.added, str):
-            self.added = datetime.datetime.fromisoformat(self.added.rstrip("Z"))
+            self.added = datetime.datetime.fromisoformat(self.added)
 
         if isinstance(self.started, str):
-            self.started = datetime.datetime.fromisoformat(self.started.rstrip("Z"))
+            self.started = datetime.datetime.fromisoformat(self.started)
 
         if isinstance(self.completed, str):
-            self.completed = datetime.datetime.fromisoformat(self.completed.rstrip("Z"))
+            self.completed = datetime.datetime.fromisoformat(self.completed)
 
         if isinstance(self.result, dict):
             self.result = results.ResultsAdapter.validate_python(self.result)
@@ -1803,7 +1803,7 @@ class Release(sqlmodel.SQLModel, table=True):
         for name in ("activity_at", "archived", "created", "released", "vote_resolved", "vote_started"):
             value = getattr(self, name)
             if isinstance(value, str):
-                setattr(self, name, datetime.datetime.fromisoformat(value.rstrip("Z")))
+                setattr(self, name, datetime.datetime.fromisoformat(value))
 
         if isinstance(self.phase, str):
             self.phase = ReleasePhase(self.phase)
@@ -1939,7 +1939,7 @@ class BallotPaper(sqlmodel.SQLModel, table=True):
             self.choice = VoteChoice(self.choice)
 
         if isinstance(self.created, str):
-            self.created = datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+            self.created = datetime.datetime.fromisoformat(self.created)
 
     @property
     def safe_revision_number_at_cast(self) -> safe.RevisionNumber:
@@ -2020,7 +2020,7 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
-            self.created = datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+            self.created = datetime.datetime.fromisoformat(self.created)
 
 
 # Distribution: Release
@@ -2128,7 +2128,7 @@ class SigningCertificate(sqlmodel.SQLModel, table=True):
 
     def model_post_init(self, _context):
         if isinstance(self.latest_self_signature, str):
-            self.latest_self_signature = datetime.datetime.fromisoformat(self.latest_self_signature.rstrip("Z"))
+            self.latest_self_signature = datetime.datetime.fromisoformat(self.latest_self_signature)
 
 
 # SigningKey: SigningCertificate
@@ -2168,10 +2168,10 @@ class SigningKey(sqlmodel.SQLModel, table=True):
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
-            self.created = datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+            self.created = datetime.datetime.fromisoformat(self.created)
 
         if isinstance(self.expires, str):
-            self.expires = datetime.datetime.fromisoformat(self.expires.rstrip("Z"))
+            self.expires = datetime.datetime.fromisoformat(self.expires)
 
 
 # Quarantined: Release
@@ -2212,10 +2212,10 @@ class Quarantined(sqlmodel.SQLModel, table=True):
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
-            self.created = datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+            self.created = datetime.datetime.fromisoformat(self.created)
 
         if isinstance(self.completed, str):
-            self.completed = datetime.datetime.fromisoformat(self.completed.rstrip("Z"))
+            self.completed = datetime.datetime.fromisoformat(self.completed)
 
         if isinstance(self.status, str):
             self.status = QuarantineStatus(self.status)
@@ -2446,7 +2446,7 @@ class Revision(sqlmodel.SQLModel, table=True):
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
-            self.created = datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+            self.created = datetime.datetime.fromisoformat(self.created)
 
         if isinstance(self.phase, str):
             self.phase = ReleasePhase(self.phase)
diff --git a/tests/unit/test_model_datetimes.py b/tests/unit/test_model_datetimes.py
new file mode 100644
index 00000000..8bc72e42
--- /dev/null
+++ b/tests/unit/test_model_datetimes.py
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+import pytest
+import sqlmodel
+
+import atr.models.sql as sql
+
+
+@pytest.mark.parametrize(
+    ("model", "fields"),
+    [
+        (sql.BallotPaper, ("created",)),
+        (sql.CheckResultIgnore, ("created",)),
+        (sql.Quarantined, ("created", "completed")),
+        (sql.Release, ("activity_at", "archived", "created", "released", "vote_resolved", "vote_started")),
+        (sql.Revision, ("created",)),
+        (sql.SigningCertificate, ("latest_self_signature",)),
+        (sql.SigningKey, ("created", "expires")),
+        (sql.Task, ("added", "started", "completed")),
+    ],
+)
+@pytest.mark.parametrize("timestamp", ["2026-09-28T12:34:56.123456Z", "2026-09-28T14:34:56.123456+02:00"])
+def test_model_datetime_strings_preserve_timezone(
+    model: type[sqlmodel.SQLModel], fields: tuple[str, ...], timestamp: str
+) -> None:
+    record = model(**dict.fromkeys(fields, timestamp))
+    expected = datetime.datetime(2026, 9, 28, 12, 34, 56, 123456, tzinfo=datetime.UTC)
+    for field in fields:
+        assert getattr(record, field) == expected


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org