humanize-duration.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. // HumanizeDuration.js - https://git.io/j0HgmQ
  2. // @ts-check
  3. /**
  4. * @typedef {string | ((unitCount: number) => string)} Unit
  5. */
  6. /**
  7. * @typedef {("y" | "mo" | "w" | "d" | "h" | "m" | "s" | "ms")} UnitName
  8. */
  9. /**
  10. * @typedef {Object} UnitMeasures
  11. * @prop {number} y
  12. * @prop {number} mo
  13. * @prop {number} w
  14. * @prop {number} d
  15. * @prop {number} h
  16. * @prop {number} m
  17. * @prop {number} s
  18. * @prop {number} ms
  19. */
  20. /**
  21. * @internal
  22. * @typedef {[string, string, string, string, string, string, string, string, string, string]} DigitReplacements
  23. */
  24. /**
  25. * @typedef {Object} Language
  26. * @prop {Unit} y
  27. * @prop {Unit} mo
  28. * @prop {Unit} w
  29. * @prop {Unit} d
  30. * @prop {Unit} h
  31. * @prop {Unit} m
  32. * @prop {Unit} s
  33. * @prop {Unit} ms
  34. * @prop {string} [decimal]
  35. * @prop {string} [delimiter]
  36. * @prop {DigitReplacements} [_digitReplacements]
  37. * @prop {boolean} [_numberFirst]
  38. */
  39. /**
  40. * @typedef {Object} Options
  41. * @prop {string} [language]
  42. * @prop {Record<string, Language>} [languages]
  43. * @prop {string[]} [fallbacks]
  44. * @prop {string} [delimiter]
  45. * @prop {string} [spacer]
  46. * @prop {boolean} [round]
  47. * @prop {number} [largest]
  48. * @prop {UnitName[]} [units]
  49. * @prop {string} [decimal]
  50. * @prop {string} [conjunction]
  51. * @prop {number} [maxDecimalPoints]
  52. * @prop {UnitMeasures} [unitMeasures]
  53. * @prop {boolean} [serialComma]
  54. * @prop {DigitReplacements} [digitReplacements]
  55. */
  56. /**
  57. * @internal
  58. * @typedef {Required<Options>} NormalizedOptions
  59. */
  60. (function () {
  61. // Fallback for `Object.assign` if relevant.
  62. var assign =
  63. Object.assign ||
  64. /** @param {...any} destination */
  65. function (destination) {
  66. var source;
  67. for (var i = 1; i < arguments.length; i++) {
  68. source = arguments[i];
  69. for (var prop in source) {
  70. if (has(source, prop)) {
  71. destination[prop] = source[prop];
  72. }
  73. }
  74. }
  75. return destination;
  76. };
  77. // Fallback for `Array.isArray` if relevant.
  78. var isArray =
  79. Array.isArray ||
  80. function (arg) {
  81. return Object.prototype.toString.call(arg) === "[object Array]";
  82. };
  83. // This has to be defined separately because of a bug: we want to alias
  84. // `gr` and `el` for backwards-compatiblity. In a breaking change, we can
  85. // remove `gr` entirely.
  86. // See https://github.com/EvanHahn/HumanizeDuration.js/issues/143 for more.
  87. var GREEK = language("έ", "μ", "ε", "η", "ώ", "λ", "δ", "χδ", ","); //
  88. /**
  89. * @internal
  90. * @type {Record<string, Language>}
  91. */
  92. var LANGUAGES = {
  93. // Afrikaans (Afrikaans)
  94. af: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
  95. // አማርኛ (Amharic)
  96. am: language("ዓ", "ወ", "ሳ", "ቀ", "ሰ", "ደ", "ሰከ", "ሳ", "ሚሊ"),
  97. //العربية (Arabic) (RTL)
  98. // https://github.com/EvanHahn/HumanizeDuration.js/issues/221#issuecomment-2119762498
  99. // year -> ع stands for "عام" or س stands for "سنة"
  100. // month -> ش stands for "شهر"
  101. // week -> أ stands for "أسبوع"
  102. // day -> ي stands for "يوم"
  103. // hour -> س stands for "ساعة"
  104. // minute -> د stands for "دقيقة"
  105. // second -> ث stands for "ثانية"
  106. ar: assign(language("س", "ش", "أ", "ي", "س", "د", "ث", "م ث", ","), {
  107. _digitReplacements: ["۰", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"]
  108. }),
  109. // български (Bulgarian)
  110. bg: language("г", "мес", "с", "д", "ч", "м", "сек", "мс", ","),
  111. // বাংলা (Bengali)
  112. bn: language("ব", "ম", "সপ্তা", "দ", "ঘ", "মি", "স", "মি.স"),
  113. // català (Catalan)
  114. ca: language("a", "mes", "set", "d", "h", "m", "s", "ms", ","),
  115. //کوردیی ناوەڕاست (Central Kurdish) (RTL)
  116. ckb: language("م چ", "چ", "خ", "ک", "ڕ", "ه", "م", "س", "."),
  117. // čeština (Czech)
  118. cs: language("r", "měs", "t", "d", "h", "m", "s", "ms", ","),
  119. // Cymraeg (Welsh)
  120. cy: language("b", "mis", "wth", "d", "awr", "mun", "eil", "ms"),
  121. // dansk (Danish)
  122. da: language("å", "md", "u", "d", "t", "m", "s", "ms", ","),
  123. // Deutsch (German)
  124. de: language("J", "mo", "w", "t", "std", "m", "s", "ms", ","),
  125. // Ελληνικά (Greek)
  126. el: GREEK,
  127. // English (English)
  128. en: language("y", "mo", "w", "d", "h", "m", "s", "ms"),
  129. // Esperanto (Esperanto)
  130. eo: language("j", "mo", "se", "t", "h", "m", "s", "ms", ","),
  131. // español (Spanish)
  132. es: language("a", "me", "se", "d", "h", "m", "s", "ms", ","),
  133. // eesti keel (Estonian)
  134. et: language("a", "k", "n", "p", "t", "m", "s", "ms", ","),
  135. // euskara (Basque)
  136. eu: language("u", "h", "a", "e", "o", "m", "s", "ms", ","),
  137. //فارسی (Farsi/Persian) (RTL)
  138. fa: language("س", "ما", "ه", "ر", "سا", "دقی", "ثانی", "میلی‌ثانیه"),
  139. // suomi (Finnish)
  140. fi: language("v", "kk", "vk", "pv", "t", "m", "s", "ms", ","),
  141. // føroyskt (Faroese)
  142. fo: language("á", "má", "v", "d", "t", "m", "s", "ms", ","),
  143. // français (French)
  144. fr: language("a", "m", "sem", "j", "h", "m", "s", "ms", ","),
  145. // Ελληνικά (Greek) (el)
  146. gr: GREEK,
  147. //עברית (Hebrew) (RTL)
  148. he: language("ש׳", "ח׳", "שב׳", "י׳", "שע׳", "ד׳", "שנ׳", "מל׳"),
  149. // hrvatski (Croatian)
  150. hr: language("g", "mj", "t", "d", "h", "m", "s", "ms", ","),
  151. // हिंदी (Hindi)
  152. hi: language("व", "म", "स", "द", "घ", "मि", "से", "मि.से"),
  153. // magyar (Hungarian)
  154. hu: language("é", "h", "hét", "n", "ó", "p", "mp", "ms", ","),
  155. // Indonesia (Indonesian)
  156. id: language("t", "b", "mgg", "h", "j", "m", "d", "md"),
  157. // íslenska (Icelandic)
  158. is: language("ár", "mán", "v", "d", "k", "m", "s", "ms"),
  159. // italiano (Italian)
  160. it: language("a", "me", "se", "g", "h", "m", "s", "ms", ","),
  161. // 日本語 (Japanese)
  162. ja: language("年", "月", "週", "日", "時", "分", "秒", "ミリ秒"),
  163. // ភាសាខ្មែរ (Khmer)
  164. km: language("ឆ", "ខ", "សប្តា", "ថ", "ម", "ន", "វ", "មវ"),
  165. // ಕನ್ನಡ (Kannada)
  166. kn: language("ವ", "ತ", "ವ", "ದ", "ಗಂ", "ನಿ", "ಸೆ", "ಮಿಸೆ"),
  167. // 한국어 (Korean)
  168. ko: language("년", "월", "주", "일", "시", "분", "초", "밀리초"),
  169. // Kurdî (Kurdish)
  170. ku: language("sal", "m", "h", "r", "s", "d", "ç", "ms", ","),
  171. // ລາວ (Lao)
  172. lo: language("ປ", "ເດ", "ອ", "ວ", "ຊ", "ນທ", "ວິນ", "ມິລິວິນາທີ", ","),
  173. // lietuvių (Lithuanian)
  174. lt: language("met", "mėn", "sav", "d", "v", "m", "s", "ms", ","),
  175. // latviešu (Latvian)
  176. lv: language("g", "mēn", "n", "d", "st", "m", "s", "ms", ","),
  177. // македонски (Macedonian)
  178. mk: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
  179. // монгол (Mongolian)
  180. mn: language("ж", "с", "дх", "ө", "ц", "м", "с", "мс"),
  181. // मराठी (Marathi)
  182. mr: language("व", "म", "आ", "दि", "त", "मि", "से", "मि.से"),
  183. // Melayu (Malay)
  184. ms: language("thn", "bln", "mgg", "hr", "j", "m", "s", "ms"),
  185. // Nederlands (Dutch)
  186. nl: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
  187. // norsk (Norwegian)
  188. no: language("år", "mnd", "u", "d", "t", "m", "s", "ms", ","),
  189. // polski (Polish)
  190. pl: language("r", "mi", "t", "d", "g", "m", "s", "ms", ","),
  191. // português (Portuguese)
  192. pt: language("a", "mês", "sem", "d", "h", "m", "s", "ms", ","),
  193. // română (Romanian) săpt?
  194. ro: language("a", "l", "să", "z", "h", "m", "s", "ms", ","),
  195. // русский (Russian)
  196. ru: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
  197. // shqip (Albanian) orë? muaj?
  198. sq: language("v", "mu", "j", "d", "o", "m", "s", "ms", ","),
  199. // српски (Serbian)
  200. sr: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
  201. // தமிழ் (Tamil)
  202. ta: language("ஆ", "மா", "வ", "நா", "ம", "நி", "வி", "மி.வி"),
  203. // తెలుగు (Telugu)
  204. te: language("సం", "నె", "వ", "రో", "గం", "ని", "సె", "మి.సె"), //
  205. // українська (Ukrainian)
  206. uk: language("р", "м", "т", "д", "г", "хв", "с", "мс", ","),
  207. //اردو (Urdu) (RTL)
  208. ur: language("س", "م", "ہ", "د", "گ", "م", "س", "م س"),
  209. // slovenčina (Slovak)
  210. sk: language("r", "mes", "t", "d", "h", "m", "s", "ms", ","),
  211. // slovenščina (Slovenian)
  212. sl: language("l", "mes", "t", "d", "ur", "m", "s", "ms", ","),
  213. // svenska (Swedish)
  214. sv: language("å", "mån", "v", "d", "h", "m", "s", "ms", ","),
  215. // Kiswahili (Swahili)
  216. sw: assign(language("mw", "m", "w", "s", "h", "dk", "s", "ms"), {
  217. _numberFirst: true
  218. }),
  219. // Türkçe (Turkish)
  220. tr: language("y", "a", "h", "g", "sa", "d", "s", "ms", ","),
  221. // ไทย (Thai)
  222. th: language("ปี", "ด", "ส", "ว", "ชม", "น", "วิ", "มิลลิวินาที"),
  223. // o'zbek (Uzbek)
  224. uz: language("y", "o", "h", "k", "soa", "m", "s", "ms"),
  225. // Ўзбек (Кирилл) (Uzbek (Cyrillic))
  226. uz_CYR: language("й", "о", "х", "к", "соа", "д", "с", "мс"),
  227. // Tiếng Việt (Vietnamese)
  228. vi: language("n", "th", "t", "ng", "gi", "p", "g", "ms", ","),
  229. // 中文 (简体) (Chinese, simplified)
  230. zh_CN: language("年", "月", "周", "天", "时", "分", "秒", "毫秒"),
  231. // 中文 (繁體) (Chinese, traditional)
  232. zh_TW: language("年", "月", "週", "天", "時", "分", "秒", "毫秒")
  233. };
  234. /**
  235. * Helper function for creating language definitions.
  236. *
  237. * @internal
  238. * @param {Unit} y
  239. * @param {Unit} mo
  240. * @param {Unit} w
  241. * @param {Unit} d
  242. * @param {Unit} h
  243. * @param {Unit} m
  244. * @param {Unit} s
  245. * @param {Unit} ms
  246. * @param {string} [decimal]
  247. * @returns {Language}
  248. */
  249. function language(y, mo, w, d, h, m, s, ms, decimal) {
  250. /** @type {Language} */
  251. var result = { y: y, mo: mo, w: w, d: d, h: h, m: m, s: s, ms: ms };
  252. if (typeof decimal !== "undefined") {
  253. result.decimal = decimal;
  254. }
  255. return result;
  256. }
  257. /**
  258. * Helper function for Arabic.
  259. *
  260. * @internal
  261. * @param {number} c
  262. * @returns {0 | 1 | 2}
  263. */
  264. // function getArabicForm(c) {
  265. // if (c === 2) {
  266. // return 1;
  267. // }
  268. // if (c > 2 && c < 11) {
  269. // return 2;
  270. // }
  271. // return 0;
  272. // }
  273. /**
  274. * Helper function for Polish.
  275. *
  276. * @internal
  277. * @param {number} c
  278. * @returns {0 | 1 | 2 | 3}
  279. */
  280. // function getPolishForm(c) {
  281. // if (c === 1) {
  282. // return 0;
  283. // }
  284. // if (Math.floor(c) !== c) {
  285. // return 1;
  286. // }
  287. // if (c % 10 >= 2 && c % 10 <= 4 && !(c % 100 > 10 && c % 100 < 20)) {
  288. // return 2;
  289. // }
  290. // return 3;
  291. // }
  292. /**
  293. * Helper function for Slavic languages.
  294. *
  295. * @internal
  296. * @param {number} c
  297. * @returns {0 | 1 | 2 | 3}
  298. */
  299. // function getSlavicForm(c) {
  300. // if (Math.floor(c) !== c) {
  301. // return 2;
  302. // }
  303. // if (
  304. // (c % 100 >= 5 && c % 100 <= 20) ||
  305. // (c % 10 >= 5 && c % 10 <= 9) ||
  306. // c % 10 === 0
  307. // ) {
  308. // return 0;
  309. // }
  310. // if (c % 10 === 1) {
  311. // return 1;
  312. // }
  313. // if (c > 1) {
  314. // return 2;
  315. // }
  316. // return 0;
  317. // }
  318. /**
  319. * Helper function for Czech or Slovak.
  320. *
  321. * @internal
  322. * @param {number} c
  323. * @returns {0 | 1 | 2 | 3}
  324. */
  325. // function getCzechOrSlovakForm(c) {
  326. // if (c === 1) {
  327. // return 0;
  328. // }
  329. // if (Math.floor(c) !== c) {
  330. // return 1;
  331. // }
  332. // if (c % 10 >= 2 && c % 10 <= 4 && c % 100 < 10) {
  333. // return 2;
  334. // }
  335. // return 3;
  336. // }
  337. /**
  338. * Helper function for Lithuanian.
  339. *
  340. * @internal
  341. * @param {number} c
  342. * @returns {0 | 1 | 2}
  343. */
  344. // function getLithuanianForm(c) {
  345. // if (c === 1 || (c % 10 === 1 && c % 100 > 20)) {
  346. // return 0;
  347. // }
  348. // if (
  349. // Math.floor(c) !== c ||
  350. // (c % 10 >= 2 && c % 100 > 20) ||
  351. // (c % 10 >= 2 && c % 100 < 10)
  352. // ) {
  353. // return 1;
  354. // }
  355. // return 2;
  356. // }
  357. /**
  358. * Helper function for Latvian.
  359. *
  360. * @internal
  361. * @param {number} c
  362. * @returns {boolean}
  363. */
  364. // function getLatvianForm(c) {
  365. // return c % 10 === 1 && c % 100 !== 11;
  366. // }
  367. /**
  368. * @internal
  369. * @template T
  370. * @param {T} obj
  371. * @param {keyof T} key
  372. * @returns {boolean}
  373. */
  374. function has(obj, key) {
  375. return Object.prototype.hasOwnProperty.call(obj, key);
  376. }
  377. /**
  378. * @internal
  379. * @param {Pick<Required<Options>, "language" | "fallbacks" | "languages">} options
  380. * @throws {Error} Throws an error if language is not found.
  381. * @returns {Language}
  382. */
  383. function getLanguage(options) {
  384. var possibleLanguages = [options.language];
  385. if (has(options, "fallbacks")) {
  386. if (isArray(options.fallbacks) && options.fallbacks.length) {
  387. possibleLanguages = possibleLanguages.concat(options.fallbacks);
  388. } else {
  389. throw new Error("fallbacks must be an array with at least one element");
  390. }
  391. }
  392. for (var i = 0; i < possibleLanguages.length; i++) {
  393. var languageToTry = possibleLanguages[i];
  394. if (has(options.languages, languageToTry)) {
  395. return options.languages[languageToTry];
  396. }
  397. if (has(LANGUAGES, languageToTry)) {
  398. return LANGUAGES[languageToTry];
  399. }
  400. }
  401. throw new Error("No language found.");
  402. }
  403. /**
  404. * @internal
  405. * @param {Piece} piece
  406. * @param {Language} language
  407. * @param {Pick<Required<Options>, "decimal" | "spacer" | "maxDecimalPoints" | "digitReplacements">} options
  408. */
  409. function renderPiece(piece, language, options) {
  410. var unitName = piece.unitName;
  411. var unitCount = piece.unitCount;
  412. var spacer = options.spacer;
  413. var maxDecimalPoints = options.maxDecimalPoints;
  414. /** @type {string} */
  415. var decimal;
  416. if (has(options, "decimal")) {
  417. decimal = options.decimal;
  418. } else if (has(language, "decimal")) {
  419. decimal = language.decimal;
  420. } else {
  421. decimal = ".";
  422. }
  423. /** @type {undefined | DigitReplacements} */
  424. var digitReplacements;
  425. if ("digitReplacements" in options) {
  426. digitReplacements = options.digitReplacements;
  427. } else if ("_digitReplacements" in language) {
  428. digitReplacements = language._digitReplacements;
  429. }
  430. /** @type {string} */
  431. var formattedCount;
  432. var normalizedUnitCount =
  433. maxDecimalPoints === void 0
  434. ? unitCount
  435. : Math.floor(unitCount * Math.pow(10, maxDecimalPoints)) /
  436. Math.pow(10, maxDecimalPoints);
  437. var countStr = normalizedUnitCount.toString();
  438. if (digitReplacements) {
  439. formattedCount = "";
  440. for (var i = 0; i < countStr.length; i++) {
  441. var char = countStr[i];
  442. if (char === ".") {
  443. formattedCount += decimal;
  444. } else {
  445. // @ts-ignore because `char` should always be 0-9 at this point.
  446. formattedCount += digitReplacements[char];
  447. }
  448. }
  449. } else {
  450. formattedCount = countStr.replace(".", decimal);
  451. }
  452. var languageWord = language[unitName];
  453. var word;
  454. if (typeof languageWord === "function") {
  455. word = languageWord(unitCount);
  456. } else {
  457. word = languageWord;
  458. }
  459. if (language._numberFirst) {
  460. return word + spacer + formattedCount;
  461. }
  462. return formattedCount + spacer + word;
  463. }
  464. /**
  465. * @internal
  466. * @typedef {Object} Piece
  467. * @prop {UnitName} unitName
  468. * @prop {number} unitCount
  469. */
  470. /**
  471. * @internal
  472. * @param {number} ms
  473. * @param {Pick<Required<Options>, "units" | "unitMeasures" | "largest" | "round">} options
  474. * @returns {Piece[]}
  475. */
  476. function getPieces(ms, options) {
  477. /** @type {UnitName} */
  478. var unitName;
  479. /** @type {number} */
  480. var i;
  481. /** @type {number} */
  482. var unitCount;
  483. /** @type {number} */
  484. var msRemaining;
  485. var units = options.units;
  486. var unitMeasures = options.unitMeasures;
  487. var largest = "largest" in options ? options.largest : Infinity;
  488. if (!units.length) return [];
  489. // Get the counts for each unit. Doesn't round or truncate anything.
  490. // For example, might create an object like `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`.
  491. /** @type {Partial<Record<UnitName, number>>} */
  492. var unitCounts = {};
  493. msRemaining = ms;
  494. for (i = 0; i < units.length; i++) {
  495. unitName = units[i];
  496. var unitMs = unitMeasures[unitName];
  497. var isLast = i === units.length - 1;
  498. unitCount = isLast
  499. ? msRemaining / unitMs
  500. : Math.floor(msRemaining / unitMs);
  501. unitCounts[unitName] = unitCount;
  502. msRemaining -= unitCount * unitMs;
  503. }
  504. if (options.round) {
  505. // Update counts based on the `largest` option.
  506. // For example, if `largest === 2` and `unitCount` is `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`,
  507. // updates to something like `{ y: 7, m: 6.2 }`.
  508. var unitsRemainingBeforeRound = largest;
  509. for (i = 0; i < units.length; i++) {
  510. unitName = units[i];
  511. unitCount = unitCounts[unitName];
  512. if (unitCount === 0) continue;
  513. unitsRemainingBeforeRound--;
  514. // "Take" the rest of the units into this one.
  515. if (unitsRemainingBeforeRound === 0) {
  516. for (var j = i + 1; j < units.length; j++) {
  517. var smallerUnitName = units[j];
  518. var smallerUnitCount = unitCounts[smallerUnitName];
  519. unitCounts[unitName] +=
  520. (smallerUnitCount * unitMeasures[smallerUnitName]) /
  521. unitMeasures[unitName];
  522. unitCounts[smallerUnitName] = 0;
  523. }
  524. break;
  525. }
  526. }
  527. // Round the last piece (which should be the only non-integer).
  528. //
  529. // This can be a little tricky if the last piece "bubbles up" to a larger
  530. // unit. For example, "3 days, 23.99 hours" should be rounded to "4 days".
  531. // It can also require multiple passes. For example, "6 days, 23.99 hours"
  532. // should become "1 week".
  533. for (i = units.length - 1; i >= 0; i--) {
  534. unitName = units[i];
  535. unitCount = unitCounts[unitName];
  536. if (unitCount === 0) continue;
  537. var rounded = Math.round(unitCount);
  538. unitCounts[unitName] = rounded;
  539. if (i === 0) break;
  540. var previousUnitName = units[i - 1];
  541. var previousUnitMs = unitMeasures[previousUnitName];
  542. var amountOfPreviousUnit = Math.floor(
  543. (rounded * unitMeasures[unitName]) / previousUnitMs
  544. );
  545. if (amountOfPreviousUnit) {
  546. unitCounts[previousUnitName] += amountOfPreviousUnit;
  547. unitCounts[unitName] = 0;
  548. } else {
  549. break;
  550. }
  551. }
  552. }
  553. /** @type {Piece[]} */
  554. var result = [];
  555. for (i = 0; i < units.length && result.length < largest; i++) {
  556. unitName = units[i];
  557. unitCount = unitCounts[unitName];
  558. if (unitCount) {
  559. result.push({ unitName: unitName, unitCount: unitCount });
  560. }
  561. }
  562. return result;
  563. }
  564. /**
  565. * @internal
  566. * @param {Piece[]} pieces
  567. * @param {Pick<Required<Options>, "units" | "language" | "languages" | "fallbacks" | "delimiter" | "spacer" | "decimal" | "conjunction" | "maxDecimalPoints" | "serialComma" | "digitReplacements">} options
  568. * @returns {string}
  569. */
  570. function formatPieces(pieces, options) {
  571. var language = getLanguage(options);
  572. if (!pieces.length) {
  573. var units = options.units;
  574. var smallestUnitName = units[units.length - 1];
  575. return renderPiece(
  576. { unitName: smallestUnitName, unitCount: 0 },
  577. language,
  578. options
  579. );
  580. }
  581. var conjunction = options.conjunction;
  582. var serialComma = options.serialComma;
  583. var delimiter;
  584. if (has(options, "delimiter")) {
  585. delimiter = options.delimiter;
  586. } else if (has(language, "delimiter")) {
  587. delimiter = language.delimiter;
  588. } else {
  589. delimiter = " ";
  590. }
  591. /** @type {string[]} */
  592. var renderedPieces = [];
  593. for (var i = 0; i < pieces.length; i++) {
  594. renderedPieces.push(renderPiece(pieces[i], language, options));
  595. }
  596. if (!conjunction || pieces.length === 1) {
  597. return renderedPieces.join(delimiter);
  598. }
  599. if (pieces.length === 2) {
  600. return renderedPieces.join(conjunction);
  601. }
  602. return (
  603. renderedPieces.slice(0, -1).join(delimiter) +
  604. (serialComma ? "," : "") +
  605. conjunction +
  606. renderedPieces.slice(-1)
  607. );
  608. }
  609. /**
  610. * Create a humanizer, which lets you change the default options.
  611. *
  612. * @param {Options} [passedOptions]
  613. */
  614. function humanizer(passedOptions) {
  615. /**
  616. * @param {number} ms
  617. * @param {Options} [humanizerOptions]
  618. * @returns {string}
  619. */
  620. var result = function humanizer(ms, humanizerOptions) {
  621. // Make sure we have a positive number.
  622. //
  623. // Has the nice side-effect of converting things to numbers. For example,
  624. // converts `"123"` and `Number(123)` to `123`.
  625. ms = Math.abs(ms);
  626. var options = assign({}, result, humanizerOptions || {});
  627. var pieces = getPieces(ms, options);
  628. return formatPieces(pieces, options);
  629. };
  630. return assign(
  631. result,
  632. {
  633. language: "en",
  634. spacer: "",
  635. conjunction: "",
  636. serialComma: true,
  637. units: ["y", "mo", "w", "d", "h", "m", "s"],
  638. languages: {},
  639. round: false,
  640. unitMeasures: {
  641. y: 31557600000,
  642. mo: 2629800000,
  643. w: 604800000,
  644. d: 86400000,
  645. h: 3600000,
  646. m: 60000,
  647. s: 1000,
  648. ms: 1
  649. }
  650. },
  651. passedOptions
  652. );
  653. }
  654. /**
  655. * Humanize a duration.
  656. *
  657. * This is a wrapper around the default humanizer.
  658. */
  659. var humanizeDuration = assign(humanizer({}), {
  660. getSupportedLanguages: function getSupportedLanguages() {
  661. var result = [];
  662. for (var language in LANGUAGES) {
  663. if (has(LANGUAGES, language) && language !== "gr") {
  664. result.push(language);
  665. }
  666. }
  667. return result;
  668. },
  669. humanizer: humanizer
  670. });
  671. // @ts-ignore
  672. if (typeof define === "function" && define.amd) {
  673. // @ts-ignore
  674. define(function () {
  675. return humanizeDuration;
  676. });
  677. } else if (typeof module !== "undefined" && module.exports) {
  678. module.exports = humanizeDuration;
  679. } else {
  680. this.humanizeDuration = humanizeDuration;
  681. }
  682. })();