Commit df91367a authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Migrate time module to TS

parent 4770a365
...@@ -4,10 +4,8 @@ const HOUR = 60 * MINUTE; ...@@ -4,10 +4,8 @@ const HOUR = 60 * MINUTE;
/** /**
* Map of stringified `DateTimeFormatOptions` to cached `DateTimeFormat` instances. * Map of stringified `DateTimeFormatOptions` to cached `DateTimeFormat` instances.
*
* @type {Record<string, Intl.DateTimeFormat>}
*/ */
let formatters = {}; let formatters: Record<string, Intl.DateTimeFormat> = {};
/** /**
* Clears the cache of formatters. * Clears the cache of formatters.
...@@ -18,27 +16,27 @@ export function clearFormatters() { ...@@ -18,27 +16,27 @@ export function clearFormatters() {
/** /**
* Calculate time delta in milliseconds between two `Date` objects * Calculate time delta in milliseconds between two `Date` objects
*
* @param {Date} date
* @param {Date} now
*/ */
function delta(date, now) { function delta(date: Date, now: Date) {
// @ts-ignore // @ts-ignore
return now - date; return now - date;
} }
type IntlType = typeof window.Intl;
/** /**
* Return date string formatted with `options`. * Return date string formatted with `options`.
* *
* This is a caching wrapper for `Intl.DateTimeFormat.format`, useful because * This is a caching wrapper for `Intl.DateTimeFormat.format`, useful because
* constructing a `DateTimeFormat` is expensive. * constructing a `DateTimeFormat` is expensive.
* *
* @param {Date} date * @param Intl - Test seam. JS `Intl` API implementation.
* @param {Intl.DateTimeFormatOptions} options
* @param {Intl} Intl - Test seam. JS `Intl` API implementation.
* @return {string}
*/ */
function format(date, options, Intl = window.Intl) { function format(
date: Date,
options: Intl.DateTimeFormatOptions,
Intl: IntlType = window.Intl
): string {
const key = JSON.stringify(options); const key = JSON.stringify(options);
let formatter = formatters[key]; let formatter = formatters[key];
if (!formatter) { if (!formatter) {
...@@ -48,56 +46,46 @@ function format(date, options, Intl = window.Intl) { ...@@ -48,56 +46,46 @@ function format(date, options, Intl = window.Intl) {
} }
/** /**
* @callback DateFormatter * @return formatted date
* @param {Date} date
* @param {Date} now
* @param {Intl} [intl]
* @return {string} formatted date
*/ */
type DateFormatter = (date: Date, now: Date, intl?: IntlType) => string;
/** @type {DateFormatter} */ const nSec: DateFormatter = (date, now) => {
function nSec(date, now) {
const n = Math.floor(delta(date, now) / SECOND); const n = Math.floor(delta(date, now) / SECOND);
return `${n} secs ago`; return `${n} secs ago`;
} };
/** @type {DateFormatter} */ const nMin: DateFormatter = (date, now) => {
function nMin(date, now) {
const n = Math.floor(delta(date, now) / MINUTE); const n = Math.floor(delta(date, now) / MINUTE);
const plural = n > 1 ? 's' : ''; const plural = n > 1 ? 's' : '';
return `${n} min${plural} ago`; return `${n} min${plural} ago`;
} };
/** @type {DateFormatter} */ const nHr: DateFormatter = (date, now) => {
function nHr(date, now) {
const n = Math.floor(delta(date, now) / HOUR); const n = Math.floor(delta(date, now) / HOUR);
const plural = n > 1 ? 's' : ''; const plural = n > 1 ? 's' : '';
return `${n} hr${plural} ago`; return `${n} hr${plural} ago`;
} };
/** @type {DateFormatter} */ const dayAndMonth: DateFormatter = (date, now, Intl) => {
function dayAndMonth(date, now, Intl) {
return format(date, { month: 'short', day: 'numeric' }, Intl); return format(date, { month: 'short', day: 'numeric' }, Intl);
} };
/** @type {DateFormatter} */ const dayAndMonthAndYear: DateFormatter = (date, now, Intl) => {
function dayAndMonthAndYear(date, now, Intl) {
return format( return format(
date, date,
{ day: 'numeric', month: 'short', year: 'numeric' }, { day: 'numeric', month: 'short', year: 'numeric' },
Intl Intl
); );
} };
/** type Breakpoint = {
* @typedef Breakpoint test: (date: Date, now: Date) => boolean;
* @prop {(date: Date, now: Date) => boolean} test formatter: DateFormatter;
* @prop {(date: Date, now: Date, Intl?: typeof window.Intl) => string} formatter nextUpdate: number | null;
* @prop {number|null} nextUpdate };
*/
/** @type {Breakpoint[]} */ const BREAKPOINTS: Breakpoint[] = [
const BREAKPOINTS = [
{ {
// Less than 30 seconds // Less than 30 seconds
test: (date, now) => delta(date, now) < 30 * SECOND, test: (date, now) => delta(date, now) < 30 * SECOND,
...@@ -130,8 +118,7 @@ const BREAKPOINTS = [ ...@@ -130,8 +118,7 @@ const BREAKPOINTS = [
}, },
]; ];
/** @type {Breakpoint} */ const DEFAULT_BREAKPOINT: Breakpoint = {
const DEFAULT_BREAKPOINT = {
test: /* istanbul ignore next */ () => true, test: /* istanbul ignore next */ () => true,
formatter: dayAndMonthAndYear, formatter: dayAndMonthAndYear,
nextUpdate: null, nextUpdate: null,
...@@ -141,12 +128,12 @@ const DEFAULT_BREAKPOINT = { ...@@ -141,12 +128,12 @@ const DEFAULT_BREAKPOINT = {
* Returns a dict that describes how to format the date based on the delta * Returns a dict that describes how to format the date based on the delta
* between date and now. * between date and now.
* *
* @param {Date} date - The date to consider as the timestamp to format. * @param date - The date to consider as the timestamp to format.
* @param {Date} now - The date to consider as the current time. * @param now - The date to consider as the current time.
* @return {Breakpoint} An object that describes how to format the date. * @return An object that describes how to format the date.
*/ */
function getBreakpoint(date, now) { function getBreakpoint(date: Date, now: Date): Breakpoint {
for (let breakpoint of BREAKPOINTS) { for (const breakpoint of BREAKPOINTS) {
if (breakpoint.test(date, now)) { if (breakpoint.test(date, now)) {
return breakpoint; return breakpoint;
} }
...@@ -156,10 +143,8 @@ function getBreakpoint(date, now) { ...@@ -156,10 +143,8 @@ function getBreakpoint(date, now) {
/** /**
* See https://262.ecma-international.org/6.0/#sec-time-values-and-time-range * See https://262.ecma-international.org/6.0/#sec-time-values-and-time-range
*
* @param {Date} date
*/ */
function isDateValid(date) { function isDateValid(date: Date): boolean {
return !isNaN(date.valueOf()); return !isNaN(date.valueOf());
} }
...@@ -167,12 +152,9 @@ function isDateValid(date) { ...@@ -167,12 +152,9 @@ function isDateValid(date) {
* Return the number of milliseconds until the next update for a given date * Return the number of milliseconds until the next update for a given date
* should be handled, based on the delta between `date` and `now`. * should be handled, based on the delta between `date` and `now`.
* *
* @param {Date|null} date * @return ms until next update or `null` if no update should occur
* @param {Date} now
* @return {Number|null} - ms until next update or `null` if no update
* should occur
*/ */
export function nextFuzzyUpdate(date, now) { export function nextFuzzyUpdate(date: Date | null, now: Date): number | null {
if (!date || !isDateValid(date) || !isDateValid(now)) { if (!date || !isDateValid(date) || !isDateValid(now)) {
return null; return null;
} }
...@@ -200,13 +182,15 @@ export function nextFuzzyUpdate(date, now) { ...@@ -200,13 +182,15 @@ export function nextFuzzyUpdate(date, now) {
* This is useful for refreshing UI components displaying timestamps generated * This is useful for refreshing UI components displaying timestamps generated
* by `formatRelativeDate`, since the output changes less often for older timestamps. * by `formatRelativeDate`, since the output changes less often for older timestamps.
* *
* @param {string} date - Date string to use to determine the interval frequency * @param date - Date string to use to determine the interval frequency
* @param {() => void} callback - Interval callback * @param callback - Interval callback
* @return {() => void} A function that cancels the interval * @return A function that cancels the interval
*/ */
export function decayingInterval(date, callback) { export function decayingInterval(
/** @type {number|undefined} */ date: string,
let timer; callback: () => void
): () => void {
let timer: number | undefined;
const timestamp = new Date(date); const timestamp = new Date(date);
const update = () => { const update = () => {
...@@ -237,12 +221,16 @@ export function decayingInterval(date, callback) { ...@@ -237,12 +221,16 @@ export function decayingInterval(date, callback) {
* - "5 minutes ago" * - "5 minutes ago"
* - "25 Oct 2018" * - "25 Oct 2018"
* *
* @param {Date|null} date - The date to consider as the timestamp to format. * @param date - The date to consider as the timestamp to format.
* @param {Date} now - The date to consider as the current time. * @param now - The date to consider as the current time.
* @param {Intl} [Intl] - Test seam. JS `Intl` API implementation. * @param Intl - Test seam. JS `Intl` API implementation.
* @return {string} A 'fuzzy' string describing the relative age of the date. * @return A 'fuzzy' string describing the relative age of the date.
*/ */
export function formatRelativeDate(date, now, Intl) { export function formatRelativeDate(
date: Date | null,
now: Date,
Intl?: IntlType
): string {
if (!date) { if (!date) {
return ''; return '';
} }
...@@ -257,11 +245,9 @@ export function formatRelativeDate(date, now, Intl) { ...@@ -257,11 +245,9 @@ export function formatRelativeDate(date, now, Intl) {
* *
* "Sunday, Dec 17, 2017, 10:00 AM" * "Sunday, Dec 17, 2017, 10:00 AM"
* *
* @param {Date} date * @param Intl - Test seam. JS `Intl` API implementation.
* @param {Intl} [Intl] - Test seam. JS `Intl` API implementation.
* @return {string}
*/ */
export function formatDate(date, Intl) { export function formatDate(date: Date, Intl?: IntlType): string {
return format( return format(
date, date,
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment