aboutsummaryrefslogtreecommitdiff
path: root/src/lib/shared/temporal.ts
blob: 6fd085d9c7967c6f658189195b3a7fa6a795aa96 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

// Temporal display helpers: format ISO instants for the viewer's timezone.

/** Format an ISO instant in the viewer's timezone. */
export function formatInstant(
	iso: string,
	options: Intl.DateTimeFormatOptions,
): string {
	return Temporal.Instant.from(iso)
		.toZonedDateTimeISO(Temporal.Now.timeZoneId())
		.toLocaleString('fr-FR', options);
}

/** Short day-month stamp ("8 sept."). */

export function formatDayMonth(iso: string): string {
	return formatInstant(iso, { day: 'numeric', month: 'short' });
}

/** Day-month-year stamp ("8 sept. 2026"). */
export function formatDayMonthYear(iso: string): string {
	return formatInstant(iso, {
		day: 'numeric',
		month: 'short',
		year: 'numeric',
	});
}

const ONE_DAY_MS = 86_400_000;

/**
 * Stamp to store for a `YYYY-MM-DD` form day: noon UTC — a midnight stamp can
 * read back as the wrong day in a timezone ahead of UTC. Clamped to `now` when
 * the day is still ahead of the clock, and null for a malformed day or one
 * more than a day in the future (timezones ahead of UTC make "today" fuzzy).
 */
export function instantFromDay(
	day: string,
	now: Temporal.Instant,
): string | null {
	let midnight: Temporal.Instant;
	try {
		midnight = Temporal.PlainDate.from(day.trim())
			.toZonedDateTime('UTC')
			.toInstant();
	} catch {
		return null;
	}
	const noon = midnight.add({ hours: 12 });
	if (noon.epochMilliseconds - now.epochMilliseconds > ONE_DAY_MS) return null;
	const stamp = Temporal.Instant.compare(noon, now) > 0 ? now : noon;
	return stamp.toString({ smallestUnit: 'millisecond' });
}