First working prototype

This commit is contained in:
2026-09-06 13:50:58 +02:00
parent ecf08f8307
commit f61b9025eb
44 changed files with 5118 additions and 905 deletions

80
src/lib/time.js Normal file
View File

@@ -0,0 +1,80 @@
/**
* Time helpers. Everything here works in *local* time — the prototype derived day
* keys from `toISOString()`, which is UTC and files late/early work under the wrong
* day. Timestamps are epoch milliseconds; rounding is applied at render only.
*/
export const MINUTE = 60_000;
export const HOUR = 3_600_000;
export const DAY = 86_400_000;
const pad = (n) => String(n).padStart(2, '0');
/** Local 'YYYY-MM-DD' for a timestamp. */
export function localDateKey(ts = Date.now()) {
const d = new Date(ts);
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
/** Timestamp of local midnight starting `dateKey`. */
export function startOfDay(dateKey) {
const [y, m, d] = dateKey.split('-').map(Number);
return new Date(y, m - 1, d).getTime();
}
/** Timestamp of 'HH:MM' local time on `dateKey`. */
export function atLocalTime(dateKey, hhmm) {
const [y, m, d] = dateKey.split('-').map(Number);
const [h, min] = hhmm.split(':').map(Number);
return new Date(y, m - 1, d, h, min, 0, 0).getTime();
}
/** `dateKey` shifted by `n` days, DST-safe. */
export function addDays(dateKey, n) {
const [y, m, d] = dateKey.split('-').map(Number);
return localDateKey(new Date(y, m - 1, d + n).getTime());
}
/** JS weekday for a date key: 0 = Sunday … 6 = Saturday. */
export function weekday(dateKey) {
return new Date(startOfDay(dateKey)).getDay();
}
/**
* Round to the nearest `minutes`, half up, relative to *local midnight* rather
* than the epoch — so zones with a :30 or :45 offset still land on whole
* wall-clock steps. Always drops seconds.
*/
export function roundTs(ts, minutes) {
const step = Math.max(1, minutes || 1) * MINUTE;
const midnight = startOfDay(localDateKey(ts));
return midnight + Math.round((ts - midnight) / step) * step;
}
/** 'HH:MM' in local time, rounded to `roundMinutes`. */
export function fmtTime(ts, roundMinutes = 1) {
const d = new Date(roundTs(ts, roundMinutes));
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
/** A duration as '8h15' / '0h45'. */
export function fmtDuration(ms) {
const total = Math.max(0, Math.round(ms / MINUTE));
return `${Math.floor(total / 60)}h${pad(total % 60)}`;
}
/** 'vendredi 5 septembre' — the resume heading. */
export function fmtDayLabel(dateKey) {
const s = new Intl.DateTimeFormat('fr-FR', {
weekday: 'long',
day: 'numeric',
month: 'long',
}).format(new Date(startOfDay(dateKey)));
return s.charAt(0).toUpperCase() + s.slice(1);
}
/** 'HH:MM' → minutes since midnight. */
export function hhmmToMinutes(hhmm) {
const [h, m] = hhmm.split(':').map(Number);
return h * 60 + m;
}