formatDistanceStrict.mjs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { defaultLocale } from "./_lib/defaultLocale.mjs";
  2. import { getDefaultOptions } from "./_lib/defaultOptions.mjs";
  3. import { getRoundingMethod } from "./_lib/getRoundingMethod.mjs";
  4. import { getTimezoneOffsetInMilliseconds } from "./_lib/getTimezoneOffsetInMilliseconds.mjs";
  5. import { compareAsc } from "./compareAsc.mjs";
  6. import {
  7. millisecondsInMinute,
  8. minutesInDay,
  9. minutesInMonth,
  10. minutesInYear,
  11. } from "./constants.mjs";
  12. import { toDate } from "./toDate.mjs";
  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. * @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).
  39. *
  40. * @param date - The date
  41. * @param baseDate - The date to compare with
  42. * @param options - An object with options
  43. *
  44. * @returns The distance in words
  45. *
  46. * @throws `date` must not be Invalid Date
  47. * @throws `baseDate` must not be Invalid Date
  48. * @throws `options.unit` must be 'second', 'minute', 'hour', 'day', 'month' or 'year'
  49. * @throws `options.locale` must contain `formatDistance` property
  50. *
  51. * @example
  52. * // What is the distance between 2 July 2014 and 1 January 2015?
  53. * const result = formatDistanceStrict(new Date(2014, 6, 2), new Date(2015, 0, 2))
  54. * //=> '6 months'
  55. *
  56. * @example
  57. * // What is the distance between 1 January 2015 00:00:15
  58. * // and 1 January 2015 00:00:00?
  59. * const result = formatDistanceStrict(
  60. * new Date(2015, 0, 1, 0, 0, 15),
  61. * new Date(2015, 0, 1, 0, 0, 0)
  62. * )
  63. * //=> '15 seconds'
  64. *
  65. * @example
  66. * // What is the distance from 1 January 2016
  67. * // to 1 January 2015, with a suffix?
  68. * const result = formatDistanceStrict(new Date(2015, 0, 1), new Date(2016, 0, 1), {
  69. * addSuffix: true
  70. * })
  71. * //=> '1 year ago'
  72. *
  73. * @example
  74. * // What is the distance from 1 January 2016
  75. * // to 1 January 2015, in minutes?
  76. * const result = formatDistanceStrict(new Date(2016, 0, 1), new Date(2015, 0, 1), {
  77. * unit: 'minute'
  78. * })
  79. * //=> '525600 minutes'
  80. *
  81. * @example
  82. * // What is the distance from 1 January 2015
  83. * // to 28 January 2015, in months, rounded up?
  84. * const result = formatDistanceStrict(new Date(2015, 0, 28), new Date(2015, 0, 1), {
  85. * unit: 'month',
  86. * roundingMethod: 'ceil'
  87. * })
  88. * //=> '1 month'
  89. *
  90. * @example
  91. * // What is the distance between 1 August 2016 and 1 January 2015 in Esperanto?
  92. * import { eoLocale } from 'date-fns/locale/eo'
  93. * const result = formatDistanceStrict(new Date(2016, 7, 1), new Date(2015, 0, 1), {
  94. * locale: eoLocale
  95. * })
  96. * //=> '1 jaro'
  97. */
  98. export function formatDistanceStrict(date, baseDate, options) {
  99. const defaultOptions = getDefaultOptions();
  100. const locale = options?.locale ?? defaultOptions.locale ?? defaultLocale;
  101. const comparison = compareAsc(date, baseDate);
  102. if (isNaN(comparison)) {
  103. throw new RangeError("Invalid time value");
  104. }
  105. const localizeOptions = Object.assign({}, options, {
  106. addSuffix: options?.addSuffix,
  107. comparison: comparison,
  108. });
  109. let dateLeft;
  110. let dateRight;
  111. if (comparison > 0) {
  112. dateLeft = toDate(baseDate);
  113. dateRight = toDate(date);
  114. } else {
  115. dateLeft = toDate(date);
  116. dateRight = toDate(baseDate);
  117. }
  118. const roundingMethod = getRoundingMethod(options?.roundingMethod ?? "round");
  119. const milliseconds = dateRight.getTime() - dateLeft.getTime();
  120. const minutes = milliseconds / millisecondsInMinute;
  121. const timezoneOffset =
  122. getTimezoneOffsetInMilliseconds(dateRight) -
  123. getTimezoneOffsetInMilliseconds(dateLeft);
  124. // Use DST-normalized difference in minutes for years, months and days;
  125. // use regular difference in minutes for hours, minutes and seconds.
  126. const dstNormalizedMinutes =
  127. (milliseconds - timezoneOffset) / millisecondsInMinute;
  128. const defaultUnit = options?.unit;
  129. let unit;
  130. if (!defaultUnit) {
  131. if (minutes < 1) {
  132. unit = "second";
  133. } else if (minutes < 60) {
  134. unit = "minute";
  135. } else if (minutes < minutesInDay) {
  136. unit = "hour";
  137. } else if (dstNormalizedMinutes < minutesInMonth) {
  138. unit = "day";
  139. } else if (dstNormalizedMinutes < minutesInYear) {
  140. unit = "month";
  141. } else {
  142. unit = "year";
  143. }
  144. } else {
  145. unit = defaultUnit;
  146. }
  147. // 0 up to 60 seconds
  148. if (unit === "second") {
  149. const seconds = roundingMethod(milliseconds / 1000);
  150. return locale.formatDistance("xSeconds", seconds, localizeOptions);
  151. // 1 up to 60 mins
  152. } else if (unit === "minute") {
  153. const roundedMinutes = roundingMethod(minutes);
  154. return locale.formatDistance("xMinutes", roundedMinutes, localizeOptions);
  155. // 1 up to 24 hours
  156. } else if (unit === "hour") {
  157. const hours = roundingMethod(minutes / 60);
  158. return locale.formatDistance("xHours", hours, localizeOptions);
  159. // 1 up to 30 days
  160. } else if (unit === "day") {
  161. const days = roundingMethod(dstNormalizedMinutes / minutesInDay);
  162. return locale.formatDistance("xDays", days, localizeOptions);
  163. // 1 up to 12 months
  164. } else if (unit === "month") {
  165. const months = roundingMethod(dstNormalizedMinutes / minutesInMonth);
  166. return months === 12 && defaultUnit !== "month"
  167. ? locale.formatDistance("xYears", 1, localizeOptions)
  168. : locale.formatDistance("xMonths", months, localizeOptions);
  169. // 1 year up to max Date
  170. } else {
  171. const years = roundingMethod(dstNormalizedMinutes / minutesInYear);
  172. return locale.formatDistance("xYears", years, localizeOptions);
  173. }
  174. }
  175. // Fallback for modularized imports:
  176. export default formatDistanceStrict;