From c84b8c6d04fe28946f445663527b589be7d712a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=BCnzel?= <simonk@fsmpi.rwth-aachen.de> Date: Tue, 22 Apr 2025 18:35:42 +0200 Subject: [PATCH] Improve datetime handling and update to new API version --- src/pages/index.tsx | 18 +++------------- src/videoag/course/Lecture.tsx | 6 ++---- src/videoag/course/Player.tsx | 6 +++--- src/videoag/form/TypeEditor.tsx | 20 ++++++------------ src/videoag/miscellaneous/Formatting.tsx | 27 ++++++++++++++++++------ src/videoag/miscellaneous/Util.tsx | 16 ++++++++++++++ 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index bdaae65..d788f8f 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 52b2d14..fe9e5ab 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 2a296dc..70dbc31 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 40ab15c..ed4e913 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 0657733..c877dc2 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 12c019c..8aad538 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) { -- GitLab