UNPKG

8.22 kBJavaScriptView Raw
1import {
2 secondsInDay,
3 secondsInHour,
4 secondsInMinute,
5 secondsInMonth,
6 secondsInQuarter,
7 secondsInWeek,
8 secondsInYear,
9} from "./constants.mjs";
10import { differenceInCalendarDays } from "./differenceInCalendarDays.mjs";
11import { differenceInCalendarMonths } from "./differenceInCalendarMonths.mjs";
12import { differenceInCalendarQuarters } from "./differenceInCalendarQuarters.mjs";
13import { differenceInCalendarWeeks } from "./differenceInCalendarWeeks.mjs";
14import { differenceInCalendarYears } from "./differenceInCalendarYears.mjs";
15import { differenceInHours } from "./differenceInHours.mjs";
16import { differenceInMinutes } from "./differenceInMinutes.mjs";
17import { differenceInSeconds } from "./differenceInSeconds.mjs";
18import { toDate } from "./toDate.mjs";
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 * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
59 *
60 * @param date - The date
61 * @param baseDate - The date to compare with.
62 * @param options - An object with options.
63 * 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)
64 * The narrow one could be similar to the short one for some locales.
65 *
66 * @returns The distance in words according to language-sensitive relative time formatting.
67 *
68 * @throws `date` must not be Invalid Date
69 * @throws `baseDate` must not be Invalid Date
70 * @throws `options.unit` must not be invalid Unit
71 * @throws `options.locale` must not be invalid locale
72 * @throws `options.localeMatcher` must not be invalid localeMatcher
73 * @throws `options.numeric` must not be invalid numeric
74 * @throws `options.style` must not be invalid style
75 *
76 * @example
77 * // What is the distance between the dates when the fist date is after the second?
78 * intlFormatDistance(
79 * new Date(1986, 3, 4, 11, 30, 0),
80 * new Date(1986, 3, 4, 10, 30, 0)
81 * )
82 * //=> 'in 1 hour'
83 *
84 * // What is the distance between the dates when the fist date is before the second?
85 * intlFormatDistance(
86 * new Date(1986, 3, 4, 10, 30, 0),
87 * new Date(1986, 3, 4, 11, 30, 0)
88 * )
89 * //=> '1 hour ago'
90 *
91 * @example
92 * // Use the unit option to force the function to output the result in quarters. Without setting it, the example would return "next year"
93 * intlFormatDistance(
94 * new Date(1987, 6, 4, 10, 30, 0),
95 * new Date(1986, 3, 4, 10, 30, 0),
96 * { unit: 'quarter' }
97 * )
98 * //=> 'in 5 quarters'
99 *
100 * @example
101 * // Use the locale option to get the result in Spanish. Without setting it, the example would return "in 1 hour".
102 * intlFormatDistance(
103 * new Date(1986, 3, 4, 11, 30, 0),
104 * new Date(1986, 3, 4, 10, 30, 0),
105 * { locale: 'es' }
106 * )
107 * //=> 'dentro de 1 hora'
108 *
109 * @example
110 * // Use the numeric option to force the function to use numeric values. Without setting it, the example would return "tomorrow".
111 * intlFormatDistance(
112 * new Date(1986, 3, 5, 11, 30, 0),
113 * new Date(1986, 3, 4, 11, 30, 0),
114 * { numeric: 'always' }
115 * )
116 * //=> 'in 1 day'
117 *
118 * @example
119 * // Use the style option to force the function to use short values. Without setting it, the example would return "in 2 years".
120 * intlFormatDistance(
121 * new Date(1988, 3, 4, 11, 30, 0),
122 * new Date(1986, 3, 4, 11, 30, 0),
123 * { style: 'short' }
124 * )
125 * //=> 'in 2 yr'
126 */
127export function intlFormatDistance(date, baseDate, options) {
128 let value = 0;
129 let unit;
130 const dateLeft = toDate(date);
131 const dateRight = toDate(baseDate);
132
133 if (!options?.unit) {
134 // Get the unit based on diffInSeconds calculations if no unit is specified
135 const diffInSeconds = differenceInSeconds(dateLeft, dateRight); // The smallest unit
136
137 if (Math.abs(diffInSeconds) < secondsInMinute) {
138 value = differenceInSeconds(dateLeft, dateRight);
139 unit = "second";
140 } else if (Math.abs(diffInSeconds) < secondsInHour) {
141 value = differenceInMinutes(dateLeft, dateRight);
142 unit = "minute";
143 } else if (
144 Math.abs(diffInSeconds) < secondsInDay &&
145 Math.abs(differenceInCalendarDays(dateLeft, dateRight)) < 1
146 ) {
147 value = differenceInHours(dateLeft, dateRight);
148 unit = "hour";
149 } else if (
150 Math.abs(diffInSeconds) < secondsInWeek &&
151 (value = differenceInCalendarDays(dateLeft, dateRight)) &&
152 Math.abs(value) < 7
153 ) {
154 unit = "day";
155 } else if (Math.abs(diffInSeconds) < secondsInMonth) {
156 value = differenceInCalendarWeeks(dateLeft, dateRight);
157 unit = "week";
158 } else if (Math.abs(diffInSeconds) < secondsInQuarter) {
159 value = differenceInCalendarMonths(dateLeft, dateRight);
160 unit = "month";
161 } else if (Math.abs(diffInSeconds) < secondsInYear) {
162 if (differenceInCalendarQuarters(dateLeft, dateRight) < 4) {
163 // To filter out cases that are less than a year but match 4 quarters
164 value = differenceInCalendarQuarters(dateLeft, dateRight);
165 unit = "quarter";
166 } else {
167 value = differenceInCalendarYears(dateLeft, dateRight);
168 unit = "year";
169 }
170 } else {
171 value = differenceInCalendarYears(dateLeft, dateRight);
172 unit = "year";
173 }
174 } else {
175 // Get the value if unit is specified
176 unit = options?.unit;
177 if (unit === "second") {
178 value = differenceInSeconds(dateLeft, dateRight);
179 } else if (unit === "minute") {
180 value = differenceInMinutes(dateLeft, dateRight);
181 } else if (unit === "hour") {
182 value = differenceInHours(dateLeft, dateRight);
183 } else if (unit === "day") {
184 value = differenceInCalendarDays(dateLeft, dateRight);
185 } else if (unit === "week") {
186 value = differenceInCalendarWeeks(dateLeft, dateRight);
187 } else if (unit === "month") {
188 value = differenceInCalendarMonths(dateLeft, dateRight);
189 } else if (unit === "quarter") {
190 value = differenceInCalendarQuarters(dateLeft, dateRight);
191 } else if (unit === "year") {
192 value = differenceInCalendarYears(dateLeft, dateRight);
193 }
194 }
195
196 const rtf = new Intl.RelativeTimeFormat(options?.locale, {
197 localeMatcher: options?.localeMatcher,
198 numeric: options?.numeric || "auto",
199 style: options?.style,
200 });
201
202 return rtf.format(value, unit);
203}
204
205// Fallback for modularized imports:
206export default intlFormatDistance;