humanize-duration.js 22 KB

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