import type { PresenceSample } from "#lib/api/types.js"; import { formatDay, formatTime } from "#lib/format/datetime.js"; import { locale, t } from "#lib/i18n/locale.svelte.js"; import type { MessageKey } from "#lib/i18n/messages.js"; const SECONDS_IN_MINUTE = 60; const MINUTES_IN_HOUR = 60; const SECONDS_IN_HOUR = 3600; const MS_IN_MINUTE = 60_000; const SECONDS_IN_DAY = 86_400; const MS_IN_DAY = 86_400_000; const DAYS_IN_WEEK = 7; const MONDAY = Date.UTC(2024, 0, 1); const FUZZY: Record = { last_month: "presence.lastMonth", last_week: "presence.lastWeek", long_time_ago: "presence.longAgo", recently: "presence.recently", }; export function isOnline( sample: PresenceSample | null, now = Date.now() ): boolean { if (sample?.status !== "online" || sample.next_offline_date === null) { return false; } return new Date(sample.next_offline_date).getTime() > now; } function lastSeen(iso: string, now: number): string { const date = new Date(iso); const minutes = Math.floor((now - date.getTime()) / MS_IN_MINUTE); if (minutes < 1) { return t("presence.justNow"); } if (minutes < MINUTES_IN_HOUR) { return t("presence.ago", { count: minutes }); } const time = formatTime(iso); const today = new Date(now); if (date.toDateString() === today.toDateString()) { return t("presence.todayAt", { time }); } if (date.toDateString() === new Date(now - MS_IN_DAY).toDateString()) { return t("presence.yesterdayAt", { time }); } return t("presence.on", { day: formatDay(iso), time }); } export function formatPresence( sample: PresenceSample, now = Date.now() ): string { if (sample.status === "online") { return isOnline(sample, now) ? t("presence.online") : lastSeen(sample.next_offline_date ?? sample.ts, now); } const fuzzy = FUZZY[sample.status]; if (fuzzy) { return t(fuzzy); } return sample.last_online_date ? lastSeen(sample.last_online_date, now) : t("presence.offline"); } export function formatSpan(seconds: number): string { if (seconds < SECONDS_IN_MINUTE) { return t("presence.seconds", { seconds: Math.round(seconds) }); } const minutes = Math.round(seconds / SECONDS_IN_MINUTE); if (minutes < MINUTES_IN_HOUR) { return t("presence.minutes", { minutes }); } const hours = Math.floor(minutes / MINUTES_IN_HOUR); const rest = minutes % MINUTES_IN_HOUR; return rest === 0 ? t("presence.hours", { hours }) : t("presence.hoursMinutes", { hours, minutes: rest }); } export function formatClock(secondsOfDay: number): string { const hours = Math.floor(secondsOfDay / SECONDS_IN_HOUR); const minutes = Math.floor( (secondsOfDay % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE ); return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`; } export function formatLongSpan(seconds: number): string { if (seconds < 2 * SECONDS_IN_DAY) { return formatSpan(seconds); } const days = Math.floor(seconds / SECONDS_IN_DAY); const hours = Math.round((seconds % SECONDS_IN_DAY) / SECONDS_IN_HOUR); const text = t("presence.days", { count: days }); return hours === 0 ? text : `${text} ${t("presence.hours", { hours })}`; } function capitalize(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); } export function weekdayLabels(): string[] { const format = new Intl.DateTimeFormat(locale.tag, { timeZone: "UTC", weekday: "short", }); return Array.from({ length: DAYS_IN_WEEK }, (_, day) => capitalize(format.format(MONDAY + day * MS_IN_DAY)) ); }