formatDistanceStrict.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { defaultLocale } from "./_lib/defaultLocale.js";
  2. import { getDefaultOptions } from "./_lib/defaultOptions.js";
  3. import { getRoundingMethod } from "./_lib/getRoundingMethod.js";
  4. import { getTimezoneOffsetInMilliseconds } from "./_lib/getTimezoneOffsetInMilliseconds.js";
  5. import { normalizeDates } from "./_lib/normalizeDates.js";
  6. import { compareAsc } from "./compareAsc.js";
  7. import {
  8. millisecondsInMinute,
  9. minutesInDay,
  10. minutesInMonth,
  11. minutesInYear,
  12. } from "./constants.js";
  13. /**
  14. * The {@link formatDistanceStrict} function options.
  15. */
  16. /**
  17. * The unit used to format the distance in {@link formatDistanceStrict}.
  18. */
  19. /**
  20. * @name formatDistanceStrict
  21. * @category Common Helpers
  22. * @summary Return the distance between the given dates in words.
  23. *
  24. * @description
  25. * Return the distance between the given dates in words, using strict units.
  26. * This is like `formatDistance`, but does not use helpers like 'almost', 'over',
  27. * 'less than' and the like.
  28. *
  29. * | Distance between dates | Result |
  30. * |------------------------|---------------------|
  31. * | 0 ... 59 secs | [0..59] seconds |
  32. * | 1 ... 59 mins | [1..59] minutes |
  33. * | 1 ... 23 hrs | [1..23] hours |
  34. * | 1 ... 29 days | [1..29] days |
  35. * | 1 ... 11 months | [1..11] months |
  36. * | 1 ... N years | [1..N] years |
  37. *
  38. * @param laterDate - The date
  39. * @param earlierDate - The date to compare with
  40. * @param options - An object with options
  41. *
  42. * @returns The distance in words
  43. *
  44. * @throws `date` must not be Invalid Date
  45. * @throws `baseDate` must not be Invalid Date
  46. * @throws `options.unit` must be 'second', 'minute', 'hour', 'day', 'month' or 'year'
  47. * @throws `options.locale` must contain `formatDistance` property
  48. *
  49. * @example
  50. * // What is the distance between 2 July 2014 and 1 January 2015?
  51. * const result = formatDistanceStrict(new Date(2014, 6, 2), new Date(2015, 0, 2))
  52. * //=> '6 months'
  53. *
  54. * @example
  55. * // What is the distance between 1 January 2015 00:00:15
  56. * // and 1 January 2015 00:00:00?
  57. * const result = formatDistanceStrict(
  58. * new Date(2015, 0, 1, 0, 0, 15),
  59. * new Date(2015, 0, 1, 0, 0, 0)
  60. * )
  61. * //=> '15 seconds'
  62. *
  63. * @example
  64. * // What is the distance from 1 January 2016
  65. * // to 1 January 2015, with a suffix?
  66. * const result = formatDistanceStrict(new Date(2015, 0, 1), new Date(2016, 0, 1), {
  67. * addSuffix: true
  68. * })
  69. * //=> '1 year ago'
  70. *
  71. * @example
  72. * // What is the distance from 1 January 2016
  73. * // to 1 January 2015, in minutes?
  74. * const result = formatDistanceStrict(new Date(2016, 0, 1), new Date(2015, 0, 1), {
  75. * unit: 'minute'
  76. * })
  77. * //=> '525600 minutes'
  78. *
  79. * @example
  80. * // What is the distance from 1 January 2015
  81. * // to 28 January 2015, in months, rounded up?
  82. * const result = formatDistanceStrict(new Date(2015, 0, 28), new Date(2015, 0, 1), {
  83. * unit: 'month',
  84. * roundingMethod: 'ceil'
  85. * })
  86. * //=> '1 month'
  87. *
  88. * @example
  89. * // What is the distance between 1 August 2016 and 1 January 2015 in Esperanto?
  90. * import { eoLocale } from 'date-fns/locale/eo'
  91. * const result = formatDistanceStrict(new Date(2016, 7, 1), new Date(2015, 0, 1), {
  92. * locale: eoLocale
  93. * })
  94. * //=> '1 jaro'
  95. */
  96. export function formatDistanceStrict(laterDate, earlierDate, options) {
  97. const defaultOptions = getDefaultOptions();
  98. const locale = options?.locale ?? defaultOptions.locale ?? defaultLocale;
  99. const comparison = compareAsc(laterDate, earlierDate);
  100. if (isNaN(comparison)) {
  101. throw new RangeError("Invalid time value");
  102. }
  103. const localizeOptions = Object.assign({}, options, {
  104. addSuffix: options?.addSuffix,
  105. comparison: comparison,
  106. });
  107. const [laterDate_, earlierDate_] = normalizeDates(
  108. options?.in,
  109. ...(comparison > 0 ? [earlierDate, laterDate] : [laterDate, earlierDate]),
  110. );
  111. const roundingMethod = getRoundingMethod(options?.roundingMethod ?? "round");
  112. const milliseconds = earlierDate_.getTime() - laterDate_.getTime();
  113. const minutes = milliseconds / millisecondsInMinute;
  114. const timezoneOffset =
  115. getTimezoneOffsetInMilliseconds(earlierDate_) -
  116. getTimezoneOffsetInMilliseconds(laterDate_);
  117. // Use DST-normalized difference in minutes for years, months and days;
  118. // use regular difference in minutes for hours, minutes and seconds.
  119. const dstNormalizedMinutes =
  120. (milliseconds - timezoneOffset) / millisecondsInMinute;
  121. const defaultUnit = options?.unit;
  122. let unit;
  123. if (!defaultUnit) {
  124. if (minutes < 1) {
  125. unit = "second";
  126. } else if (minutes < 60) {
  127. unit = "minute";
  128. } else if (minutes < minutesInDay) {
  129. unit = "hour";
  130. } else if (dstNormalizedMinutes < minutesInMonth) {
  131. unit = "day";
  132. } else if (dstNormalizedMinutes < minutesInYear) {
  133. unit = "month";
  134. } else {
  135. unit = "year";
  136. }
  137. } else {
  138. unit = defaultUnit;
  139. }
  140. // 0 up to 60 seconds
  141. if (unit === "second") {
  142. const seconds = roundingMethod(milliseconds / 1000);
  143. return locale.formatDistance("xSeconds", seconds, localizeOptions);
  144. // 1 up to 60 mins
  145. } else if (unit === "minute") {
  146. const roundedMinutes = roundingMethod(minutes);
  147. return locale.formatDistance("xMinutes", roundedMinutes, localizeOptions);
  148. // 1 up to 24 hours
  149. } else if (unit === "hour") {
  150. const hours = roundingMethod(minutes / 60);
  151. return locale.formatDistance("xHours", hours, localizeOptions);
  152. // 1 up to 30 days
  153. } else if (unit === "day") {
  154. const days = roundingMethod(dstNormalizedMinutes / minutesInDay);
  155. return locale.formatDistance("xDays", days, localizeOptions);
  156. // 1 up to 12 months
  157. } else if (unit === "month") {
  158. const months = roundingMethod(dstNormalizedMinutes / minutesInMonth);
  159. return months === 12 && defaultUnit !== "month"
  160. ? locale.formatDistance("xYears", 1, localizeOptions)
  161. : locale.formatDistance("xMonths", months, localizeOptions);
  162. // 1 year up to max Date
  163. } else {
  164. const years = roundingMethod(dstNormalizedMinutes / minutesInYear);
  165. return locale.formatDistance("xYears", years, localizeOptions);
  166. }
  167. }
  168. // Fallback for modularized imports:
  169. export default formatDistanceStrict;