diff --git a/src/pages/faq.tsx b/src/pages/faq.tsx
index 109b74319cf7f56b59b1720892edc00c40c807c4..ece8bc78a9d3904dee4b563651992a9bbecc3886 100644
--- a/src/pages/faq.tsx
+++ b/src/pages/faq.tsx
@@ -211,13 +211,12 @@ export default function FAQ() {
                 >
                     <p>
                         Auf vielfachen Wunsch unserer Dozenten haben wir die Möglichkeit umgesetzt,
-                        Videos nur für Teilnehmer eines bestimmten Moodle-Lernraums
-                        zugänglich zu machen. Um dies umzusetzen benötigen wir die Liste der
-                        Lernräume eines Nutzers, und damit Zugriff auf den Moodle-Account.
-                        Leider erlaubt uns das Moodle nur entweder keinen oder vollen
-                        (also lesenden und schreibenden) Zugriff, sodass du uns zur
-                        Authentifizierung für Lernraum-interne Videos mehr Zugriffsrechte geben
-                        musst, als theoretisch nötig.
+                        Videos nur für Teilnehmer eines bestimmten Moodle-Lernraums zugänglich zu
+                        machen. Um dies umzusetzen benötigen wir die Liste der Lernräume eines
+                        Nutzers, und damit Zugriff auf den Moodle-Account. Leider erlaubt uns das
+                        Moodle nur entweder keinen oder vollen (also lesenden und schreibenden)
+                        Zugriff, sodass du uns zur Authentifizierung für Lernraum-interne Videos
+                        mehr Zugriffsrechte geben musst, als theoretisch nötig.
                     </p>
                     <p>
                         Um die Missbrauchsgefahr zu minimieren, widerrufen wir die Zugriffsrechte
@@ -236,32 +235,46 @@ export default function FAQ() {
                 </FaqEntry>
                 <FaqEntry id="cookies" title="Welche Cookies werden gesetzt und wofür?">
                     <p>
-                        Hier ist zu unterscheiden zwischen Cookies und Local Storage (Lokaler Speicher).
-                        Cookies werden vom Server gesetzt und vom Browser gespeichert und mit jeder Webanfrage wieder an
-                        den Server geschickt. Local Storage wird auch im Browser gespeichert aber nur vom Java Script im
-                        Browser abgerufen und nicht an den Server geschickt.
+                        Hier ist zu unterscheiden zwischen Cookies und Local Storage (Lokaler
+                        Speicher). Cookies werden vom Server gesetzt und vom Browser gespeichert und
+                        mit jeder Webanfrage wieder an den Server geschickt. Local Storage wird auch
+                        im Browser gespeichert aber nur vom Java Script im Browser abgerufen und
+                        nicht an den Server geschickt.
                     </p>
                     <p>
-                        Aktuell verwenden wir den Local Storage um folgendes zu speichern (Technisch Notwendig)
+                        Aktuell verwenden wir den Local Storage um folgendes zu speichern (Technisch
+                        Notwendig)
                         <ul>
-                            <li><code>language</code>: Welche Sprache du gewählt hast (Deutsch/Englisch)</li>
-                            <li><code>theme</code>: Welches Design du gewählt hast (Dark/Light)</li>
-                            <li><code>hidden_announcements</code>: Welches Ankündungen du bereits gesehen hast und mit dem x weggeklickt hast</li>
+                            <li>
+                                <code>language</code>: Welche Sprache du gewählt hast
+                                (Deutsch/Englisch)
+                            </li>
+                            <li>
+                                <code>theme</code>: Welches Design du gewählt hast (Dark/Light)
+                            </li>
+                            <li>
+                                <code>hidden_announcements</code>: Welches Ankündungen du bereits
+                                gesehen hast und mit dem x weggeklickt hast
+                            </li>
                         </ul>
                     </p>
                     <p>
-                        Cookies werden beim reinen Betrachten der Seite setzen überhaupt keine gesetzt. Einzig
-                        bei der Authentifizierung für RWTH- oder Lernraum-interne Videos werden Cookies benötigt. Dafür
-                        wird der <code>session</code>-Cookie gesetzt welcher OAuth-Tokens und Zugriffsrechte speichert.
-                        Das Cookie ist kryptographisch gesichert und enthält nur die zum jeweiligen Zeitpunkt nötigen Daten.
+                        Cookies werden beim reinen Betrachten der Seite setzen überhaupt keine
+                        gesetzt. Einzig bei der Authentifizierung für RWTH- oder Lernraum-interne
+                        Videos werden Cookies benötigt. Dafür wird der <code>session</code>-Cookie
+                        gesetzt welcher OAuth-Tokens und Zugriffsrechte speichert. Das Cookie ist
+                        kryptographisch gesichert und enthält nur die zum jeweiligen Zeitpunkt
+                        nötigen Daten.
                     </p>
                     <p>
-                        Für unsere Statistiken verwenden wir keine Cookies oder Local Storage! Wenn du ein Video guckst
-                        wird eine komplett zufällige <code>watch_id</code> generiert die notwendig ist um Anfragen des
-                        selben Videoabrufs zusammenfassen zu können. Sobald du aber die Seite neulädst oder ein anderes
-                        Video anguckst wird eine neue <code>watch_id</code> generiert welche nicht zur vorherigen ID
-                        oder Dir zuzuordnen ist. Zusätzlich werden nach einigen Stunden, wenn die Views aggregiert sind,
-                        alle <code>watch_id</code>s auf unseren Servern gelöscht.
+                        Für unsere Statistiken verwenden wir keine Cookies oder Local Storage! Wenn
+                        du ein Video guckst wird eine komplett zufällige <code>watch_id</code>{" "}
+                        generiert die notwendig ist um Anfragen des selben Videoabrufs
+                        zusammenfassen zu können. Sobald du aber die Seite neulädst oder ein anderes
+                        Video anguckst wird eine neue <code>watch_id</code> generiert welche nicht
+                        zur vorherigen ID oder Dir zuzuordnen ist. Zusätzlich werden nach einigen
+                        Stunden, wenn die Views aggregiert sind, alle <code>watch_id</code>s auf
+                        unseren Servern gelöscht.
                     </p>
                     <p>
                         In keinem Fall erstellen wir auf Basis der gespeicherten Daten
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index d788f8f0bd0e12458499ed4b084017f0c7bee6ff..e9d4fc4a1cea4780514c329c5e8ae49ccf7c8635 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -11,7 +11,10 @@ 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 {
+    datetimeToStringOnlyDate,
+    datetimeToStringOnlyTime,
+} from "@/videoag/miscellaneous/Formatting";
 import { ReloadBoundary, useReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
 import {
     ResourceType,
diff --git a/src/videoag/course/Player.tsx b/src/videoag/course/Player.tsx
index f82a2bde216151d20d632baee9215d98b431b796..6d20efaf341046d532ac951e64aa942de98cede1 100644
--- a/src/videoag/course/Player.tsx
+++ b/src/videoag/course/Player.tsx
@@ -109,11 +109,11 @@ function initPlayer(
     (player as any).markers.reset(markers);
 }
 
-function generateWatchId() {
-    const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+function generateWatchId(): string {
+    const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
     let res = "";
     for (let i = 0; i < 64; i++) {
-        res += chars.charAt(Math.floor(Math.random() * chars.length))
+        res += chars.charAt(Math.floor(Math.random() * chars.length));
     }
     return res;
 }
@@ -130,7 +130,7 @@ function VideoPlayer({
     const api = useApi();
     const videoRef = useRef<HTMLVideoElement>(null);
     const statTracker = useRef<StatTracker | undefined>(undefined);
-    const watchIdRef = useRef<string>(generateWatchId);
+    const watchIdRef = useRef<string>(generateWatchId());
 
     useEffect(() => {
         return () => {
diff --git a/src/videoag/course/Stats.tsx b/src/videoag/course/Stats.tsx
index c51a5fa12fda9d227bc0219e814b80bee615dc87..4710f3e6b1929f2001079c3b500a49eb33f427fc 100644
--- a/src/videoag/course/Stats.tsx
+++ b/src/videoag/course/Stats.tsx
@@ -336,11 +336,20 @@ export class StatTracker {
 
         const currentTime = this.getExpectedCurrentTime()!; // Not null as we are playing
 
-        if ((currentTime - this.currentSegment.getStartTime()) / this.currentSegment.getPlaybackRate() >= MAX_SEGMENT_REALTIME_DURATION_SEC) {
+        if (
+            (currentTime - this.currentSegment.getStartTime()) /
+                this.currentSegment.getPlaybackRate() >=
+            MAX_SEGMENT_REALTIME_DURATION_SEC
+        ) {
             this.logDebug("Starting new segment after current one is getting too long");
-            const cutTime = this.currentSegment.getStartTime() + MAX_SEGMENT_REALTIME_DURATION_SEC * this.currentSegment.getPlaybackRate();
+            const cutTime =
+                this.currentSegment.getStartTime() +
+                MAX_SEGMENT_REALTIME_DURATION_SEC * this.currentSegment.getPlaybackRate();
             let newStartTime = cutTime;
-            if ((currentTime - newStartTime) / this.currentSegment.getPlaybackRate() >= MAX_SEGMENT_REALTIME_DURATION_SEC) {
+            if (
+                (currentTime - newStartTime) / this.currentSegment.getPlaybackRate() >=
+                MAX_SEGMENT_REALTIME_DURATION_SEC
+            ) {
                 this.logDebug(
                     `Periodic check is falling behind. Dropping segments for ${newStartTime} - ${currentTime} (${currentTime - newStartTime} seconds)`,
                 );
@@ -379,7 +388,9 @@ export class StatTracker {
             MIN_SEGMENT_DURATION_SEC
         ) {
             const segment = {
-                timestamp: formatApiDatetime(DateTime.fromMillis(this.currentSegment.getPauseRealtimeMs()!)),
+                timestamp: formatApiDatetime(
+                    DateTime.fromMillis(this.currentSegment.getPauseRealtimeMs()!),
+                ),
                 watch_start_time_sec: Math.round(this.currentSegment.getStartTime()),
                 // Use difference of rounded times, so that continuous segments stay continuous
                 watch_duration_sec:
diff --git a/src/videoag/form/TypeEditor.tsx b/src/videoag/form/TypeEditor.tsx
index ed4e9139868fd59c814d9d683bb2f28c6809d1e4..0f9b9aadce6b89c453c69fd211d267b376003d2b 100644
--- a/src/videoag/form/TypeEditor.tsx
+++ b/src/videoag/form/TypeEditor.tsx
@@ -1,4 +1,5 @@
 import React, { useEffect, useRef, useState } from "react";
+import { DateTime } from "luxon";
 
 import type { int } from "@/videoag/api/types";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
@@ -232,11 +233,13 @@ export function DatetimeEditor({
     }, [knownValue, value, setForceInputUpdate]);
 
     const isValidCheck = (val: string | null | undefined) => {
+        if (val === null || val === undefined) {
+            return mayBeNull === true;
+        }
         let parsedVal = null;
         try {
             parsedVal = parseApiDatetime(val);
-        } catch {}
-        if (val === null || val === undefined || parsedVal === null) {
+        } catch {
             return mayBeNull === true;
         }
         return true;
@@ -250,7 +253,7 @@ export function DatetimeEditor({
                 isValid = false;
             }
         } else {
-            newValue = formatApiDatetime(new Date(newValue));
+            newValue = formatApiDatetime(DateTime.fromJSDate(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 c877dc220b18e8cb1ac70709271ae187a085a3ae..c27d8bf7cb809638649646bf4ed4025116ab2e89 100644
--- a/src/videoag/miscellaneous/Formatting.tsx
+++ b/src/videoag/miscellaneous/Formatting.tsx
@@ -1,3 +1,5 @@
+import { DateTime } from "luxon";
+
 import { zeropad, parseApiDatetime } from "./Util";
 
 export function semesterToHuman(semester?: string, long_str?: boolean): string {
@@ -30,17 +32,22 @@ 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" });
+    return datetime.toLocaleString({
+        weekday: "short",
+        year: "numeric",
+        month: "2-digit",
+        day: "2-digit",
+    });
 }
 
-export function datetimeToStringOnlyTime(datetime: DateTime): string {
+export function datetimeToStringOnlyTime(datetime: DateTime | string): string {
     if (typeof datetime === "string") {
         datetime = parseApiDatetime(datetime);
     }
     return datetime.toLocaleString({ hour: "2-digit", minute: "2-digit" });
 }
 
-export function datetimeToString(datetime: DateTime): string {
+export function datetimeToString(datetime: DateTime | string): string {
     return `${datetimeToStringOnlyDate(datetime)}, ${datetimeToStringOnlyTime(datetime)}`;
 }
 
diff --git a/src/videoag/miscellaneous/Util.tsx b/src/videoag/miscellaneous/Util.tsx
index 8aad538a2491f352e571f7f7059567cd4a108477..bfb869ed49138bedad6112edeac4271762e62456 100644
--- a/src/videoag/miscellaneous/Util.tsx
+++ b/src/videoag/miscellaneous/Util.tsx
@@ -11,7 +11,12 @@ export function zeropad(num: number, places: number) {
 }
 
 export function formatApiDatetime(datetime: DateTime): string {
-    return datetime.toUTC().toISO({ format: "extended", suppressSeconds: false, suppressMilliseconds: false, includeOffset: true });
+    return datetime.toUTC().toISO({
+        format: "extended",
+        suppressSeconds: false,
+        suppressMilliseconds: false,
+        includeOffset: true,
+    })!;
 }
 
 export function parseApiDatetime(datetimeString: datetime): DateTime {