ultisuite-client/lib/agenda/agenda-date.ts
R3D347HR4Y 3bbf3691b0
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
bordel c'est beau
2026-06-11 10:10:39 +02:00

136 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
addDays,
endOfDay,
endOfMonth,
endOfWeek,
format,
isSameMonth,
isSameYear,
parse,
startOfDay,
startOfMonth,
startOfWeek,
} from "date-fns"
import { fr } from "date-fns/locale"
import type { AgendaView } from "./agenda-url.ts"
export const WEEK_OPTS = { weekStartsOn: 1 as const, locale: fr }
/** Parse une valeur de date ICS (`YYYYMMDD` ou `YYYYMMDDTHHMMSS[Z]`). */
export function parseICSDate(value: string): Date | null {
const v = value.trim()
if (/^\d{8}$/.test(v)) {
const y = Number(v.slice(0, 4))
const m = Number(v.slice(4, 6))
const d = Number(v.slice(6, 8))
return new Date(y, m - 1, d)
}
const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/.exec(v)
if (!match) {
const fallback = new Date(v)
return Number.isNaN(fallback.getTime()) ? null : fallback
}
const [, y, mo, d, h, mi, s, z] = match
if (z === "Z") {
return new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +s))
}
return new Date(+y, +mo - 1, +d, +h, +mi, +s)
}
/** Format ICS UTC : `YYYYMMDDTHHMMSSZ`. */
export function formatICSDateTimeUTC(date: Date): string {
const p = (n: number, w = 2) => String(n).padStart(w, "0")
return (
`${p(date.getUTCFullYear(), 4)}${p(date.getUTCMonth() + 1)}${p(date.getUTCDate())}` +
`T${p(date.getUTCHours())}${p(date.getUTCMinutes())}${p(date.getUTCSeconds())}Z`
)
}
/** Format ICS date locale (journée entière) : `YYYYMMDD`. */
export function formatICSDateOnly(date: Date): string {
return format(date, "yyyyMMdd")
}
export function dateKey(date: Date): string {
return format(date, "yyyy-MM-dd")
}
export function parseDateKey(key: string): Date | null {
const parsed = parse(key, "yyyy-MM-dd", new Date())
return Number.isNaN(parsed.getTime()) ? null : parsed
}
/** Bornes affichées pour une vue (le mois inclut les semaines débordantes). */
export function viewRange(view: AgendaView, date: Date): { start: Date; end: Date } {
switch (view) {
case "day":
return { start: startOfDay(date), end: endOfDay(date) }
case "week":
return {
start: startOfWeek(date, WEEK_OPTS),
end: endOfWeek(date, WEEK_OPTS),
}
case "month":
return {
start: startOfWeek(startOfMonth(date), WEEK_OPTS),
end: endOfWeek(endOfMonth(date), WEEK_OPTS),
}
}
}
export function viewDays(view: AgendaView, date: Date): Date[] {
const { start, end } = viewRange(view, date)
const days: Date[] = []
for (let d = start; d <= end; d = addDays(d, 1)) days.push(d)
return days
}
function capitalize(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1)
}
/** Titre du header façon Google Calendar. */
export function viewTitle(view: AgendaView, date: Date): string {
if (view === "day") {
return capitalize(format(date, "EEEE d MMMM yyyy", { locale: fr }))
}
if (view === "month") {
return capitalize(format(date, "MMMM yyyy", { locale: fr }))
}
const { start, end } = viewRange("week", date)
if (isSameMonth(start, end)) {
return capitalize(format(start, "MMMM yyyy", { locale: fr }))
}
if (isSameYear(start, end)) {
return `${capitalize(format(start, "MMM", { locale: fr }))} ${format(end, "MMM yyyy", { locale: fr })}`
}
return `${capitalize(format(start, "MMM yyyy", { locale: fr }))} ${format(end, "MMM yyyy", { locale: fr })}`
}
export function formatEventTime(date: Date): string {
return format(date, "HH:mm")
}
/** Plage horaire lisible pour le popover de détails. */
export function formatEventRange(start: Date, end: Date, allDay: boolean): string {
const sameDay = dateKey(start) === dateKey(end)
const dayLabel = (d: Date) => capitalize(format(d, "EEEE d MMMM", { locale: fr }))
if (allDay) {
const endIncl = addDays(end, -1)
if (dateKey(start) === dateKey(endIncl) || end <= start) return dayLabel(start)
return `${dayLabel(start)} ${dayLabel(endIncl)}`
}
if (sameDay) {
return `${dayLabel(start)}${formatEventTime(start)} ${formatEventTime(end)}`
}
return `${dayLabel(start)} ${formatEventTime(start)} ${dayLabel(end)} ${formatEventTime(end)}`
}
export function minutesSinceMidnight(date: Date): number {
return date.getHours() * 60 + date.getMinutes()
}
export function roundToStep(minutes: number, step = 15): number {
return Math.round(minutes / step) * step
}