···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+// adapted from https://github.com/CaptainCodeman/svelte-relative-time
22+33+export * from './action';
44+export type { Callback } from './render';
55+export { register, unregister } from './state';
66+export { default } from './RelativeTime.svelte';
77+88+export { default as RelativeTime } 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).replace('ago', '')
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+}
···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-// adapted from https://github.com/CaptainCodeman/svelte-relative-time
22-33-export * from './action';
44-export type { Callback } from './render';
55-export { register, unregister } from './state';
66-export { 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).replace('ago', '')
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-}