2024-06-05 03:05:51 -07:00
|
|
|
|
// HumanizeDuration.js - https://git.io/j0HgmQ
|
|
|
|
|
|
|
|
|
|
// @ts-check
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {string | ((unitCount: number) => string)} Unit
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {("y" | "mo" | "w" | "d" | "h" | "m" | "s" | "ms")} UnitName
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {Object} UnitMeasures
|
|
|
|
|
* @prop {number} y
|
|
|
|
|
* @prop {number} mo
|
|
|
|
|
* @prop {number} w
|
|
|
|
|
* @prop {number} d
|
|
|
|
|
* @prop {number} h
|
|
|
|
|
* @prop {number} m
|
|
|
|
|
* @prop {number} s
|
|
|
|
|
* @prop {number} ms
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @typedef {[string, string, string, string, string, string, string, string, string, string]} DigitReplacements
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {Object} Language
|
|
|
|
|
* @prop {Unit} y
|
|
|
|
|
* @prop {Unit} mo
|
|
|
|
|
* @prop {Unit} w
|
|
|
|
|
* @prop {Unit} d
|
|
|
|
|
* @prop {Unit} h
|
|
|
|
|
* @prop {Unit} m
|
|
|
|
|
* @prop {Unit} s
|
|
|
|
|
* @prop {Unit} ms
|
|
|
|
|
* @prop {string} [decimal]
|
|
|
|
|
* @prop {string} [delimiter]
|
|
|
|
|
* @prop {DigitReplacements} [_digitReplacements]
|
|
|
|
|
* @prop {boolean} [_numberFirst]
|
2024-09-15 01:21:18 -07:00
|
|
|
|
* @prop {boolean} [_hideCountIf2]
|
2024-06-05 03:05:51 -07:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {Object} Options
|
|
|
|
|
* @prop {string} [language]
|
|
|
|
|
* @prop {Record<string, Language>} [languages]
|
|
|
|
|
* @prop {string[]} [fallbacks]
|
|
|
|
|
* @prop {string} [delimiter]
|
|
|
|
|
* @prop {string} [spacer]
|
|
|
|
|
* @prop {boolean} [round]
|
|
|
|
|
* @prop {number} [largest]
|
|
|
|
|
* @prop {UnitName[]} [units]
|
|
|
|
|
* @prop {string} [decimal]
|
|
|
|
|
* @prop {string} [conjunction]
|
|
|
|
|
* @prop {number} [maxDecimalPoints]
|
|
|
|
|
* @prop {UnitMeasures} [unitMeasures]
|
|
|
|
|
* @prop {boolean} [serialComma]
|
|
|
|
|
* @prop {DigitReplacements} [digitReplacements]
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @typedef {Required<Options>} NormalizedOptions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
// Fallback for `Object.assign` if relevant.
|
|
|
|
|
var assign =
|
|
|
|
|
Object.assign ||
|
|
|
|
|
/** @param {...any} destination */
|
|
|
|
|
function (destination) {
|
|
|
|
|
var source;
|
|
|
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
|
|
|
source = arguments[i];
|
|
|
|
|
for (var prop in source) {
|
|
|
|
|
if (has(source, prop)) {
|
|
|
|
|
destination[prop] = source[prop];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return destination;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Fallback for `Array.isArray` if relevant.
|
|
|
|
|
var isArray =
|
|
|
|
|
Array.isArray ||
|
|
|
|
|
function (arg) {
|
|
|
|
|
return Object.prototype.toString.call(arg) === "[object Array]";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// This has to be defined separately because of a bug: we want to alias
|
|
|
|
|
// `gr` and `el` for backwards-compatiblity. In a breaking change, we can
|
|
|
|
|
// remove `gr` entirely.
|
|
|
|
|
// See https://github.com/EvanHahn/HumanizeDuration.js/issues/143 for more.
|
|
|
|
|
var GREEK = language("έ", "μ", "ε", "η", "ώ", "λ", "δ", "χδ", ","); //
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @type {Record<string, Language>}
|
|
|
|
|
*/
|
|
|
|
|
var LANGUAGES = {
|
|
|
|
|
// Afrikaans (Afrikaans)
|
|
|
|
|
af: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
|
|
|
|
|
// አማርኛ (Amharic)
|
|
|
|
|
am: language("ዓ", "ወ", "ሳ", "ቀ", "ሰ", "ደ", "ሰከ", "ሳ", "ሚሊ"),
|
|
|
|
|
//العربية (Arabic) (RTL)
|
|
|
|
|
// https://github.com/EvanHahn/HumanizeDuration.js/issues/221#issuecomment-2119762498
|
|
|
|
|
// year -> ع stands for "عام" or س stands for "سنة"
|
|
|
|
|
// month -> ش stands for "شهر"
|
|
|
|
|
// week -> أ stands for "أسبوع"
|
|
|
|
|
// day -> ي stands for "يوم"
|
|
|
|
|
// hour -> س stands for "ساعة"
|
|
|
|
|
// minute -> د stands for "دقيقة"
|
|
|
|
|
// second -> ث stands for "ثانية"
|
|
|
|
|
ar: assign(language("س", "ش", "أ", "ي", "س", "د", "ث", "م ث", ","), {
|
2024-09-15 01:21:18 -07:00
|
|
|
|
_hideCountIf2: true,
|
2024-06-05 03:05:51 -07:00
|
|
|
|
_digitReplacements: ["۰", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"]
|
|
|
|
|
}),
|
|
|
|
|
// български (Bulgarian)
|
|
|
|
|
bg: language("г", "мес", "с", "д", "ч", "м", "сек", "мс", ","),
|
|
|
|
|
// বাংলা (Bengali)
|
|
|
|
|
bn: language("ব", "ম", "সপ্তা", "দ", "ঘ", "মি", "স", "মি.স"),
|
|
|
|
|
// català (Catalan)
|
|
|
|
|
ca: language("a", "mes", "set", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
//کوردیی ناوەڕاست (Central Kurdish) (RTL)
|
|
|
|
|
ckb: language("م چ", "چ", "خ", "ک", "ڕ", "ه", "م", "س", "."),
|
|
|
|
|
// čeština (Czech)
|
|
|
|
|
cs: language("r", "měs", "t", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// Cymraeg (Welsh)
|
|
|
|
|
cy: language("b", "mis", "wth", "d", "awr", "mun", "eil", "ms"),
|
|
|
|
|
// dansk (Danish)
|
|
|
|
|
da: language("å", "md", "u", "d", "t", "m", "s", "ms", ","),
|
|
|
|
|
// Deutsch (German)
|
|
|
|
|
de: language("J", "mo", "w", "t", "std", "m", "s", "ms", ","),
|
|
|
|
|
// Ελληνικά (Greek)
|
|
|
|
|
el: GREEK,
|
|
|
|
|
// English (English)
|
|
|
|
|
en: language("y", "mo", "w", "d", "h", "m", "s", "ms"),
|
|
|
|
|
// Esperanto (Esperanto)
|
|
|
|
|
eo: language("j", "mo", "se", "t", "h", "m", "s", "ms", ","),
|
|
|
|
|
// español (Spanish)
|
|
|
|
|
es: language("a", "me", "se", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// eesti keel (Estonian)
|
|
|
|
|
et: language("a", "k", "n", "p", "t", "m", "s", "ms", ","),
|
|
|
|
|
// euskara (Basque)
|
|
|
|
|
eu: language("u", "h", "a", "e", "o", "m", "s", "ms", ","),
|
|
|
|
|
//فارسی (Farsi/Persian) (RTL)
|
|
|
|
|
fa: language("س", "ما", "ه", "ر", "سا", "دقی", "ثانی", "میلیثانیه"),
|
|
|
|
|
// suomi (Finnish)
|
|
|
|
|
fi: language("v", "kk", "vk", "pv", "t", "m", "s", "ms", ","),
|
|
|
|
|
// føroyskt (Faroese)
|
|
|
|
|
fo: language("á", "má", "v", "d", "t", "m", "s", "ms", ","),
|
|
|
|
|
// français (French)
|
|
|
|
|
fr: language("a", "m", "sem", "j", "h", "m", "s", "ms", ","),
|
|
|
|
|
// Ελληνικά (Greek) (el)
|
|
|
|
|
gr: GREEK,
|
|
|
|
|
//עברית (Hebrew) (RTL)
|
|
|
|
|
he: language("ש׳", "ח׳", "שב׳", "י׳", "שע׳", "ד׳", "שנ׳", "מל׳"),
|
|
|
|
|
// hrvatski (Croatian)
|
|
|
|
|
hr: language("g", "mj", "t", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// हिंदी (Hindi)
|
|
|
|
|
hi: language("व", "म", "स", "द", "घ", "मि", "से", "मि.से"),
|
|
|
|
|
// magyar (Hungarian)
|
|
|
|
|
hu: language("é", "h", "hét", "n", "ó", "p", "mp", "ms", ","),
|
|
|
|
|
// Indonesia (Indonesian)
|
|
|
|
|
id: language("t", "b", "mgg", "h", "j", "m", "d", "md"),
|
|
|
|
|
// íslenska (Icelandic)
|
|
|
|
|
is: language("ár", "mán", "v", "d", "k", "m", "s", "ms"),
|
|
|
|
|
// italiano (Italian)
|
|
|
|
|
it: language("a", "me", "se", "g", "h", "m", "s", "ms", ","),
|
|
|
|
|
// 日本語 (Japanese)
|
|
|
|
|
ja: language("年", "月", "週", "日", "時", "分", "秒", "ミリ秒"),
|
|
|
|
|
// ភាសាខ្មែរ (Khmer)
|
|
|
|
|
km: language("ឆ", "ខ", "សប្តា", "ថ", "ម", "ន", "វ", "មវ"),
|
|
|
|
|
// ಕನ್ನಡ (Kannada)
|
|
|
|
|
kn: language("ವ", "ತ", "ವ", "ದ", "ಗಂ", "ನಿ", "ಸೆ", "ಮಿಸೆ"),
|
|
|
|
|
// 한국어 (Korean)
|
2024-09-15 01:21:18 -07:00
|
|
|
|
ko: language("년", "달", "주", "일", "시간", "분", "초", "밀리초"),
|
2024-06-05 03:05:51 -07:00
|
|
|
|
// Kurdî (Kurdish)
|
|
|
|
|
ku: language("sal", "m", "h", "r", "s", "d", "ç", "ms", ","),
|
|
|
|
|
// ລາວ (Lao)
|
|
|
|
|
lo: language("ປ", "ເດ", "ອ", "ວ", "ຊ", "ນທ", "ວິນ", "ມິລິວິນາທີ", ","),
|
|
|
|
|
// lietuvių (Lithuanian)
|
|
|
|
|
lt: language("met", "mėn", "sav", "d", "v", "m", "s", "ms", ","),
|
|
|
|
|
// latviešu (Latvian)
|
|
|
|
|
lv: language("g", "mēn", "n", "d", "st", "m", "s", "ms", ","),
|
|
|
|
|
// македонски (Macedonian)
|
|
|
|
|
mk: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
|
|
|
|
|
// монгол (Mongolian)
|
|
|
|
|
mn: language("ж", "с", "дх", "ө", "ц", "м", "с", "мс"),
|
|
|
|
|
// मराठी (Marathi)
|
|
|
|
|
mr: language("व", "म", "आ", "दि", "त", "मि", "से", "मि.से"),
|
|
|
|
|
// Melayu (Malay)
|
|
|
|
|
ms: language("thn", "bln", "mgg", "hr", "j", "m", "s", "ms"),
|
|
|
|
|
// Nederlands (Dutch)
|
|
|
|
|
nl: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
|
|
|
|
|
// norsk (Norwegian)
|
|
|
|
|
no: language("år", "mnd", "u", "d", "t", "m", "s", "ms", ","),
|
|
|
|
|
// polski (Polish)
|
|
|
|
|
pl: language("r", "mi", "t", "d", "g", "m", "s", "ms", ","),
|
|
|
|
|
// português (Portuguese)
|
|
|
|
|
pt: language("a", "mês", "sem", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// română (Romanian) săpt?
|
|
|
|
|
ro: language("a", "l", "să", "z", "h", "m", "s", "ms", ","),
|
|
|
|
|
// русский (Russian)
|
|
|
|
|
ru: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
|
|
|
|
|
// shqip (Albanian) orë? muaj?
|
|
|
|
|
sq: language("v", "mu", "j", "d", "o", "m", "s", "ms", ","),
|
|
|
|
|
// српски (Serbian)
|
|
|
|
|
sr: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
|
|
|
|
|
// தமிழ் (Tamil)
|
|
|
|
|
ta: language("ஆ", "மா", "வ", "நா", "ம", "நி", "வி", "மி.வி"),
|
|
|
|
|
// తెలుగు (Telugu)
|
|
|
|
|
te: language("సం", "నె", "వ", "రో", "గం", "ని", "సె", "మి.సె"), //
|
|
|
|
|
// українська (Ukrainian)
|
|
|
|
|
uk: language("р", "м", "т", "д", "г", "хв", "с", "мс", ","),
|
|
|
|
|
//اردو (Urdu) (RTL)
|
|
|
|
|
ur: language("س", "م", "ہ", "د", "گ", "م", "س", "م س"),
|
|
|
|
|
// slovenčina (Slovak)
|
|
|
|
|
sk: language("r", "mes", "t", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// slovenščina (Slovenian)
|
|
|
|
|
sl: language("l", "mes", "t", "d", "ur", "m", "s", "ms", ","),
|
|
|
|
|
// svenska (Swedish)
|
|
|
|
|
sv: language("å", "mån", "v", "d", "h", "m", "s", "ms", ","),
|
|
|
|
|
// Kiswahili (Swahili)
|
|
|
|
|
sw: assign(language("mw", "m", "w", "s", "h", "dk", "s", "ms"), {
|
|
|
|
|
_numberFirst: true
|
|
|
|
|
}),
|
|
|
|
|
// Türkçe (Turkish)
|
|
|
|
|
tr: language("y", "a", "h", "g", "sa", "d", "s", "ms", ","),
|
|
|
|
|
// ไทย (Thai)
|
|
|
|
|
th: language("ปี", "ด", "ส", "ว", "ชม", "น", "วิ", "มิลลิวินาที"),
|
|
|
|
|
// o'zbek (Uzbek)
|
|
|
|
|
uz: language("y", "o", "h", "k", "soa", "m", "s", "ms"),
|
|
|
|
|
// Ўзбек (Кирилл) (Uzbek (Cyrillic))
|
|
|
|
|
uz_CYR: language("й", "о", "х", "к", "соа", "д", "с", "мс"),
|
|
|
|
|
// Tiếng Việt (Vietnamese)
|
|
|
|
|
vi: language("n", "th", "t", "ng", "gi", "p", "g", "ms", ","),
|
|
|
|
|
// 中文 (简体) (Chinese, simplified)
|
|
|
|
|
zh_CN: language("年", "月", "周", "天", "时", "分", "秒", "毫秒"),
|
|
|
|
|
// 中文 (繁體) (Chinese, traditional)
|
|
|
|
|
zh_TW: language("年", "月", "週", "天", "時", "分", "秒", "毫秒")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for creating language definitions.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {Unit} y
|
|
|
|
|
* @param {Unit} mo
|
|
|
|
|
* @param {Unit} w
|
|
|
|
|
* @param {Unit} d
|
|
|
|
|
* @param {Unit} h
|
|
|
|
|
* @param {Unit} m
|
|
|
|
|
* @param {Unit} s
|
|
|
|
|
* @param {Unit} ms
|
|
|
|
|
* @param {string} [decimal]
|
|
|
|
|
* @returns {Language}
|
|
|
|
|
*/
|
|
|
|
|
function language(y, mo, w, d, h, m, s, ms, decimal) {
|
|
|
|
|
/** @type {Language} */
|
|
|
|
|
var result = { y: y, mo: mo, w: w, d: d, h: h, m: m, s: s, ms: ms };
|
|
|
|
|
if (typeof decimal !== "undefined") {
|
|
|
|
|
result.decimal = decimal;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Arabic.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {0 | 1 | 2}
|
|
|
|
|
*/
|
|
|
|
|
// function getArabicForm(c) {
|
|
|
|
|
// if (c === 2) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// }
|
|
|
|
|
// if (c > 2 && c < 11) {
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Polish.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {0 | 1 | 2 | 3}
|
|
|
|
|
*/
|
|
|
|
|
// function getPolishForm(c) {
|
|
|
|
|
// if (c === 1) {
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
// if (Math.floor(c) !== c) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// }
|
|
|
|
|
// if (c % 10 >= 2 && c % 10 <= 4 && !(c % 100 > 10 && c % 100 < 20)) {
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
// return 3;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Slavic languages.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {0 | 1 | 2 | 3}
|
|
|
|
|
*/
|
|
|
|
|
// function getSlavicForm(c) {
|
|
|
|
|
// if (Math.floor(c) !== c) {
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
// if (
|
|
|
|
|
// (c % 100 >= 5 && c % 100 <= 20) ||
|
|
|
|
|
// (c % 10 >= 5 && c % 10 <= 9) ||
|
|
|
|
|
// c % 10 === 0
|
|
|
|
|
// ) {
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
// if (c % 10 === 1) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// }
|
|
|
|
|
// if (c > 1) {
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Czech or Slovak.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {0 | 1 | 2 | 3}
|
|
|
|
|
*/
|
|
|
|
|
// function getCzechOrSlovakForm(c) {
|
|
|
|
|
// if (c === 1) {
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
// if (Math.floor(c) !== c) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// }
|
|
|
|
|
// if (c % 10 >= 2 && c % 10 <= 4 && c % 100 < 10) {
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
// return 3;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Lithuanian.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {0 | 1 | 2}
|
|
|
|
|
*/
|
|
|
|
|
// function getLithuanianForm(c) {
|
|
|
|
|
// if (c === 1 || (c % 10 === 1 && c % 100 > 20)) {
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
// if (
|
|
|
|
|
// Math.floor(c) !== c ||
|
|
|
|
|
// (c % 10 >= 2 && c % 100 > 20) ||
|
|
|
|
|
// (c % 10 >= 2 && c % 100 < 10)
|
|
|
|
|
// ) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// }
|
|
|
|
|
// return 2;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function for Latvian.
|
|
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} c
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
// function getLatvianForm(c) {
|
|
|
|
|
// return c % 10 === 1 && c % 100 !== 11;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @template T
|
|
|
|
|
* @param {T} obj
|
|
|
|
|
* @param {keyof T} key
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function has(obj, key) {
|
|
|
|
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {Pick<Required<Options>, "language" | "fallbacks" | "languages">} options
|
|
|
|
|
* @throws {Error} Throws an error if language is not found.
|
|
|
|
|
* @returns {Language}
|
|
|
|
|
*/
|
|
|
|
|
function getLanguage(options) {
|
|
|
|
|
var possibleLanguages = [options.language];
|
|
|
|
|
|
|
|
|
|
if (has(options, "fallbacks")) {
|
|
|
|
|
if (isArray(options.fallbacks) && options.fallbacks.length) {
|
|
|
|
|
possibleLanguages = possibleLanguages.concat(options.fallbacks);
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error("fallbacks must be an array with at least one element");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < possibleLanguages.length; i++) {
|
|
|
|
|
var languageToTry = possibleLanguages[i];
|
|
|
|
|
if (has(options.languages, languageToTry)) {
|
|
|
|
|
return options.languages[languageToTry];
|
|
|
|
|
}
|
|
|
|
|
if (has(LANGUAGES, languageToTry)) {
|
|
|
|
|
return LANGUAGES[languageToTry];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Error("No language found.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {Piece} piece
|
|
|
|
|
* @param {Language} language
|
|
|
|
|
* @param {Pick<Required<Options>, "decimal" | "spacer" | "maxDecimalPoints" | "digitReplacements">} options
|
|
|
|
|
*/
|
|
|
|
|
function renderPiece(piece, language, options) {
|
|
|
|
|
var unitName = piece.unitName;
|
|
|
|
|
var unitCount = piece.unitCount;
|
|
|
|
|
|
|
|
|
|
var spacer = options.spacer;
|
|
|
|
|
var maxDecimalPoints = options.maxDecimalPoints;
|
|
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
|
var decimal;
|
|
|
|
|
if (has(options, "decimal")) {
|
|
|
|
|
decimal = options.decimal;
|
|
|
|
|
} else if (has(language, "decimal")) {
|
|
|
|
|
decimal = language.decimal;
|
|
|
|
|
} else {
|
|
|
|
|
decimal = ".";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {undefined | DigitReplacements} */
|
|
|
|
|
var digitReplacements;
|
|
|
|
|
if ("digitReplacements" in options) {
|
|
|
|
|
digitReplacements = options.digitReplacements;
|
|
|
|
|
} else if ("_digitReplacements" in language) {
|
|
|
|
|
digitReplacements = language._digitReplacements;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
|
var formattedCount;
|
|
|
|
|
var normalizedUnitCount =
|
|
|
|
|
maxDecimalPoints === void 0
|
|
|
|
|
? unitCount
|
|
|
|
|
: Math.floor(unitCount * Math.pow(10, maxDecimalPoints)) /
|
|
|
|
|
Math.pow(10, maxDecimalPoints);
|
|
|
|
|
var countStr = normalizedUnitCount.toString();
|
2024-09-15 01:21:18 -07:00
|
|
|
|
|
|
|
|
|
if (language._hideCountIf2 && unitCount === 2) {
|
2024-06-05 03:05:51 -07:00
|
|
|
|
formattedCount = "";
|
2024-09-15 01:21:18 -07:00
|
|
|
|
spacer = "";
|
|
|
|
|
} else {
|
|
|
|
|
if (digitReplacements) {
|
|
|
|
|
formattedCount = "";
|
|
|
|
|
for (var i = 0; i < countStr.length; i++) {
|
|
|
|
|
var char = countStr[i];
|
|
|
|
|
if (char === ".") {
|
|
|
|
|
formattedCount += decimal;
|
|
|
|
|
} else {
|
|
|
|
|
// @ts-ignore because `char` should always be 0-9 at this point.
|
|
|
|
|
formattedCount += digitReplacements[char];
|
|
|
|
|
}
|
2024-06-05 03:05:51 -07:00
|
|
|
|
}
|
2024-09-15 01:21:18 -07:00
|
|
|
|
} else {
|
|
|
|
|
formattedCount = countStr.replace(".", decimal);
|
2024-06-05 03:05:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var languageWord = language[unitName];
|
|
|
|
|
var word;
|
|
|
|
|
if (typeof languageWord === "function") {
|
|
|
|
|
word = languageWord(unitCount);
|
|
|
|
|
} else {
|
|
|
|
|
word = languageWord;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (language._numberFirst) {
|
|
|
|
|
return word + spacer + formattedCount;
|
|
|
|
|
}
|
|
|
|
|
return formattedCount + spacer + word;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @typedef {Object} Piece
|
|
|
|
|
* @prop {UnitName} unitName
|
|
|
|
|
* @prop {number} unitCount
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {number} ms
|
|
|
|
|
* @param {Pick<Required<Options>, "units" | "unitMeasures" | "largest" | "round">} options
|
|
|
|
|
* @returns {Piece[]}
|
|
|
|
|
*/
|
|
|
|
|
function getPieces(ms, options) {
|
|
|
|
|
/** @type {UnitName} */
|
|
|
|
|
var unitName;
|
|
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
|
var i;
|
|
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
|
var unitCount;
|
|
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
|
var msRemaining;
|
|
|
|
|
|
|
|
|
|
var units = options.units;
|
|
|
|
|
var unitMeasures = options.unitMeasures;
|
|
|
|
|
var largest = "largest" in options ? options.largest : Infinity;
|
|
|
|
|
|
|
|
|
|
if (!units.length) return [];
|
|
|
|
|
|
|
|
|
|
// Get the counts for each unit. Doesn't round or truncate anything.
|
|
|
|
|
// For example, might create an object like `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`.
|
|
|
|
|
/** @type {Partial<Record<UnitName, number>>} */
|
|
|
|
|
var unitCounts = {};
|
|
|
|
|
msRemaining = ms;
|
|
|
|
|
for (i = 0; i < units.length; i++) {
|
|
|
|
|
unitName = units[i];
|
|
|
|
|
var unitMs = unitMeasures[unitName];
|
|
|
|
|
|
|
|
|
|
var isLast = i === units.length - 1;
|
|
|
|
|
unitCount = isLast
|
|
|
|
|
? msRemaining / unitMs
|
|
|
|
|
: Math.floor(msRemaining / unitMs);
|
|
|
|
|
unitCounts[unitName] = unitCount;
|
|
|
|
|
|
|
|
|
|
msRemaining -= unitCount * unitMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.round) {
|
|
|
|
|
// Update counts based on the `largest` option.
|
|
|
|
|
// For example, if `largest === 2` and `unitCount` is `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`,
|
|
|
|
|
// updates to something like `{ y: 7, m: 6.2 }`.
|
|
|
|
|
var unitsRemainingBeforeRound = largest;
|
|
|
|
|
for (i = 0; i < units.length; i++) {
|
|
|
|
|
unitName = units[i];
|
|
|
|
|
unitCount = unitCounts[unitName];
|
|
|
|
|
|
|
|
|
|
if (unitCount === 0) continue;
|
|
|
|
|
|
|
|
|
|
unitsRemainingBeforeRound--;
|
|
|
|
|
|
|
|
|
|
// "Take" the rest of the units into this one.
|
|
|
|
|
if (unitsRemainingBeforeRound === 0) {
|
|
|
|
|
for (var j = i + 1; j < units.length; j++) {
|
|
|
|
|
var smallerUnitName = units[j];
|
|
|
|
|
var smallerUnitCount = unitCounts[smallerUnitName];
|
|
|
|
|
unitCounts[unitName] +=
|
|
|
|
|
(smallerUnitCount * unitMeasures[smallerUnitName]) /
|
|
|
|
|
unitMeasures[unitName];
|
|
|
|
|
unitCounts[smallerUnitName] = 0;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Round the last piece (which should be the only non-integer).
|
|
|
|
|
//
|
|
|
|
|
// This can be a little tricky if the last piece "bubbles up" to a larger
|
|
|
|
|
// unit. For example, "3 days, 23.99 hours" should be rounded to "4 days".
|
|
|
|
|
// It can also require multiple passes. For example, "6 days, 23.99 hours"
|
|
|
|
|
// should become "1 week".
|
|
|
|
|
for (i = units.length - 1; i >= 0; i--) {
|
|
|
|
|
unitName = units[i];
|
|
|
|
|
unitCount = unitCounts[unitName];
|
|
|
|
|
|
|
|
|
|
if (unitCount === 0) continue;
|
|
|
|
|
|
|
|
|
|
var rounded = Math.round(unitCount);
|
|
|
|
|
unitCounts[unitName] = rounded;
|
|
|
|
|
|
|
|
|
|
if (i === 0) break;
|
|
|
|
|
|
|
|
|
|
var previousUnitName = units[i - 1];
|
|
|
|
|
var previousUnitMs = unitMeasures[previousUnitName];
|
|
|
|
|
var amountOfPreviousUnit = Math.floor(
|
|
|
|
|
(rounded * unitMeasures[unitName]) / previousUnitMs
|
|
|
|
|
);
|
|
|
|
|
if (amountOfPreviousUnit) {
|
|
|
|
|
unitCounts[previousUnitName] += amountOfPreviousUnit;
|
|
|
|
|
unitCounts[unitName] = 0;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {Piece[]} */
|
|
|
|
|
var result = [];
|
|
|
|
|
for (i = 0; i < units.length && result.length < largest; i++) {
|
|
|
|
|
unitName = units[i];
|
|
|
|
|
unitCount = unitCounts[unitName];
|
|
|
|
|
if (unitCount) {
|
|
|
|
|
result.push({ unitName: unitName, unitCount: unitCount });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
* @param {Piece[]} pieces
|
|
|
|
|
* @param {Pick<Required<Options>, "units" | "language" | "languages" | "fallbacks" | "delimiter" | "spacer" | "decimal" | "conjunction" | "maxDecimalPoints" | "serialComma" | "digitReplacements">} options
|
|
|
|
|
* @returns {string}
|
|
|
|
|
*/
|
|
|
|
|
function formatPieces(pieces, options) {
|
|
|
|
|
var language = getLanguage(options);
|
|
|
|
|
|
|
|
|
|
if (!pieces.length) {
|
|
|
|
|
var units = options.units;
|
|
|
|
|
var smallestUnitName = units[units.length - 1];
|
|
|
|
|
return renderPiece(
|
|
|
|
|
{ unitName: smallestUnitName, unitCount: 0 },
|
|
|
|
|
language,
|
|
|
|
|
options
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var conjunction = options.conjunction;
|
|
|
|
|
var serialComma = options.serialComma;
|
|
|
|
|
|
|
|
|
|
var delimiter;
|
|
|
|
|
if (has(options, "delimiter")) {
|
|
|
|
|
delimiter = options.delimiter;
|
|
|
|
|
} else if (has(language, "delimiter")) {
|
|
|
|
|
delimiter = language.delimiter;
|
|
|
|
|
} else {
|
|
|
|
|
delimiter = " ";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {string[]} */
|
|
|
|
|
var renderedPieces = [];
|
|
|
|
|
for (var i = 0; i < pieces.length; i++) {
|
|
|
|
|
renderedPieces.push(renderPiece(pieces[i], language, options));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!conjunction || pieces.length === 1) {
|
|
|
|
|
return renderedPieces.join(delimiter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pieces.length === 2) {
|
|
|
|
|
return renderedPieces.join(conjunction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
renderedPieces.slice(0, -1).join(delimiter) +
|
|
|
|
|
(serialComma ? "," : "") +
|
|
|
|
|
conjunction +
|
|
|
|
|
renderedPieces.slice(-1)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a humanizer, which lets you change the default options.
|
|
|
|
|
*
|
|
|
|
|
* @param {Options} [passedOptions]
|
|
|
|
|
*/
|
|
|
|
|
function humanizer(passedOptions) {
|
|
|
|
|
/**
|
|
|
|
|
* @param {number} ms
|
|
|
|
|
* @param {Options} [humanizerOptions]
|
|
|
|
|
* @returns {string}
|
|
|
|
|
*/
|
|
|
|
|
var result = function humanizer(ms, humanizerOptions) {
|
|
|
|
|
// Make sure we have a positive number.
|
|
|
|
|
//
|
|
|
|
|
// Has the nice side-effect of converting things to numbers. For example,
|
|
|
|
|
// converts `"123"` and `Number(123)` to `123`.
|
|
|
|
|
ms = Math.abs(ms);
|
|
|
|
|
|
|
|
|
|
var options = assign({}, result, humanizerOptions || {});
|
|
|
|
|
|
|
|
|
|
var pieces = getPieces(ms, options);
|
|
|
|
|
|
|
|
|
|
return formatPieces(pieces, options);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return assign(
|
|
|
|
|
result,
|
|
|
|
|
{
|
|
|
|
|
language: "en",
|
|
|
|
|
spacer: "",
|
|
|
|
|
conjunction: "",
|
|
|
|
|
serialComma: true,
|
|
|
|
|
units: ["y", "mo", "w", "d", "h", "m", "s"],
|
|
|
|
|
languages: {},
|
|
|
|
|
round: false,
|
|
|
|
|
unitMeasures: {
|
|
|
|
|
y: 31557600000,
|
|
|
|
|
mo: 2629800000,
|
|
|
|
|
w: 604800000,
|
|
|
|
|
d: 86400000,
|
|
|
|
|
h: 3600000,
|
|
|
|
|
m: 60000,
|
|
|
|
|
s: 1000,
|
|
|
|
|
ms: 1
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
passedOptions
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Humanize a duration.
|
|
|
|
|
*
|
|
|
|
|
* This is a wrapper around the default humanizer.
|
|
|
|
|
*/
|
|
|
|
|
var humanizeDuration = assign(humanizer({}), {
|
|
|
|
|
getSupportedLanguages: function getSupportedLanguages() {
|
|
|
|
|
var result = [];
|
|
|
|
|
for (var language in LANGUAGES) {
|
|
|
|
|
if (has(LANGUAGES, language) && language !== "gr") {
|
|
|
|
|
result.push(language);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
humanizer: humanizer
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
if (typeof define === "function" && define.amd) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
define(function () {
|
|
|
|
|
return humanizeDuration;
|
|
|
|
|
});
|
|
|
|
|
} else if (typeof module !== "undefined" && module.exports) {
|
|
|
|
|
module.exports = humanizeDuration;
|
|
|
|
|
} else {
|
|
|
|
|
this.humanizeDuration = humanizeDuration;
|
|
|
|
|
}
|
|
|
|
|
})();
|