···11+// keep a cache of formatter per locale, to avoid re-creating them (GC)
22+const formatters = new Map<string, Intl.RelativeTimeFormat>();
33+44+// get the Intl.RelativeTimeFormat formatter for the given locale
55+export function getFormatter(locale: string) {
66+ if (formatters.has(locale)) {
77+ return formatters.get(locale)!;
88+ }
99+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'always', style: 'narrow' });
1010+ formatters.set(locale, formatter);
1111+ return formatter;
1212+}
···11+export * from './action';
22+export type { Callback } from './render';
33+export { register, unregister } from './state';
44+export { default as default } from './RelativeTime.svelte';
···11+export type Callback = (result: {
22+ seconds: number;
33+ count: number;
44+ units: Intl.RelativeTimeFormatUnit;
55+ text: string;
66+}) => void;
77+88+export interface RenderState {
99+ date: Date | number;
1010+ callback: Callback;
1111+ formatter: Intl.RelativeTimeFormat;
1212+}
1313+1414+// Array reprsenting one minute, hour, day, week, month, etc in seconds
1515+const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
1616+1717+// Array equivalent to the above but in the string representation of the units
1818+const formatUnits: Intl.RelativeTimeFormatUnit[] = [
1919+ 'seconds',
2020+ 'minutes',
2121+ 'hours',
2222+ 'days',
2323+ 'weeks',
2424+ 'months',
2525+ 'years'
2626+];
2727+2828+// function to render relative time into
2929+export function render(state: RenderState, now: number) {
3030+ const { date, callback, formatter } = state;
3131+3232+ // Allow dates or times to be passed
3333+ const timeMs = typeof date === 'number' ? date : date.getTime();
3434+3535+ // Get the amount of seconds between the given date and now
3636+ const delta = timeMs - now;
3737+ const seconds = Math.round(delta / 1000);
3838+3939+ // Grab the ideal cutoff unit
4040+ const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(seconds));
4141+4242+ // units
4343+ const units = formatUnits[unitIndex];
4444+4545+ // Get the divisor to divide from the seconds. E.g. if our unit is 'day' our divisor
4646+ // is one day in seconds, so we can divide our seconds by this to get the # of days
4747+ const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
4848+4949+ // count of units
5050+ const count = Math.round(seconds / divisor);
5151+5252+ // Intl.RelativeTimeFormat do its magic
5353+ callback({
5454+ seconds: seconds,
5555+ count,
5656+ units,
5757+ text: formatter.format(count, units)
5858+ });
5959+6060+ // calculate time to next update, taking account rounding (e.g. it goes from 2 minutes to 1 minute at 90 seconds)
6161+ // and also the changeover from units (59 seconds shouldn't show as 1 minute, so at 61 seconds we schedule the next
6262+ // update for 1 second time)
6363+6464+ const divisorMs = divisor * 1000;
6565+6666+ let updateIn;
6767+6868+ if (unitIndex) {
6969+ updateIn = divisorMs / 2 - (Math.abs(delta) % divisorMs);
7070+ if (updateIn < 0) {
7171+ updateIn += divisorMs;
7272+ }
7373+ } else {
7474+ updateIn = divisorMs - (Math.abs(delta) % divisorMs);
7575+ }
7676+7777+ const updateAt = now + updateIn;
7878+7979+ return updateAt;
8080+}
···11+import { getFormatter } from './formatter';
22+import { render } from './render';
33+import type { Callback, RenderState } from './render';
44+55+interface UpdateState extends RenderState {
66+ update: number;
77+}
88+99+// keep track of each instance
1010+const instances = new Map<Object, UpdateState>();
1111+1212+// we use a single timer for efficiency and to keep updates in sync
1313+let updateInterval: number | NodeJS.Timeout;
1414+1515+// register or update instance
1616+export function register(
1717+ instance: Object,
1818+ date: Date | number,
1919+ locale: string,
2020+ live: boolean,
2121+ callback: Callback
2222+) {
2323+ // get the formatter for the given locale, we do this here so we don't keep having to look it up on each tick
2424+ const formatter = getFormatter(locale);
2525+2626+ // create state to render
2727+ const state = { date, callback, formatter };
2828+2929+ // initial render is immediate, so works for SSR
3030+ const update = render(state, Date.now());
3131+3232+ // if it's to update live, we keep a track and schedule the next update
3333+ if (live) {
3434+ instances.set(instance, { ...state, update });
3535+ } else {
3636+ instances.delete(instance);
3737+ }
3838+3939+ // start the clock ticking if there are any live instances
4040+ if (instances.size) {
4141+ updateInterval =
4242+ updateInterval ||
4343+ setInterval(() => {
4444+ const now = Date.now();
4545+ for (const state of instances.values()) {
4646+ if (state.update <= now) {
4747+ state.update = render(state, now);
4848+ }
4949+ }
5050+ }, 1000);
5151+ }
5252+}
5353+5454+export function unregister(instance: Object) {
5555+ instances.delete(instance);
5656+ if (instances.size === 0) {
5757+ clearInterval(updateInterval);
5858+ updateInterval = 0;
5959+ }
6060+}