- Introduced additional terms under Section 7 of the GNU AGPL v3 for KIBU, including attribution, marking of modified versions, and trademark usage. - Added MIT License file for clarity on licensing options. - Updated README.md to reflect the new licensing terms, including detailed usage rights and obligations. - Created REUSE.toml for SPDX compliance and license annotations. - Added THIRD_PARTY_NOTICES.md to clarify third-party dependencies and their licenses. - Updated various source files and scripts to include SPDX headers for copyright and licensing. - Enhanced footer in layout to display licensing information and project link. - Implemented a new license page to present the AGPL and additional terms clearly.
54 lines
2.4 KiB
TypeScript
54 lines
2.4 KiB
TypeScript
// SPDX-FileCopyrightText: 2026 Paul Schoeneck
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only AND LicenseRef-KIBU-Additional-Terms
|
|
|
|
export function localClock(now = new Date()) {
|
|
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
timeZone: process.env.APP_TIMEZONE ?? "Europe/Berlin",
|
|
year: "numeric", month: "2-digit", day: "2-digit",
|
|
hour: "2-digit", minute: "2-digit", hourCycle: "h23",
|
|
}).formatToParts(now);
|
|
const get = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)!.value;
|
|
const day = `${get("year")}-${get("month")}-${get("day")}`;
|
|
const weekday = new Date(`${day}T12:00:00Z`).getUTCDay() || 7;
|
|
return { day, time: `${get("hour")}:${get("minute")}`, weekday };
|
|
}
|
|
|
|
export function pickupState(pickup: string | null, time: string, end?: string | null) {
|
|
if (!pickup) return "none";
|
|
const minutes = (value: string) => Number(value.slice(0, 2)) * 60 + Number(value.slice(3, 5));
|
|
const delta = minutes(pickup) - minutes(time);
|
|
if (minutes(end ?? pickup) < minutes(time)) return "overdue";
|
|
if (delta <= 15) return "due";
|
|
return "later";
|
|
}
|
|
|
|
export function displayDay(day: string) {
|
|
return new Intl.DateTimeFormat("de-DE", {
|
|
weekday: "long", day: "numeric", month: "long", timeZone: "UTC",
|
|
}).format(new Date(`${day}T12:00:00Z`));
|
|
}
|
|
|
|
export function timeInZone(instant: string, timeZone: string) {
|
|
return new Intl.DateTimeFormat("en-GB", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" })
|
|
.format(new Date(instant));
|
|
}
|
|
|
|
export function localInstant(day: string, time: string, timeZone: string): Date {
|
|
const wall = Date.parse(`${day}T${time}:00Z`);
|
|
const format = (date: Date) => new Intl.DateTimeFormat("sv-SE", {
|
|
timeZone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit",
|
|
minute: "2-digit", second: "2-digit", hourCycle: "h23",
|
|
}).format(date).replace(" ", "T");
|
|
const offsets = new Set([-36, 0, 36].map((hours) => {
|
|
const sample = new Date(wall + hours * 3_600_000);
|
|
return Date.parse(`${format(sample)}Z`) - sample.getTime();
|
|
}));
|
|
const candidates = [...offsets].map((offset) => new Date(wall - offset))
|
|
.filter((date) => format(date) === `${day}T${time}:00`);
|
|
if (candidates.length !== 1) {
|
|
throw new Error("Diese Uhrzeit ist wegen der Zeitumstellung nicht eindeutig oder existiert nicht. Bitte eine andere Uhrzeit wählen.");
|
|
}
|
|
return candidates[0];
|
|
}
|