eratic / packages / core / src / lib / format.ts
281 words
 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
 * Formatting helpers.
 *
 * Dates are formatted in IDENTITY.timeZone, never in the build machine's local
 * zone. Without that, the same post renders with a different date depending on
 * whether it was built on your laptop or in CI.
 */

import { IDENTITY } from '../config.js';

const long = new Intl.DateTimeFormat(IDENTITY.locale, {
	year: 'numeric',
	month: 'long',
	day: 'numeric',
	timeZone: IDENTITY.timeZone,
});

const short = new Intl.DateTimeFormat(IDENTITY.locale, {
	year: 'numeric',
	month: 'short',
	day: 'numeric',
	timeZone: IDENTITY.timeZone,
});

const monthYear = new Intl.DateTimeFormat(IDENTITY.locale, {
	year: 'numeric',
	month: 'long',
	timeZone: IDENTITY.timeZone,
});

/** "5 August 2026" */
export const formatDate = (date: Date): string => long.format(date);

/** "5 Aug 2026" */
export const formatDateShort = (date: Date): string => short.format(date);

/** "August 2026" — listing group headers. */
export const formatMonth = (date: Date): string => monthYear.format(date);

/** The machine-readable form for <time datetime>. */
export const isoDate = (date: Date): string => date.toISOString();

/** Group entries under a month heading, newest month first. */
export function groupByMonth<T extends { pubDate: Date }>(entries: T[]): Array<[string, T[]]> {
	const groups = new Map<string, T[]>();
	for (const entry of entries) {
		const key = formatMonth(entry.pubDate);
		const bucket = groups.get(key);
		if (bucket) bucket.push(entry);
		else groups.set(key, [entry]);
	}
	return [...groups.entries()];
}

/**
 * Rough reading time. Deliberately vague — "6 min" is useful, "6.4 min" is
 * false precision about someone else's reading speed.
 */
export function readingTime(text: string): string {
	const words = text.trim().split(/\s+/).length;
	return `${Math.max(1, Math.round(words / 220))} min`;
}

/** Strip a URL down to something worth showing as a link label. */
export function prettyHost(url: string): string {
	try {
		return new URL(url).hostname.replace(/^www\./, '');
	} catch {
		return url;
	}
}

static mirror of HEAD · about · clone: git clone https://git.makefuture.ro/eratic.git