Repository: plone/plone.restapi (language: python)

Issue:
time is returned without timezone specifier, causing the client to not use local time
The api returns time information of the entries without a timezone specifier.  See https://stackoverflow.com/questions/71024015/how-to-get-timezone-from-iso-time-string-in-python for a deeper explanation.

The lack of the timezone specifier causes Intl (and derivative libraries such as  the `FormattedDate` component  of `react-intl`) in the browser not to use local time, which is disturbing for the user who accepts local time. Thus this is considered a bug in applications. 

To solve the problem we can add the timezone specifier to the end of the datetime string. Since the python timestamp does not contain the information about the originating timezone itself, this is always UTC and we can simply add Z to the end in all cases. This ensures that the date will be interpreted properly by the client.


Discussion / hints:

Fix patch:
diff --git a/news/1530.bugfix b/news/1530.bugfix
new file mode 100644
index 0000000000..451acb4cfc
--- /dev/null
+++ b/news/1530.bugfix
@@ -0,0 +1,1 @@
+Fix time to be returned with a timezone specifier in history endpoint @reebalazs
\ No newline at end of file
diff --git a/src/plone/restapi/services/history/get.py b/src/plone/restapi/services/history/get.py
index 22095e6154..d4543eb411 100644
--- a/src/plone/restapi/services/history/get.py
+++ b/src/plone/restapi/services/history/get.py
@@ -71,7 +71,13 @@ def reply(self):
             # Versioning entries use a timestamp,
             # workflow ISO formatted string
             if not isinstance(item["time"], str):
-                item["time"] = dt.fromtimestamp(int(item["time"])).isoformat()
+                # Note that isoformat does not add the Z at the end of the string, The lack of the
+                # timezone specifier causes Intl (and derivative libraries) in the browser not to
+                # use local time, which is considered a bug in applications. Therefore we add the Z
+                # to the end to make sure that the date will be interpreted properly by the client.
+                item["time"] = dt.fromtimestamp(int(item["time"])).strftime(
+                    "%Y-%m-%dT%H:%M:%SZ"
+                )
 
             # The create event has an empty 'action', but we like it to say
             # 'Create', alike the transition_title


Test patch:
diff --git a/src/plone/restapi/tests/http-examples/history_get.resp b/src/plone/restapi/tests/http-examples/history_get.resp
index de29a07dc9..7819c1085f 100644
--- a/src/plone/restapi/tests/http-examples/history_get.resp
+++ b/src/plone/restapi/tests/http-examples/history_get.resp
@@ -13,7 +13,7 @@ Content-Type: application/json
         },
         "comments": "Initial version",
         "may_revert": true,
-        "time": "1995-07-31T17:30:00",
+        "time": "1995-07-31T17:30:00Z",
         "transition_title": "Edited",
         "type": "versioning",
         "version": 0
@@ -29,7 +29,7 @@ Content-Type: application/json
         "comments": "",
         "review_state": "private",
         "state_title": "Private",
-        "time": "1995-07-31T18:30:00",
+        "time": "1995-07-31T18:30:00Z",
         "transition_title": "Create",
         "type": "workflow"
     }
diff --git a/src/plone/restapi/tests/http-examples/translated_messages_addons.resp b/src/plone/restapi/tests/http-examples/translated_messages_addons.resp
index b310a87fdf..5af758c196 100644
--- a/src/plone/restapi/tests/http-examples/translated_messages_addons.resp
+++ b/src/plone/restapi/tests/http-examples/translated_messages_addons.resp
@@ -73,7 +73,7 @@ Content-Type: application/json
                 "newVersion": "0006",
                 "required": false
             },
-            "version": "8.31.1.dev0"
+            "version": "8.32.1.dev0"
         },
         {
             "@id": "http://localhost:55001/plone/@addons/plone.session",
diff --git a/src/plone/restapi/tests/http-examples/translated_messages_object_history.resp b/src/plone/restapi/tests/http-examples/translated_messages_object_history.resp
index ae005d8778..d691c98e3a 100644
--- a/src/plone/restapi/tests/http-examples/translated_messages_object_history.resp
+++ b/src/plone/restapi/tests/http-examples/translated_messages_object_history.resp
@@ -13,7 +13,7 @@ Content-Type: application/json
         },
         "comments": "Versi\u00f3n inicial",
         "may_revert": true,
-        "time": "1995-07-31T17:30:00",
+        "time": "1995-07-31T17:30:00Z",
         "transition_title": "Editado",
         "type": "versioning",
         "version": 0
@@ -29,7 +29,7 @@ Content-Type: application/json
         "comments": "",
         "review_state": "private",
         "state_title": "Privado",
-        "time": "1995-07-31T18:30:00",
+        "time": "1995-07-31T18:30:00Z",
         "transition_title": "Crear",
         "type": "workflow"
     }