UNPKG

8.12 kBJavaScriptView Raw
1import { normalizeDates } from "./_lib/normalizeDates.js";
2import {
3 secondsInDay,
4 secondsInHour,
5 secondsInMinute,
6 secondsInMonth,
7 secondsInQuarter,
8 secondsInWeek,
9 secondsInYear,
10} from "./constants.js";
11import { differenceInCalendarDays } from "./differenceInCalendarDays.js";
12import { differenceInCalendarMonths } from "./differenceInCalendarMonths.js";
13import { differenceInCalendarQuarters } from "./differenceInCalendarQuarters.js";
14import { differenceInCalendarWeeks } from "./differenceInCalendarWeeks.js";
15import { differenceInCalendarYears } from "./differenceInCalendarYears.js";
16import { differenceInHours } from "./differenceInHours.js";
17import { differenceInMinutes } from "./differenceInMinutes.js";
18import { differenceInSeconds } from "./differenceInSeconds.js";
19
20/**
21 * The {@link intlFormatDistance} function options.
22 */
23
24/**
25 * The unit used to format the distance in {@link intlFormatDistance}.
26 */
27
28/**
29 * @name intlFormatDistance
30 * @category Common Helpers
31 * @summary Formats distance between two dates in a human-readable format
32 * @description
33 * The function calculates the difference between two dates and formats it as a human-readable string.
34 *
35 * The function will pick the most appropriate unit depending on the distance between dates. For example, if the distance is a few hours, it might return `x hours`. If the distance is a few months, it might return `x months`.
36 *
37 * You can also specify a unit to force using it regardless of the distance to get a result like `123456 hours`.
38 *
39 * See the table below for the unit picking logic:
40 *
41 * | Distance between dates | Result (past) | Result (future) |
42 * | ---------------------- | -------------- | --------------- |
43 * | 0 seconds | now | now |
44 * | 1-59 seconds | X seconds ago | in X seconds |
45 * | 1-59 minutes | X minutes ago | in X minutes |
46 * | 1-23 hours | X hours ago | in X hours |
47 * | 1 day | yesterday | tomorrow |
48 * | 2-6 days | X days ago | in X days |
49 * | 7 days | last week | next week |
50 * | 8 days-1 month | X weeks ago | in X weeks |
51 * | 1 month | last month | next month |
52 * | 2-3 months | X months ago | in X months |
53 * | 1 quarter | last quarter | next quarter |
54 * | 2-3 quarters | X quarters ago | in X quarters |
55 * | 1 year | last year | next year |
56 * | 2+ years | X years ago | in X years |
57 *
58 * @param laterDate - The date
59 * @param earlierDate - The date to compare with.
60 * @param options - An object with options.
61 * See MDN for details [Locale identification and negotiation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation)
62 * The narrow one could be similar to the short one for some locales.
63 *
64 * @returns The distance in words according to language-sensitive relative time formatting.
65 *
66 * @throws `date` must not be Invalid Date
67 * @throws `baseDate` must not be Invalid Date
68 * @throws `options.unit` must not be invalid Unit
69 * @throws `options.locale` must not be invalid locale
70 * @throws `options.localeMatcher` must not be invalid localeMatcher
71 * @throws `options.numeric` must not be invalid numeric
72 * @throws `options.style` must not be invalid style
73 *
74 * @example
75 * // What is the distance between the dates when the fist date is after the second?
76 * intlFormatDistance(
77 * new Date(1986, 3, 4, 11, 30, 0),
78 * new Date(1986, 3, 4, 10, 30, 0)
79 * )
80 * //=> 'in 1 hour'
81 *
82 * // What is the distance between the dates when the fist date is before the second?
83 * intlFormatDistance(
84 * new Date(1986, 3, 4, 10, 30, 0),
85 * new Date(1986, 3, 4, 11, 30, 0)
86 * )
87 * //=> '1 hour ago'
88 *
89 * @example
90 * // Use the unit option to force the function to output the result in quarters. Without setting it, the example would return "next year"
91 * intlFormatDistance(
92 * new Date(1987, 6, 4, 10, 30, 0),
93 * new Date(1986, 3, 4, 10, 30, 0),
94 * { unit: 'quarter' }
95 * )
96 * //=> 'in 5 quarters'
97 *
98 * @example
99 * // Use the locale option to get the result in Spanish. Without setting it, the example would return "in 1 hour".
100 * intlFormatDistance(
101 * new Date(1986, 3, 4, 11, 30, 0),
102 * new Date(1986, 3, 4, 10, 30, 0),
103 * { locale: 'es' }
104 * )
105 * //=> 'dentro de 1 hora'
106 *
107 * @example
108 * // Use the numeric option to force the function to use numeric values. Without setting it, the example would return "tomorrow".
109 * intlFormatDistance(
110 * new Date(1986, 3, 5, 11, 30, 0),
111 * new Date(1986, 3, 4, 11, 30, 0),
112 * { numeric: 'always' }
113 * )
114 * //=> 'in 1 day'
115 *
116 * @example
117 * // Use the style option to force the function to use short values. Without setting it, the example would return "in 2 years".
118 * intlFormatDistance(
119 * new Date(1988, 3, 4, 11, 30, 0),
120 * new Date(1986, 3, 4, 11, 30, 0),
121 * { style: 'short' }
122 * )
123 * //=> 'in 2 yr'
124 */
125export function intlFormatDistance(laterDate, earlierDate, options) {
126 let value = 0;
127 let unit;
128
129 const [laterDate_, earlierDate_] = normalizeDates(
130 options?.in,
131 laterDate,
132 earlierDate,
133 );
134
135 if (!options?.unit) {
136 // Get the unit based on diffInSeconds calculations if no unit is specified
137 const diffInSeconds = differenceInSeconds(laterDate_, earlierDate_); // The smallest unit
138
139 if (Math.abs(diffInSeconds) < secondsInMinute) {
140 value = differenceInSeconds(laterDate_, earlierDate_);
141 unit = "second";
142 } else if (Math.abs(diffInSeconds) < secondsInHour) {
143 value = differenceInMinutes(laterDate_, earlierDate_);
144 unit = "minute";
145 } else if (
146 Math.abs(diffInSeconds) < secondsInDay &&
147 Math.abs(differenceInCalendarDays(laterDate_, earlierDate_)) < 1
148 ) {
149 value = differenceInHours(laterDate_, earlierDate_);
150 unit = "hour";
151 } else if (
152 Math.abs(diffInSeconds) < secondsInWeek &&
153 (value = differenceInCalendarDays(laterDate_, earlierDate_)) &&
154 Math.abs(value) < 7
155 ) {
156 unit = "day";
157 } else if (Math.abs(diffInSeconds) < secondsInMonth) {
158 value = differenceInCalendarWeeks(laterDate_, earlierDate_);
159 unit = "week";
160 } else if (Math.abs(diffInSeconds) < secondsInQuarter) {
161 value = differenceInCalendarMonths(laterDate_, earlierDate_);
162 unit = "month";
163 } else if (Math.abs(diffInSeconds) < secondsInYear) {
164 if (differenceInCalendarQuarters(laterDate_, earlierDate_) < 4) {
165 // To filter out cases that are less than a year but match 4 quarters
166 value = differenceInCalendarQuarters(laterDate_, earlierDate_);
167 unit = "quarter";
168 } else {
169 value = differenceInCalendarYears(laterDate_, earlierDate_);
170 unit = "year";
171 }
172 } else {
173 value = differenceInCalendarYears(laterDate_, earlierDate_);
174 unit = "year";
175 }
176 } else {
177 // Get the value if unit is specified
178 unit = options?.unit;
179 if (unit === "second") {
180 value = differenceInSeconds(laterDate_, earlierDate_);
181 } else if (unit === "minute") {
182 value = differenceInMinutes(laterDate_, earlierDate_);
183 } else if (unit === "hour") {
184 value = differenceInHours(laterDate_, earlierDate_);
185 } else if (unit === "day") {
186 value = differenceInCalendarDays(laterDate_, earlierDate_);
187 } else if (unit === "week") {
188 value = differenceInCalendarWeeks(laterDate_, earlierDate_);
189 } else if (unit === "month") {
190 value = differenceInCalendarMonths(laterDate_, earlierDate_);
191 } else if (unit === "quarter") {
192 value = differenceInCalendarQuarters(laterDate_, earlierDate_);
193 } else if (unit === "year") {
194 value = differenceInCalendarYears(laterDate_, earlierDate_);
195 }
196 }
197
198 const rtf = new Intl.RelativeTimeFormat(options?.locale, {
199 numeric: "auto",
200 ...options,
201 });
202
203 return rtf.format(value, unit);
204}
205
206// Fallback for modularized imports:
207export default intlFormatDistance;