i18n-js
Version:
A small library to provide I18n on JavaScript.
154 lines (119 loc) • 3.99 kB
text/typescript
import range from "lodash/range";
import { I18n } from "../I18n";
import { DateTime, TimeAgoInWordsOptions } from "../typing";
import { parseDate } from "./parseDate";
const within = (start: number, end: number, actual: number): boolean =>
actual >= start && actual <= end;
/**
* Reports the approximate distance in time between two dates.
*
* @private
*
* @param {I18n} i18n The `I18n` instance.
*
* @param {DateTime} fromTime The initial date.
*
* @param {DateTime} toTime The end date.
* @param {TimeAgoInWordsOptions} options Options.
* @return {string} The approximate distance between the dates.
*/
export function timeAgoInWords(
i18n: I18n,
fromTime: DateTime,
toTime: DateTime,
options: TimeAgoInWordsOptions = {},
): string {
const scope = options.scope || "datetime.distance_in_words";
const t = (name: string, count = 0): string => i18n.t(name, { count, scope });
fromTime = parseDate(fromTime);
toTime = parseDate(toTime);
let fromInSeconds = fromTime.getTime() / 1000;
let toInSeconds = toTime.getTime() / 1000;
if (fromInSeconds > toInSeconds) {
[fromTime, toTime, fromInSeconds, toInSeconds] = [
toTime,
fromTime,
toInSeconds,
fromInSeconds,
];
}
const distanceInSeconds = Math.round(toInSeconds - fromInSeconds);
const distanceInMinutes = Math.round((toInSeconds - fromInSeconds) / 60);
const distanceInHours = distanceInMinutes / 60;
const distanceInDays = distanceInHours / 24;
const distanceInHoursRounded = Math.round(distanceInMinutes / 60);
const distanceInDaysRounded = Math.round(distanceInDays);
const distanceInMonthsRounded = Math.round(distanceInDaysRounded / 30);
if (within(0, 1, distanceInMinutes)) {
if (!options.includeSeconds) {
return distanceInMinutes === 0
? t("less_than_x_minutes", 1)
: t("x_minutes", distanceInMinutes);
}
if (within(0, 4, distanceInSeconds)) {
return t("less_than_x_seconds", 5);
}
if (within(5, 9, distanceInSeconds)) {
return t("less_than_x_seconds", 10);
}
if (within(10, 19, distanceInSeconds)) {
return t("less_than_x_seconds", 20);
}
if (within(20, 39, distanceInSeconds)) {
return t("half_a_minute");
}
if (within(40, 59, distanceInSeconds)) {
return t("less_than_x_minutes", 1);
}
return t("x_minutes", 1);
}
if (within(2, 44, distanceInMinutes)) {
return t("x_minutes", distanceInMinutes);
}
if (within(45, 89, distanceInMinutes)) {
return t("about_x_hours", 1);
}
if (within(90, 1439, distanceInMinutes)) {
return t("about_x_hours", distanceInHoursRounded);
}
if (within(1440, 2519, distanceInMinutes)) {
return t("x_days", 1);
}
if (within(2520, 43_199, distanceInMinutes)) {
return t("x_days", distanceInDaysRounded);
}
if (within(43_200, 86_399, distanceInMinutes)) {
return t("about_x_months", Math.round(distanceInMinutes / 43200));
}
if (within(86_400, 525_599, distanceInMinutes)) {
return t("x_months", distanceInMonthsRounded);
}
let fromYear = fromTime.getFullYear();
if (fromTime.getMonth() + 1 >= 3) {
fromYear += 1;
}
let toYear = toTime.getFullYear();
if (toTime.getMonth() + 1 < 3) {
toYear -= 1;
}
const leapYears =
fromYear > toYear
? 0
: range(fromYear, toYear).filter(
(year) => new Date(year, 1, 29).getMonth() == 1,
).length;
const minutesInYear = 525_600;
const minuteOffsetForLeapYear = leapYears * 1440;
const minutesWithOffset = distanceInMinutes - minuteOffsetForLeapYear;
const distanceInYears = Math.trunc(minutesWithOffset / minutesInYear);
const diff = parseFloat(
(minutesWithOffset / minutesInYear - distanceInYears).toPrecision(3),
);
if (diff < 0.25) {
return t("about_x_years", distanceInYears);
}
if (diff < 0.75) {
return t("over_x_years", distanceInYears);
}
return t("almost_x_years", distanceInYears + 1);
}