diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index bdaae65688f02ab1e75b6a02f916a4c851efffa5..d788f8f0bd0e12458499ed4b084017f0c7bee6ff 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -11,6 +11,7 @@ import { LectureLiveLabel } from "@/videoag/course/LiveLabel";
 import { ErrorComponent, showError, showWarningToast } from "@/videoag/error/ErrorDisplay";
 import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
+import { datetimeToStringOnlyDate, datetimeToStringOnlyTime } from "@/videoag/miscellaneous/Formatting";
 import { ReloadBoundary, useReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
 import {
     ResourceType,
@@ -170,13 +171,7 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
 
     let dates = []; // group by day/date
     for (let lecture of data.upcoming_lectures) {
-        let date_parsed = new Date(lecture.time);
-        let date_string = date_parsed.toLocaleDateString([], {
-            weekday: "short",
-            year: "numeric",
-            month: "2-digit",
-            day: "2-digit",
-        });
+        let date_string = datetimeToStringOnlyDate(lecture.time);
 
         let date_index = dates.findIndex((date) => date.date === date_string);
         if (date_index === -1) {
@@ -193,7 +188,6 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
                     <strong>{date.date}</strong>
                     <ul className="list-group">
                         {date.lectures.map((lecture, index) => {
-                            let date_parsed = new Date(lecture.time);
                             let course_data = data.course_context[lecture.course_id];
                             return (
                                 <li
@@ -201,13 +195,7 @@ function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomep
                                     style={{ border: "none" }}
                                     key={index}
                                 >
-                                    {
-                                        // only time (HH:MM)
-                                        date_parsed.toLocaleTimeString([], {
-                                            hour: "2-digit",
-                                            minute: "2-digit",
-                                        })
-                                    }{" "}
+                                    {datetimeToStringOnlyTime(lecture.time)}{" "}
                                     <a href={`/${course_data.handle}`}>{course_data.full_name}</a>
                                     {": "}
                                     <a href={`/${course_data.handle}#lecture-${lecture.id}`}>
diff --git a/src/videoag/course/Lecture.tsx b/src/videoag/course/Lecture.tsx
index 52b2d14985a35552de6003c3a1fc073f4fa2db10..fe9e5ab5fac48c2020c6f98be309160dbdd21a39 100644
--- a/src/videoag/course/Lecture.tsx
+++ b/src/videoag/course/Lecture.tsx
@@ -263,8 +263,6 @@ export function LectureCard({
     size?: "small" | "auto";
 }) {
     const { language } = useLanguage();
-    let dateStr = datetimeToString(lecture.time);
-
     let sz = size === "small" ? "xxl" : "sm";
 
     return (
@@ -306,7 +304,7 @@ export function LectureCard({
                         </span>{" "}
                         <br />
                         <br />
-                        <span>{dateStr}</span>
+                        <span>{datetimeToString(lecture.time)}</span>
                         {lecture.speaker ? (
                             <div className="small p-children-inline">
                                 {language.get("ui.generic.lecture_given_by")}{" "}
@@ -345,7 +343,7 @@ export function LectureCard({
                         <strong>{course.full_name}</strong>
                         <LectureLiveLabel lecture={lecture} />
                     </li>
-                    <li>{dateStr}</li>
+                    <li>{datetimeToString(lecture.time)}</li>
                     {lecture.speaker ? (
                         <div className="small p-children-inline">
                             {language.get("ui.generic.lecture_given_by")}{" "}
diff --git a/src/videoag/course/Player.tsx b/src/videoag/course/Player.tsx
index 2a296dc7eaa99eb54aa4d66a02dc9fb01124a95f..70dbc311d101476e1ef01f4adc9cfde10d372876 100644
--- a/src/videoag/course/Player.tsx
+++ b/src/videoag/course/Player.tsx
@@ -16,7 +16,7 @@ import { useLanguage } from "@/videoag/localization/LanguageProvider";
 import Title from "@/videoag/miscellaneous/TitleComponent";
 import { ResourceType } from "@/videoag/miscellaneous/PromiseHelpers";
 import { UpdateOverlay } from "@/videoag/miscellaneous/UpdateOverlay";
-import { showInfoToast } from "@/videoag/miscellaneous/Util";
+import { showInfoToast, parseApiDatetime } from "@/videoag/miscellaneous/Util";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import {
     OMDelete,
@@ -198,12 +198,12 @@ function LectureSuggestions({ course, lecture }: { course: course; lecture: lect
     const { language } = useLanguage();
     const prevLecture = course.lectures?.findLast(
         (l) =>
-            new Date(l.time) < new Date(lecture.time) &&
+            parseApiDatetime(l.time) < parseApiDatetime(lecture.time) &&
             (l.publish_media ?? []).find((m) => m.include_in_player),
     );
     const nextLecture = course.lectures?.find(
         (l) =>
-            new Date(l.time) > new Date(lecture.time) &&
+            parseApiDatetime(l.time) > parseApiDatetime(lecture.time) &&
             (l.publish_media ?? []).find((m) => m.include_in_player),
     );
 
diff --git a/src/videoag/form/TypeEditor.tsx b/src/videoag/form/TypeEditor.tsx
index 40ab15cb1a07983ac2b4bd4894b6b8f6b5d2531b..ed4e9139868fd59c814d9d683bb2f28c6809d1e4 100644
--- a/src/videoag/form/TypeEditor.tsx
+++ b/src/videoag/form/TypeEditor.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
 
 import type { int } from "@/videoag/api/types";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
-import { deepEquals } from "@/videoag/miscellaneous/Util";
+import { deepEquals, parseApiDatetime, formatApiDatetime } from "@/videoag/miscellaneous/Util";
 
 export type EditorArgs = {
     value?: any;
@@ -232,7 +232,11 @@ export function DatetimeEditor({
     }, [knownValue, value, setForceInputUpdate]);
 
     const isValidCheck = (val: string | null | undefined) => {
-        if (val === null || val === undefined || isNaN(new Date(val).valueOf())) {
+        let parsedVal = null;
+        try {
+            parsedVal = parseApiDatetime(val);
+        } catch {}
+        if (val === null || val === undefined || parsedVal === null) {
             return mayBeNull === true;
         }
         return true;
@@ -246,17 +250,7 @@ export function DatetimeEditor({
                 isValid = false;
             }
         } else {
-            const date = new Date(newValue);
-            const intToStr = (v: int, length: int) => {
-                let str = v.toString();
-                while (str.length < length) {
-                    str = "0" + str;
-                }
-                return str;
-            };
-            const dateStr = `${intToStr(date.getFullYear(), 4)}-${intToStr(date.getMonth() + 1, 2)}-${intToStr(date.getDate(), 2)}`;
-            const timeStr = `${intToStr(date.getHours(), 2)}:${intToStr(date.getMinutes(), 2)}:${intToStr(date.getSeconds(), 2)}`;
-            newValue = `${dateStr}T${timeStr}`;
+            newValue = formatApiDatetime(new Date(newValue));
         }
         knownValue.current = newValue;
         updateValue(newValue, isValid, false);
diff --git a/src/videoag/miscellaneous/Formatting.tsx b/src/videoag/miscellaneous/Formatting.tsx
index 065773367c4b8906a91e90a5eff94fd253e7a8dc..c877dc220b18e8cb1ac70709271ae187a085a3ae 100644
--- a/src/videoag/miscellaneous/Formatting.tsx
+++ b/src/videoag/miscellaneous/Formatting.tsx
@@ -1,3 +1,5 @@
+import { zeropad, parseApiDatetime } from "./Util";
+
 export function semesterToHuman(semester?: string, long_str?: boolean): string {
     if (!semester || semester === "zeitlos" || semester === "none" || semester.length !== 6) {
         return "Zeitlos";
@@ -16,10 +18,6 @@ export function semesterToHuman(semester?: string, long_str?: boolean): string {
         return `Wintersemester ${year}/${(parseInt(year) + 1).toString().substring(2)}`;
     }
 }
-export function zeropad(num: number, places: number) {
-    var zero = places - num.toString().length + 1;
-    return Array(+(zero > 0 && zero)).join("0") + num;
-}
 export function timestampToString(timestamp: number) {
     let h = zeropad(Math.trunc(timestamp / 3600), 2);
     let m = zeropad(Math.trunc((timestamp % 3600) / 60), 2);
@@ -27,10 +25,25 @@ export function timestampToString(timestamp: number) {
     let timeasstring = h + ":" + m + ":" + s;
     return timeasstring;
 }
-export function datetimeToString(datetime: string) {
-    let date_parsed = new Date(datetime);
-    return `${date_parsed.toLocaleDateString([], { weekday: "short", year: "numeric", month: "2-digit", day: "2-digit" })}, ${date_parsed.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
+
+export function datetimeToStringOnlyDate(datetime: DateTime | string): string {
+    if (typeof datetime === "string") {
+        datetime = parseApiDatetime(datetime);
+    }
+    return datetime.toLocaleString({ weekday: "short", year: "numeric", month: "2-digit", day: "2-digit" });
+}
+
+export function datetimeToStringOnlyTime(datetime: DateTime): string {
+    if (typeof datetime === "string") {
+        datetime = parseApiDatetime(datetime);
+    }
+    return datetime.toLocaleString({ hour: "2-digit", minute: "2-digit" });
+}
+
+export function datetimeToString(datetime: DateTime): string {
+    return `${datetimeToStringOnlyDate(datetime)}, ${datetimeToStringOnlyTime(datetime)}`;
 }
+
 export function filesizeToHuman(size: number) {
     if (size < 1024) {
         return size + " B";
diff --git a/src/videoag/miscellaneous/Util.tsx b/src/videoag/miscellaneous/Util.tsx
index 12c019c324a7f1d8912f3f909274b52300491d59..8aad538a2491f352e571f7f7059567cd4a108477 100644
--- a/src/videoag/miscellaneous/Util.tsx
+++ b/src/videoag/miscellaneous/Util.tsx
@@ -1,6 +1,22 @@
 import type React from "react";
 import { OverlayTrigger, Tooltip } from "react-bootstrap";
 import { toast, Bounce } from "react-toastify";
+import { DateTime } from "luxon";
+
+import type { datetime } from "@/videoag/api/types";
+
+export function zeropad(num: number, places: number) {
+    var zero = places - num.toString().length + 1;
+    return Array(+(zero > 0 && zero)).join("0") + num;
+}
+
+export function formatApiDatetime(datetime: DateTime): string {
+    return datetime.toUTC().toISO({ format: "extended", suppressSeconds: false, suppressMilliseconds: false, includeOffset: true });
+}
+
+export function parseApiDatetime(datetimeString: datetime): DateTime {
+    return DateTime.fromISO(datetimeString);
+}
 
 export function deepEquals(value: any, other: any) {
     if (value === null || other === null) {