diff --git a/api/api_specification.md b/api/api_specification.md
index b26b976cd71054746922392a2aaadfd1a1f3a220..3be120b485e5913db752aaae3b1a32a7255b435c 100644
--- a/api/api_specification.md
+++ b/api/api_specification.md
@@ -1,4 +1,4 @@
-# Specification of the Web API for the Video-AG Website (v0.80).
+# Specification of the Web API for the Video-AG Website (v0.81).
 
 ## Introduction
 
@@ -1725,13 +1725,13 @@ Note that views only appear some time after they viewer has watched the video (u
 
 ### Types
 
-| Type     | Notes                                                                                                                                                                                                 |
-|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| int      |                                                                                                                                                                                                       |
-| float    |                                                                                                                                                                                                       |
-| boolean  |                                                                                                                                                                                                       |
-| string   |                                                                                                                                                                                                       |
-| datetime | A string which must be in format `YYYY-MM-ddTHH:mm:ss` (Year, Month, day, Hour, minute, second) (Note that this is a local date time according to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)) |
+| Type     | Notes                                                                                                                                                                                                                                             |
+|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| int      |                                                                                                                                                                                                                                                   |
+| float    |                                                                                                                                                                                                                                                   |
+| boolean  |                                                                                                                                                                                                                                                   |
+| string   |                                                                                                                                                                                                                                                   |
+| datetime | A string which must be in format `YYYY-MM-ddTHH:mm:ss.SSSZ` (Year, Month, day, Hour, minute, second, millisecond, 'T' and 'Z' are literals) (Note that this is a *UTC* date time according to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)) |
 
 Additionally, the following objects may appear as the type of some field:
 
@@ -3188,6 +3188,10 @@ Possible `error_code`:
 
 ## Changelog
 
+### v0.81
+
+* Added milliseconds to `datetime` type and clarified that it is in UTC.
+
 ### v0.80
 
 * Updated `field_description`
diff --git a/api/api_specification_template.md b/api/api_specification_template.md
index 2098757462a0a1311a039a4ac16a51e9938a292b..5b45245f9d0126639743c5c80df0e53f10eb37fa 100644
--- a/api/api_specification_template.md
+++ b/api/api_specification_template.md
@@ -1,4 +1,4 @@
-# Specification of the Web API for the Video-AG Website (v0.80).
+# Specification of the Web API for the Video-AG Website (v0.81).
 
 ## Introduction
 
@@ -87,13 +87,13 @@ object. The object type is specified in the field description and the configurat
 
 ### Types
 
-| Type     | Notes                                                                                                                                                                                                 |
-|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| int      |                                                                                                                                                                                                       |
-| float    |                                                                                                                                                                                                       |
-| boolean  |                                                                                                                                                                                                       |
-| string   |                                                                                                                                                                                                       |
-| datetime | A string which must be in format `YYYY-MM-ddTHH:mm:ss` (Year, Month, day, Hour, minute, second) (Note that this is a local date time according to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)) |
+| Type     | Notes                                                                                                                                                                                                                                             |
+|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| int      |                                                                                                                                                                                                                                                   |
+| float    |                                                                                                                                                                                                                                                   |
+| boolean  |                                                                                                                                                                                                                                                   |
+| string   |                                                                                                                                                                                                                                                   |
+| datetime | A string which must be in format `YYYY-MM-ddTHH:mm:ss.SSSZ` (Year, Month, day, Hour, minute, second, millisecond, 'T' and 'Z' are literals) (Note that this is a *UTC* date time according to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)) |
 
 Additionally, the following objects may appear as the type of some field:
 
@@ -139,6 +139,10 @@ Possible `error_code`:
 
 ## Changelog
 
+### v0.81
+
+* Added milliseconds to `datetime` type and clarified that it is in UTC.
+
 ### v0.80
 
 * Updated `field_description`
diff --git a/api/src/api/authentication.py b/api/src/api/authentication.py
index d4fdb12a28f0edab3f3357328eb3b3393d305613..01eef6d23da579b306c749c74c6aea5790f76072 100644
--- a/api/src/api/authentication.py
+++ b/api/src/api/authentication.py
@@ -235,7 +235,7 @@ def authenticate_fsmpi(username: str, password: str) -> {}:
                 session.add(user_db)
                 session.flush()  # Get autoincrement id
 
-            user_db.last_login = datetime.now()
+            user_db.last_login = get_standard_datetime_now()
             # Update data (unlikely, but might have changed. Also fills 'missing' data after the migration)
             user_db.full_name = full_name
             user_db.display_name = display_name
