1 | import { defaultLocale } from "./_lib/defaultLocale.js";
|
2 | import { getDefaultOptions } from "./_lib/defaultOptions.js";
|
3 | import { getTimezoneOffsetInMilliseconds } from "./_lib/getTimezoneOffsetInMilliseconds.js";
|
4 | import { normalizeDates } from "./_lib/normalizeDates.js";
|
5 | import { compareAsc } from "./compareAsc.js";
|
6 | import { minutesInDay, minutesInMonth } from "./constants.js";
|
7 | import { differenceInMonths } from "./differenceInMonths.js";
|
8 | import { differenceInSeconds } from "./differenceInSeconds.js";
|
9 |
|
10 | /**
|
11 | * The {@link formatDistance} function options.
|
12 | */
|
13 |
|
14 | /**
|
15 | * @name formatDistance
|
16 | * @category Common Helpers
|
17 | * @summary Return the distance between the given dates in words.
|
18 | *
|
19 | * @description
|
20 | * Return the distance between the given dates in words.
|
21 | *
|
22 | * | Distance between dates | Result |
|
23 | * |-------------------------------------------------------------------|---------------------|
|
24 | * | 0 ... 30 secs | less than a minute |
|
25 | * | 30 secs ... 1 min 30 secs | 1 minute |
|
26 | * | 1 min 30 secs ... 44 mins 30 secs | [2..44] minutes |
|
27 | * | 44 mins ... 30 secs ... 89 mins 30 secs | about 1 hour |
|
28 | * | 89 mins 30 secs ... 23 hrs 59 mins 30 secs | about [2..24] hours |
|
29 | * | 23 hrs 59 mins 30 secs ... 41 hrs 59 mins 30 secs | 1 day |
|
30 | * | 41 hrs 59 mins 30 secs ... 29 days 23 hrs 59 mins 30 secs | [2..30] days |
|
31 | * | 29 days 23 hrs 59 mins 30 secs ... 44 days 23 hrs 59 mins 30 secs | about 1 month |
|
32 | * | 44 days 23 hrs 59 mins 30 secs ... 59 days 23 hrs 59 mins 30 secs | about 2 months |
|
33 | * | 59 days 23 hrs 59 mins 30 secs ... 1 yr | [2..12] months |
|
34 | * | 1 yr ... 1 yr 3 months | about 1 year |
|
35 | * | 1 yr 3 months ... 1 yr 9 month s | over 1 year |
|
36 | * | 1 yr 9 months ... 2 yrs | almost 2 years |
|
37 | * | N yrs ... N yrs 3 months | about N years |
|
38 | * | N yrs 3 months ... N yrs 9 months | over N years |
|
39 | * | N yrs 9 months ... N+1 yrs | almost N+1 years |
|
40 | *
|
41 | * With `options.includeSeconds == true`:
|
42 | * | Distance between dates | Result |
|
43 | * |------------------------|----------------------|
|
44 | * | 0 secs ... 5 secs | less than 5 seconds |
|
45 | * | 5 secs ... 10 secs | less than 10 seconds |
|
46 | * | 10 secs ... 20 secs | less than 20 seconds |
|
47 | * | 20 secs ... 40 secs | half a minute |
|
48 | * | 40 secs ... 60 secs | less than a minute |
|
49 | * | 60 secs ... 90 secs | 1 minute |
|
50 | *
|
51 | * @param laterDate - The date
|
52 | * @param earlierDate - The date to compare with
|
53 | * @param options - An object with options
|
54 | *
|
55 | * @returns The distance in words
|
56 | *
|
57 | * @throws `date` must not be Invalid Date
|
58 | * @throws `baseDate` must not be Invalid Date
|
59 | * @throws `options.locale` must contain `formatDistance` property
|
60 | *
|
61 | * @example
|
62 | * // What is the distance between 2 July 2014 and 1 January 2015?
|
63 | * const result = formatDistance(new Date(2014, 6, 2), new Date(2015, 0, 1))
|
64 | * //=> '6 months'
|
65 | *
|
66 | * @example
|
67 | * // What is the distance between 1 January 2015 00:00:15
|
68 | * // and 1 January 2015 00:00:00, including seconds?
|
69 | * const result = formatDistance(
|
70 | * new Date(2015, 0, 1, 0, 0, 15),
|
71 | * new Date(2015, 0, 1, 0, 0, 0),
|
72 | * { includeSeconds: true }
|
73 | * )
|
74 | * //=> 'less than 20 seconds'
|
75 | *
|
76 | * @example
|
77 | * // What is the distance from 1 January 2016
|
78 | * // to 1 January 2015, with a suffix?
|
79 | * const result = formatDistance(new Date(2015, 0, 1), new Date(2016, 0, 1), {
|
80 | * addSuffix: true
|
81 | * })
|
82 | * //=> 'about 1 year ago'
|
83 | *
|
84 | * @example
|
85 | * // What is the distance between 1 August 2016 and 1 January 2015 in Esperanto?
|
86 | * import { eoLocale } from 'date-fns/locale/eo'
|
87 | * const result = formatDistance(new Date(2016, 7, 1), new Date(2015, 0, 1), {
|
88 | * locale: eoLocale
|
89 | * })
|
90 | * //=> 'pli ol 1 jaro'
|
91 | */
|
92 | export function formatDistance(laterDate, earlierDate, options) {
|
93 | const defaultOptions = getDefaultOptions();
|
94 | const locale = options?.locale ?? defaultOptions.locale ?? defaultLocale;
|
95 | const minutesInAlmostTwoDays = 2520;
|
96 |
|
97 | const comparison = compareAsc(laterDate, earlierDate);
|
98 |
|
99 | if (isNaN(comparison)) throw new RangeError("Invalid time value");
|
100 |
|
101 | const localizeOptions = Object.assign({}, options, {
|
102 | addSuffix: options?.addSuffix,
|
103 | comparison: comparison,
|
104 | });
|
105 |
|
106 | const [laterDate_, earlierDate_] = normalizeDates(
|
107 | options?.in,
|
108 | ...(comparison > 0 ? [earlierDate, laterDate] : [laterDate, earlierDate]),
|
109 | );
|
110 |
|
111 | const seconds = differenceInSeconds(earlierDate_, laterDate_);
|
112 | const offsetInSeconds =
|
113 | (getTimezoneOffsetInMilliseconds(earlierDate_) -
|
114 | getTimezoneOffsetInMilliseconds(laterDate_)) /
|
115 | 1000;
|
116 | const minutes = Math.round((seconds - offsetInSeconds) / 60);
|
117 | let months;
|
118 |
|
119 | // 0 up to 2 mins
|
120 | if (minutes < 2) {
|
121 | if (options?.includeSeconds) {
|
122 | if (seconds < 5) {
|
123 | return locale.formatDistance("lessThanXSeconds", 5, localizeOptions);
|
124 | } else if (seconds < 10) {
|
125 | return locale.formatDistance("lessThanXSeconds", 10, localizeOptions);
|
126 | } else if (seconds < 20) {
|
127 | return locale.formatDistance("lessThanXSeconds", 20, localizeOptions);
|
128 | } else if (seconds < 40) {
|
129 | return locale.formatDistance("halfAMinute", 0, localizeOptions);
|
130 | } else if (seconds < 60) {
|
131 | return locale.formatDistance("lessThanXMinutes", 1, localizeOptions);
|
132 | } else {
|
133 | return locale.formatDistance("xMinutes", 1, localizeOptions);
|
134 | }
|
135 | } else {
|
136 | if (minutes === 0) {
|
137 | return locale.formatDistance("lessThanXMinutes", 1, localizeOptions);
|
138 | } else {
|
139 | return locale.formatDistance("xMinutes", minutes, localizeOptions);
|
140 | }
|
141 | }
|
142 |
|
143 | // 2 mins up to 0.75 hrs
|
144 | } else if (minutes < 45) {
|
145 | return locale.formatDistance("xMinutes", minutes, localizeOptions);
|
146 |
|
147 | // 0.75 hrs up to 1.5 hrs
|
148 | } else if (minutes < 90) {
|
149 | return locale.formatDistance("aboutXHours", 1, localizeOptions);
|
150 |
|
151 | // 1.5 hrs up to 24 hrs
|
152 | } else if (minutes < minutesInDay) {
|
153 | const hours = Math.round(minutes / 60);
|
154 | return locale.formatDistance("aboutXHours", hours, localizeOptions);
|
155 |
|
156 | // 1 day up to 1.75 days
|
157 | } else if (minutes < minutesInAlmostTwoDays) {
|
158 | return locale.formatDistance("xDays", 1, localizeOptions);
|
159 |
|
160 | // 1.75 days up to 30 days
|
161 | } else if (minutes < minutesInMonth) {
|
162 | const days = Math.round(minutes / minutesInDay);
|
163 | return locale.formatDistance("xDays", days, localizeOptions);
|
164 |
|
165 | // 1 month up to 2 months
|
166 | } else if (minutes < minutesInMonth * 2) {
|
167 | months = Math.round(minutes / minutesInMonth);
|
168 | return locale.formatDistance("aboutXMonths", months, localizeOptions);
|
169 | }
|
170 |
|
171 | months = differenceInMonths(earlierDate_, laterDate_);
|
172 |
|
173 | // 2 months up to 12 months
|
174 | if (months < 12) {
|
175 | const nearestMonth = Math.round(minutes / minutesInMonth);
|
176 | return locale.formatDistance("xMonths", nearestMonth, localizeOptions);
|
177 |
|
178 | // 1 year up to max Date
|
179 | } else {
|
180 | const monthsSinceStartOfYear = months % 12;
|
181 | const years = Math.trunc(months / 12);
|
182 |
|
183 | // N years up to 1 years 3 months
|
184 | if (monthsSinceStartOfYear < 3) {
|
185 | return locale.formatDistance("aboutXYears", years, localizeOptions);
|
186 |
|
187 | // N years 3 months up to N years 9 months
|
188 | } else if (monthsSinceStartOfYear < 9) {
|
189 | return locale.formatDistance("overXYears", years, localizeOptions);
|
190 |
|
191 | // N years 9 months up to N year 12 months
|
192 | } else {
|
193 | return locale.formatDistance("almostXYears", years + 1, localizeOptions);
|
194 | }
|
195 | }
|
196 | }
|
197 |
|
198 | // Fallback for modularized imports:
|
199 | export default formatDistance;
|