blob: 2fa1dc28575a008f5d3196ffa28e69c5ffb62b36 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
const DEFAULT_DATE_LOCALE = 'en-US';
// Creating the datetime formatter costs ~1.5 ms, so when formatting
// multiple timestamps, it's more performant to reuse the formatter object.
// Export FORMATTER and SHORT_FORMATTER for testing. The return value differs
// based on time zone and browser, so we can't use static strings for testing.
// We can't stub out the method because it's native code and can't be modified.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/format#Avoid_comparing_formatted_date_values_to_static_values
export const FORMATTER = new Intl.DateTimeFormat(DEFAULT_DATE_LOCALE, {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
});
export const SHORT_FORMATTER = new Intl.DateTimeFormat(DEFAULT_DATE_LOCALE, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
export const MS_PER_MINUTE = 60 * 1000;
export const MS_PER_HOUR = MS_PER_MINUTE * 60;
export const MS_PER_DAY = MS_PER_HOUR * 24;
export const MS_PER_MONTH = MS_PER_DAY * 30;
/**
* Helper to determine if a Date was less than a month ago.
* @param {Date} date The date to check.
* @return {boolean} Whether the date was less than a
* month ago.
*/
function isLessThanAMonthAgo(date) {
const now = new Date();
const msDiff = Math.abs(Math.floor((now.getTime() - date.getTime())));
return msDiff < MS_PER_MONTH;
}
/**
* Displays timestamp in a standardized format to be re-used.
* @param {Date} date
* @return {string}
*/
export function standardTime(date) {
if (!date) return;
const absoluteTime = FORMATTER.format(date);
let timeAgoBit = '';
if (isLessThanAMonthAgo(date)) {
// Only show relative time if the time is less than a
// month ago because otherwise, it's not as useful.
timeAgoBit = ` (${relativeTime(date)})`;
}
return `${absoluteTime}${timeAgoBit}`;
}
/**
* Displays a timestamp in a format that's easy for a human to immediately
* reason about, based on long ago the time was.
* @param {Date} date native JavaScript Data Object.
* @return {string} Human-readable string of the date.
*/
export function relativeTime(date) {
if (!date) return;
const now = new Date();
let msDiff = now.getTime() - date.getTime();
// Use different wording depending on whether the time is in the
// future or past.
const pastOrPresentSuffix = msDiff < 0 ? 'from now' : 'ago';
msDiff = Math.abs(msDiff);
if (msDiff < MS_PER_MINUTE) {
// Less than a minute.
return 'just now';
} else if (msDiff < MS_PER_HOUR) {
// Less than an hour.
const minutes = Math.floor(msDiff / MS_PER_MINUTE);
if (minutes === 1) {
return `a minute ${pastOrPresentSuffix}`;
}
return `${minutes} minutes ${pastOrPresentSuffix}`;
} else if (msDiff < MS_PER_DAY) {
// Less than an day.
const hours = Math.floor(msDiff / MS_PER_HOUR);
if (hours === 1) {
return `an hour ${pastOrPresentSuffix}`;
}
return `${hours} hours ${pastOrPresentSuffix}`;
} else if (msDiff < MS_PER_MONTH) {
// Less than a month.
const days = Math.floor(msDiff / MS_PER_DAY);
if (days === 1) {
return `a day ${pastOrPresentSuffix}`;
}
return `${days} days ${pastOrPresentSuffix}`;
}
// A month or more ago. Better to show an exact date at this point.
return SHORT_FORMATTER.format(date);
}