diff --git a/api/src/api/routes/job.py b/api/src/api/routes/job.py
index a8060bfd942db0a663a8cb0efaa93387f544af96..12886e55da3e00402e8449f1b67e5fcb14e9d6c3 100644
--- a/api/src/api/routes/job.py
+++ b/api/src/api/routes/job.py
@@ -142,8 +142,8 @@ def api_route_get_job_status():
                 job.id: {
                     # TODO This is not really using the serialization API
                     "state": job.state.value,
-                    "run_start_time": None if job.run_start_time is None else job.run_start_time.strftime(DEFAULT_DATETIME_FORMAT),
-                    "run_end_time": None if job.run_end_time is None else job.run_end_time.strftime(DEFAULT_DATETIME_FORMAT),
+                    "run_start_time": None if job.run_start_time is None else format_standard_datetime(job.run_start_time),
+                    "run_end_time": None if job.run_end_time is None else format_standard_datetime(job.run_end_time),
                 }
                 for job in limited_jobs
             }
diff --git a/api/src/api/routes/stats.py b/api/src/api/routes/stats.py
index b529e69c4ee9e92cb2588ef3957b3c539ffe6879..96063d819c43e9aad9f37a86a5bc9efaa19ee67a 100755
--- a/api/src/api/routes/stats.py
+++ b/api/src/api/routes/stats.py
@@ -52,13 +52,13 @@ def api_route_watch_log_publish_medium(course_handle: str, publish_medium_id: in
         raise ApiClientException(ERROR_BAD_REQUEST("User Agent too long"))
     
     try:
-        timestamp = datetime.strptime(timestamp_str, DEFAULT_DATETIME_FORMAT)
+        timestamp = parse_standard_datetime(timestamp_str)
     except ValueError:
         req_json.get("timestamp").raise_error("Invalid timestamp format")
         return
     
     # Allow some variance, but not too much
-    if abs((timestamp - datetime.now()).total_seconds()) > 30:
+    if abs((timestamp - get_standard_datetime_now()).total_seconds()) > 30:
         raise ApiClientException(ERROR_BAD_REQUEST("Invalid timestamp"))
     
     def _trans(session: SessionDb):
diff --git a/api/tests/routes/feedback.py b/api/tests/routes/feedback.py
index 75068c0720dc56decf1d719961178d5c06137e38..a01460c0c13e949e7372d8654fe279a219032f63 100644
--- a/api/tests/routes/feedback.py
+++ b/api/tests/routes/feedback.py
@@ -1,5 +1,3 @@
-import unittest
-
 from videoag_common.miscellaneous.constants import *
 
 from api_test import ApiTest
@@ -7,12 +5,12 @@ from api_test import ApiTest
 # The python json parser does not unescape the escaped strings, so we need to put the extra \ here.
 _TEST_DATA_FEEDBACK_1 = {
     "id": 1,
-    "time_created": "2024-03-18T18:53:02",
+    "time_created": "2024-03-18T18:53:02.000Z",
     "text": "Hier FuNkToNiErT NICHTS!"
 }
 _TEST_DATA_FEEDBACK_2 = {
     "id": 2,
-    "time_created": "2024-03-10T20:11:08",
+    "time_created": "2024-03-10T20:11:08.000Z",
     "email": "no-one@example.com",
     "text": "Ein bisschen Feedback"
 }
diff --git a/api/tests/routes/object_modifications.py b/api/tests/routes/object_modifications.py
index c40a3250ceb077f6e1a9a51524250cd575ad528a..c0da04fc4cf23871faeb2d97517713131fa89e7a 100644
--- a/api/tests/routes/object_modifications.py
+++ b/api/tests/routes/object_modifications.py
@@ -718,7 +718,7 @@ class ObjectModificationsTest(ApiTest):
             "/object_management/lecture/29/configuration",
             {
                 "updates": {
-                    "time": "2000-01-01T01:01:01"
+                    "time": "2000-01-01T01:01:01.000Z"
                 },
                 "expected_current_values": {}
             },
@@ -728,7 +728,7 @@ class ObjectModificationsTest(ApiTest):
             "GET",
             "/object_management/lecture/29/configuration",
             use_moderator_login=True
-        )[1], "time", "2000-01-01T01:01:01")
+        )[1], "time", "2000-01-01T01:01:01.000Z")
         self.do_json_request(
             "PATCH",
             "/object_management/announcement/3/configuration",
@@ -1363,7 +1363,7 @@ class ObjectModificationsTest(ApiTest):
                     "title": "Hallo",
                     "speaker": "Someone",
                     "location": "Somewhere",
-                    "time": "2000-01-01T00:00:00",
+                    "time": "2000-01-01T00:00:00.000Z",
                     "duration": 90,
                     "description": "Something",
                     "livestream_planned": True,
@@ -1384,7 +1384,7 @@ class ObjectModificationsTest(ApiTest):
                     "title": "Hallo",
                     "speaker": "Someone",
                     "location": "Somewhere",
-                    "time": "2000-01-01T00:00:00",
+                    "time": "2000-01-01T00:00:00.000Z",
                     "duration": 90,
                     "description": "Something",
                     "livestream_planned": True,
@@ -1405,7 +1405,7 @@ class ObjectModificationsTest(ApiTest):
                     "title": "Hallo",
                     "speaker": "Someone",
                     "location": "Somewhere",
-                    "time": "2000-01-01T00:00:00",
+                    "time": "2000-01-01T00:00:00.000Z",
                     "duration": 90,
                     "description": "Something",
                     "livestream_planned": True,
@@ -1427,7 +1427,7 @@ class ObjectModificationsTest(ApiTest):
                     "title": "Hallo",
                     "speaker": "Someone",
                     "location": "Somewhere",
-                    "time": "2000-01-01T00:00:00",
+                    "time": "2000-01-01T00:00:00.000Z",
                     "duration": 90,
                     "description": "Something",
                     "livestream_planned": True,
@@ -1449,7 +1449,7 @@ class ObjectModificationsTest(ApiTest):
                     "title": "Hallo",
                     "speaker": "Someone",
                     "location": "Somewhere",
-                    "time": "2000-01-01T00:00:00",
+                    "time": "2000-01-01T00:00:00.000Z",
                     "duration": 90,
                     "description": "Something",
                     "livestream_planned": True,
@@ -2021,7 +2021,7 @@ class ObjectModificationsTest(ApiTest):
                 "parent_id": 3,
                 "values": {
                     "title": "New Year",
-                    "time": "2024-01-01T00:00:00",
+                    "time": "2024-01-01T00:00:00.000Z",
                     "duration": 42
                 }
             },
diff --git a/api/tests/routes/stats.py b/api/tests/routes/stats.py
index 459ff972fe990ceb007e64f88885195343742489..60df40e6483103d29a30818c37f6fc1cfa7b9a43 100644
--- a/api/tests/routes/stats.py
+++ b/api/tests/routes/stats.py
@@ -1,8 +1,8 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
 
 from api_test import ApiTest
-from videoag_common.miscellaneous import HTTP_200_OK, DEFAULT_DATETIME_FORMAT, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND, \
-    HTTP_401_UNAUTHORIZED
+from videoag_common.miscellaneous import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND, \
+    HTTP_401_UNAUTHORIZED, format_standard_datetime, get_standard_datetime_now
 
 
 class StatsTest(ApiTest):
@@ -13,7 +13,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": -1,  # invalid
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -25,7 +25,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": -1,  # invalid
                 "watch_speed_percent": 150,
@@ -37,7 +37,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 0,  # invalid
@@ -49,7 +49,7 @@ class StatsTest(ApiTest):
             "/course/wrong-handle/watched/publish_medium/1495",  # wrong handle
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -61,7 +61,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": (datetime.now() + timedelta(minutes=1)).strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now() + timedelta(minutes=1)),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -73,7 +73,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": (datetime.now() - timedelta(minutes=1)).strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now() - timedelta(minutes=1)),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -97,7 +97,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -113,7 +113,7 @@ class StatsTest(ApiTest):
             "/course/07ws-diskrete/watched/publish_medium/1495",
             {
                 "watch_id": "1VdWFVE1eCDKy7ClpjRDXqtgSwJkzjHRTK3NbBg7hyLCZXGrLed2A9LmSF74vq0f",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -126,7 +126,7 @@ class StatsTest(ApiTest):
             "/course/07ws-buk/watched/publish_medium/1368",  # publish medium not visible
             {
                 "watch_id": "4nl8S29xePTNocTkhVgR9rMcHKQxsFxnvzU2hwNqinmXDjtolA9C7sDHsXWTmIAU",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -138,7 +138,7 @@ class StatsTest(ApiTest):
             "/course/07ws-buk/watched/publish_medium/1368",  # publish medium not visible
             {
                 "watch_id": "4nl8S29xePTNocTkhVgR9rMcHKQxsFxnvzU2hwNqinmXDjtolA9C7sDHsXWTmIAU",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -152,7 +152,7 @@ class StatsTest(ApiTest):
             "/course/11ws-infin/watched/publish_medium/1497",  # has authentication view perms
             {
                 "watch_id": "upsTW8OkUfFGBesyzC6JJn1j8lVUDwbZPL7R9NiWmqDyBZMzW70up13Bnm0DDzrJ",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
@@ -164,7 +164,7 @@ class StatsTest(ApiTest):
             "/course/11ws-infin/watched/publish_medium/1497",  # has authentication view perms
             {
                 "watch_id": "upsTW8OkUfFGBesyzC6JJn1j8lVUDwbZPL7R9NiWmqDyBZMzW70up13Bnm0DDzrJ",
-                "timestamp": datetime.now().strftime(DEFAULT_DATETIME_FORMAT),
+                "timestamp": format_standard_datetime(get_standard_datetime_now()),
                 "watch_start_time_sec": 50,
                 "watch_duration_sec": 30,
                 "watch_speed_percent": 150,
diff --git a/common_py/src/videoag_common/api_object/fields/basic_fields.py b/common_py/src/videoag_common/api_object/fields/basic_fields.py
index aac8cf316694deacdac01bfcba84c5234951dd68..675d33d00f752fbbfe34878b1213ad4d2a277013 100644
--- a/common_py/src/videoag_common/api_object/fields/basic_fields.py
+++ b/common_py/src/videoag_common/api_object/fields/basic_fields.py
@@ -1,6 +1,5 @@
 import enum
 import re
-from datetime import datetime
 from typing import Any, TYPE_CHECKING
 
 from videoag_common.miscellaneous import *
@@ -183,12 +182,12 @@ class ApiDatetimeField[_O: "ApiObject"](ApiSimpleColumnField[_O]):
         }
     
     def _db_value_to_json(self, db_value) -> JsonTypes:
-        return db_value.strftime(DEFAULT_DATETIME_FORMAT)
+        return format_standard_datetime(db_value)
     
     def _json_value_to_db(self, session: SessionDb, object_db: _O, json_value: CJsonValue):
         time_string = json_value.as_string(100)
         try:
-            return datetime.strptime(time_string, DEFAULT_DATETIME_FORMAT)
+            return parse_standard_datetime(time_string)
         except ValueError:
             json_value.raise_error("Invalid datetime format")
 
diff --git a/common_py/src/videoag_common/miscellaneous/__init__.py b/common_py/src/videoag_common/miscellaneous/__init__.py
index 298e13768d4cfa7b9c0c0a9214c2bf0d7af27957..15b7f81fc15d9e201d722f3109d5322e1b38431b 100644
--- a/common_py/src/videoag_common/miscellaneous/__init__.py
+++ b/common_py/src/videoag_common/miscellaneous/__init__.py
@@ -3,7 +3,9 @@ from .util import (
     check_json_type,
     ID_STRING_REGEX_NO_LENGTH,
     ID_STRING_PATTERN_NO_LENGTH,
-    DEFAULT_DATETIME_FORMAT,
+    format_standard_datetime,
+    parse_standard_datetime,
+    get_standard_datetime_now,
     ArgAttributeObject,
     _raise,
     truncate_string,
diff --git a/common_py/src/videoag_common/miscellaneous/mail.py b/common_py/src/videoag_common/miscellaneous/mail.py
index 0f704202f9b2f97cf291415f8ffaa94098dae02c..753e6cb5463ccb4742765f49f8776500dcf29ccc 100644
--- a/common_py/src/videoag_common/miscellaneous/mail.py
+++ b/common_py/src/videoag_common/miscellaneous/mail.py
@@ -8,6 +8,7 @@ from datetime import datetime
 from email.mime.text import MIMEText
 
 from .scheduler import SchedulerCoordinator
+from .util import get_standard_datetime_now
 
 
 class MailSender:
@@ -126,7 +127,7 @@ class ErrorMailNotifier:
         self._send_mail(bulk_msg)
     
     def _append_to_bulk_queue(self, msg: str):
-        self._bulk_queue.append((datetime.now(), msg))
+        self._bulk_queue.append((get_standard_datetime_now(), msg))
     
     def notify(self, message: str, exception: Exception or None = None):
         if exception is not None:
diff --git a/common_py/src/videoag_common/miscellaneous/util.py b/common_py/src/videoag_common/miscellaneous/util.py
index f82244ea4f4d4254b765138e0e8a08247c566240..4ab8d61e667a21e7c663f5f40a6ba3e48fb128a2 100644
--- a/common_py/src/videoag_common/miscellaneous/util.py
+++ b/common_py/src/videoag_common/miscellaneous/util.py
@@ -1,4 +1,7 @@
 import json
+import math
+import datetime
+from datetime import datetime as Datetime
 from pathlib import Path
 from types import ModuleType
 from typing import Callable, TypeVar, Iterable
@@ -28,7 +31,26 @@ ID_STRING_REGEX_NO_LENGTH = "(?=.*[a-z_-])[a-z0-9_-]*"
 ID_STRING_PATTERN_NO_LENGTH = re.compile(ID_STRING_REGEX_NO_LENGTH)
 
 
-DEFAULT_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
+def pad_string(val: str, padding: str, length: int):
+    return (padding * math.ceil( (length - len(val)) / len(padding) )) + val
+
+
+# Python doesn't have a formatter for milliseconds
+# Don't just remove last three numbers with microseconds. See here: https://stackoverflow.com/a/35643540
+def format_standard_datetime(dt: Datetime):
+    return dt.astimezone(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%S.") + pad_string(str(dt.microsecond // 1000), "0", 3) + "Z"
+
+
+def parse_standard_datetime(val: str):
+    if not val.endswith("Z"):
+        raise ValueError("datetime must have Z (UTC) designator")
+    val = val[:-1]
+    return Datetime.strptime(val + "000", "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=datetime.UTC)
+
+
+# ALWAYS use this method. Everything needs to be in UTC
+def get_standard_datetime_now() -> Datetime:
+    return Datetime.now(datetime.UTC)
 
 
 class ArgAttributeObject:
diff --git a/common_py/src/videoag_common/objects/site.py b/common_py/src/videoag_common/objects/site.py
index 75e580f2e7a35154c07b4c2fc6d919f2ee0cef61..0d0e420036ee57a4c39fcc34de533a642000b85e 100644
--- a/common_py/src/videoag_common/objects/site.py
+++ b/common_py/src/videoag_common/objects/site.py
@@ -2,6 +2,7 @@ from datetime import datetime
 from enum import Enum
 from typing import Any
 
+from videoag_common.miscellaneous import *
 from videoag_common.database import *
 from videoag_common.api_object import *
 from .course import Course, Lecture
@@ -66,7 +67,7 @@ class Announcement(DeletableApiObject, VisibilityApiObject, Base):
     def has_access(self, context: dict[AccessContextKey, Any]):
         cond = super().has_access(context)
         if not AC_IS_MOD.get(context):
-            current_time = datetime.now()
+            current_time = get_standard_datetime_now()
             cond &= self.hybrid_is_none(self.publish_time) | (self.publish_time <= current_time)
             cond &= self.hybrid_is_none(self.expiration_time) | (self.expiration_time > current_time)
         return cond
diff --git a/common_py/src/videoag_common/test/object_data.py b/common_py/src/videoag_common/test/object_data.py
index 9dd232e9875a40bb95c3ede1c6526ca79a120ce1..d3d6bb6996d9bcb6a8db4841d5d838a90587dd6e 100644
--- a/common_py/src/videoag_common/test/object_data.py
+++ b/common_py/src/videoag_common/test/object_data.py
@@ -447,7 +447,7 @@ TEST_DATA_LECTURE_1_NO_CHAP_MEDIA = \
         "title": "Einführung zur Berechenbarkeit",
         "speaker": "",
         "location": "",
-        "time": "2007-10-19T12:00:00",
+        "time": "2007-10-19T12:00:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -480,7 +480,7 @@ TEST_DATA_LECTURE_2_NO_CHAP_MEDIA = \
         "title": "Einführung zur Berechenbarkeit",
         "speaker": "",
         "location": "",
-        "time": "2007-10-23T08:30:00",
+        "time": "2007-10-23T08:30:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -513,7 +513,7 @@ TEST_DATA_LECTURE_3_NO_CHAP_MEDIA = \
         "title": "Einführung zur Berechenbarkeit",
         "speaker": "",
         "location": "",
-        "time": "2007-10-26T12:00:00",
+        "time": "2007-10-26T12:00:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -543,7 +543,7 @@ TEST_DATA_LECTURE_25_NO_CHAP_MEDIA = \
         "title": "Graphentheorie: Grundbegriffe, Datenstrukturen, Algorithmus für Breitensuche",
         "speaker": "",
         "location": "",
-        "time": "2007-12-11T13:30:00",
+        "time": "2007-12-11T13:30:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -555,7 +555,7 @@ TEST_DATA_LECTURE_25_NO_CHAP_MEDIA_MOD = TEST_DATA_LECTURE_25_NO_CHAP_MEDIA | \
     {
         "visible": True,
         "internal_comment": "",
-        "publish_time": "2007-12-12T19:12:04",
+        "publish_time": "2007-12-12T19:12:04.000Z",
     }
 TEST_DATA_LECTURE_25 = TEST_DATA_LECTURE_25_NO_CHAP_MEDIA | {
     "chapters": [TEST_DATA_CHAPTER_1, TEST_DATA_CHAPTER_2],
@@ -573,7 +573,7 @@ _TEST_DATA_LECTURE_26_NO_CHAP_MEDIA = \
         "title": "Hamiltonkreis, Eulertour, Eulerweg",
         "speaker": "",
         "location": "",
-        "time": "2007-12-18T13:30:00",
+        "time": "2007-12-18T13:30:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -599,7 +599,7 @@ TEST_DATA_LECTURE_29_NO_CHAP_MEDIA = \
         "title": "Modulare Arithmetik: Gruppe, Ring, Körper, abelsche Gruppe, Untergruppe, Einheitengruppe. Restklassenringe, Primzahl.",
         "speaker": "",
         "location": "",
-        "time": "2008-01-17T08:15:00",
+        "time": "2008-01-17T08:15:00.000Z",
         "duration": 0,
         "description": "",
         "no_recording": False,
@@ -629,7 +629,7 @@ TEST_DATA_LECTURE_185_NO_CHAP_MEDIA = \
         "title": "Organisatorisches, Motivation, Künstliche Pflanzen, Alphabete, Wörter, Sprachen",
         "speaker": "",
         "location": "",
-        "time": "2009-04-16T10:00:00",
+        "time": "2009-04-16T10:00:00.000Z",
         "duration": 90,
         "description": "Sorry für den schlechten Ton",
         "no_recording": False,
@@ -661,7 +661,7 @@ TEST_DATA_LECTURE_186_NO_CHAP_MEDIA = \
         "title": "Alphabete, Wörter, Sprachen, Reguläre Ausdrücke",
         "speaker": "",
         "location": "",
-        "time": "2009-04-21T08:15:00",
+        "time": "2009-04-21T08:15:00.000Z",
         "duration": 45,
         "description": "",
         "no_recording": False,
@@ -675,7 +675,7 @@ TEST_DATA_LECTURE_186_NO_CHAP_MEDIA_MOD = TEST_DATA_LECTURE_186_NO_CHAP_MEDIA |
     {
         "visible": True,
         "internal_comment": "",
-        "publish_time": "2009-05-18T03:13:20",
+        "publish_time": "2009-05-18T03:13:20.000Z",
     }
 TEST_DATA_LECTURE_186 = TEST_DATA_LECTURE_186_NO_CHAP_MEDIA | {
     "chapters": [],
@@ -703,7 +703,7 @@ TEST_DATA_LECTURE_1186_NO_CHAP_MEDIA = \
         "title": "Einführung, I. Grundlagen",
         "speaker": "",
         "location": "Aula",
-        "time": "2011-10-10T18:30:00",
+        "time": "2011-10-10T18:30:00.000Z",
         "duration": 90,
         "description": "",
         "no_recording": False,
@@ -715,7 +715,7 @@ TEST_DATA_LECTURE_1186_NO_CHAP_MEDIA_MOD = TEST_DATA_LECTURE_1186_NO_CHAP_MEDIA
     {
         "visible": True,
         "internal_comment": "",
-        "publish_time": "2011-10-17T14:33:45",
+        "publish_time": "2011-10-17T14:33:45.000Z",
     }
 TEST_DATA_LECTURE_1186 = TEST_DATA_LECTURE_1186_NO_CHAP_MEDIA | {
     "chapters": [],
@@ -733,7 +733,7 @@ TEST_DATA_LECTURE_1187_NO_CHAP_MEDIA = \
         "title": "",
         "speaker": "",
         "location": "Aula",
-        "time": "2011-10-17T18:30:00",
+        "time": "2011-10-17T18:30:00.000Z",
         "duration": 90,
         "description": "noch kein Titel",
         "no_recording": False,
@@ -763,7 +763,7 @@ TEST_DATA_LECTURE_1188_NO_CHAP_MEDIA = \
         "title": "",
         "speaker": "",
         "location": "Aula",
-        "time": "2050-10-24T18:30:00",
+        "time": "2050-10-24T18:30:00.000Z",
         "duration": 90,
         "description": "noch kein Titel",
         "no_recording": False,
@@ -915,8 +915,8 @@ TEST_DATA_ANNOUNCEMENT_1 = \
     }
 TEST_DATA_ANNOUNCEMENT_1_MOD = TEST_DATA_ANNOUNCEMENT_1 | \
     {
-        "publish_time": "2024-01-26T00:00:00",
-        "expiration_time": "2050-03-25T00:00:00",
+        "publish_time": "2024-01-26T00:00:00.000Z",
+        "expiration_time": "2050-03-25T00:00:00.000Z",
         "visible": True,
     }
 
@@ -929,7 +929,7 @@ TEST_DATA_ANNOUNCEMENT_2 = \
     }
 TEST_DATA_ANNOUNCEMENT_2_MOD = TEST_DATA_ANNOUNCEMENT_2 | \
     {
-        "publish_time": "2024-03-01T00:00:00",
+        "publish_time": "2024-03-01T00:00:00.000Z",
         "expiration_time": None,
         "visible": True,
     }
@@ -957,7 +957,7 @@ _TEST_DATA_ANNOUNCEMENT_4 = \
     }
 TEST_DATA_ANNOUNCEMENT_4_MOD = _TEST_DATA_ANNOUNCEMENT_4 | \
     {
-        "publish_time": "2050-03-22T00:00:00",
+        "publish_time": "2050-03-22T00:00:00.000Z",
         "expiration_time": None,
         "visible": True,
     }
@@ -971,8 +971,8 @@ _TEST_DATA_ANNOUNCEMENT_5 = \
     }
 TEST_DATA_ANNOUNCEMENT_5_MOD = _TEST_DATA_ANNOUNCEMENT_5 | \
     {
-        "publish_time": "2024-01-26T00:00:00",
-        "expiration_time": "2024-02-01T00:00:00",
+        "publish_time": "2024-01-26T00:00:00.000Z",
+        "expiration_time": "2024-02-01T00:00:00.000Z",
         "visible": True,
     }
 
diff --git a/job_controller/jobs/media_process_scheduler/job.py b/job_controller/jobs/media_process_scheduler/job.py
index e7952bca675cdf345cdf8141c2109da7458704d5..2ba51840cedae2fb47d8cb66caba2c7aa13aee40 100644
--- a/job_controller/jobs/media_process_scheduler/job.py
+++ b/job_controller/jobs/media_process_scheduler/job.py
@@ -1,7 +1,6 @@
 import logging
 import random
 import string
-from datetime import datetime
 from pathlib import Path
 
 from videoag_common.database import *
@@ -189,7 +188,7 @@ class ProcessScheduler:
         self._lecture.media_duration_sec = media_duration_sec
         
         if self._lecture.publish_time is None and len(self._lecture.publish_media) > 0:
-            self._lecture.publish_time = datetime.now()
+            self._lecture.publish_time = get_standard_datetime_now()
     
     def _try_create_metadata_for_file(self, file: MediumFile) -> bool:
         logger.info(f"Probing medium file {file.file_path} ({file.process_target_id}, {file.id}) for metadata")
diff --git a/job_controller/jobs/source_file_sorter/job.py b/job_controller/jobs/source_file_sorter/job.py
index bb6ac1a4ad3aa5a5049b4dcbc2fcc22132fe0f5e..0cdc7e047c903f94c562c48130ebaa66f34f9fe8 100644
--- a/job_controller/jobs/source_file_sorter/job.py
+++ b/job_controller/jobs/source_file_sorter/job.py
@@ -7,7 +7,7 @@ from pathlib import Path
 
 import videoag_common
 from videoag_common.database import *
-from videoag_common.miscellaneous import CJsonObject
+from videoag_common.miscellaneous import *
 from videoag_common.objects import *
 from videoag_common.media_process import *
 from videoag_common.objects.medium import SorterFileStatus
@@ -66,7 +66,7 @@ def _check_file(
             file_path=db_path,
             file_modification_time=file_mod_time,
             status=status,
-            update_time=datetime.now()
+            update_time=get_standard_datetime_now()
         )
         session.add(file)
         logger.info(f"Adding new file {db_path} to database")
@@ -88,7 +88,7 @@ def _check_file(
     file.status = status
     file.force_immediate_sort = False
     file.sorter_error_message = error_message
-    file.update_time = datetime.now()
+    file.update_time = get_standard_datetime_now()
     
     if sort_now:
         logger.info(f"Queuing {db_path} to be sorted")
@@ -150,7 +150,7 @@ def _sort_file(session: SessionDb, own_job_id: int, db_path: str):
     try:
         logger.info(f"Sorting file {sorter_file}")
         
-        sorter_file.update_time = datetime.now()
+        sorter_file.update_time = get_standard_datetime_now()
     
         assert sorter_file.file_path.startswith(_SORTER_DIR_NAME)
         file = _DATA_DIR.joinpath(sorter_file.file_path)
diff --git a/job_controller/jobs/view_stats_aggregator/job.py b/job_controller/jobs/view_stats_aggregator/job.py
index 1a4af09581724bc06a2f1e62760783625c92e048..fa1293629e55fb6b79d1fb1b57bc6e292ef13416 100644
--- a/job_controller/jobs/view_stats_aggregator/job.py
+++ b/job_controller/jobs/view_stats_aggregator/job.py
@@ -379,7 +379,7 @@ def execute(database, own_job_id, input_data: CJsonObject):
             .group_by(PublishMediumWatchLogEntry.watch_id)
             .having(
                 sql.func.max(PublishMediumWatchLogEntry.timestamp)
-                < (Datetime.now() - timedelta(minutes=_MINUTES_UNTIL_PROCESSING))
+                < (get_standard_datetime_now() - timedelta(minutes=_MINUTES_UNTIL_PROCESSING))
             )
             .limit(_BATCH_SIZE_LIMIT)
         )
diff --git a/job_controller/src/job_controller/executor_api/local_docker_executor.py b/job_controller/src/job_controller/executor_api/local_docker_executor.py
index 396b585e053c42ac9e102cd56337ab44a1a98f4d..024f02f822185b6cc159fcbcfc062e4110392bec 100644
--- a/job_controller/src/job_controller/executor_api/local_docker_executor.py
+++ b/job_controller/src/job_controller/executor_api/local_docker_executor.py
@@ -10,7 +10,7 @@ from pathlib import Path
 from time import sleep
 
 import job_controller
-from videoag_common.miscellaneous import JsonTypes
+from videoag_common.miscellaneous import *
 from .job_executor_api import JobExecutorApi, JobExecutionInfo
 from ..job.config import JobMetaData
 
@@ -40,7 +40,7 @@ class DockerJob(JobExecutionInfo):
         if self._failed:
             return
         self._failed = True
-        self._finish_time = datetime.now()
+        self._finish_time = get_standard_datetime_now()
     
     def spawn(self):
         if self._build_process is not None:
@@ -112,7 +112,7 @@ class DockerJob(JobExecutionInfo):
         # Might have missing quoting, etc. but can be useful for debugging.
         print("UNSAFE: " + ' '.join(str(a) for a in self._run_args))
         self._run_process = subprocess.Popen(self._run_args)
-        self._start_time = datetime.now()
+        self._start_time = get_standard_datetime_now()
     
     def _check_status_from_scheduler(self):
         self._check_and_update_status()
@@ -160,7 +160,7 @@ class DockerJob(JobExecutionInfo):
         
         # Set last for consistent variables in other threads
         if self._finish_time is None:
-            self._finish_time = datetime.now()
+            self._finish_time = get_standard_datetime_now()
     
     def is_success(self) -> bool or None:
         if self._finish_time is None:
diff --git a/job_controller/src/job_controller/job_controller.py b/job_controller/src/job_controller/job_controller.py
index d5f8f7ae5faad27a65e218c4063b4c814e834b00..0d79daecdcc803c1da1045656d4a5f4f765e24bc 100644
--- a/job_controller/src/job_controller/job_controller.py
+++ b/job_controller/src/job_controller/job_controller.py
@@ -7,6 +7,7 @@ from datetime import datetime
 # noinspection PyPep8Naming
 from sched import scheduler as Scheduler
 
+from videoag_common.miscellaneous import *
 from videoag_common.database import *
 from videoag_common.objects import *
 
@@ -204,11 +205,11 @@ class JobController:
         print(f"Handling {'failed' if failed else 'finished'} job {job.id}")
         
         if job_info is None:
-            job.run_end_time = datetime.now()
+            job.run_end_time = get_standard_datetime_now()
         else:
             job.run_end_time = job_info.get_finish_time()
             if job.run_end_time is None:  # In case of failure the API may return None
-                job.run_end_time = datetime.now()
+                job.run_end_time = get_standard_datetime_now()
         
         if failed:
             job.state = JobState.FAILED