UNPKG

@bitrix24/b24jssdk

Version:
1,940 lines (1,764 loc) 863 kB
/** * @version @bitrix24/b24jssdk v0.5.1 * @copyright (c) 2025 Bitrix24 * @licence MIT * @links https://github.com/bitrix24/b24jssdk - GitHub * @links https://bitrix24.github.io/b24jssdk/ - Documentation */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.B24Js = global.B24Js || {})); })(this, (function (exports) { 'use strict'; var LoggerType = /* @__PURE__ */ ((LoggerType2) => { LoggerType2["desktop"] = "desktop"; LoggerType2["log"] = "log"; LoggerType2["info"] = "info"; LoggerType2["warn"] = "warn"; LoggerType2["error"] = "error"; LoggerType2["trace"] = "trace"; return LoggerType2; })(LoggerType || {}); const styleCollection = /* @__PURE__ */ new Map(); styleCollection.set("title", [ "%c#title#", "color: #959ca4; font-style: italic; padding: 0 6px; border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-bottom: 1px solid #ccc" ]); styleCollection.set("desktop" /* desktop */, [ `%cDESKTOP`, "color: white; font-style: italic; background-color: #29619b; padding: 0 6px; border: 1px solid #29619b" ]); styleCollection.set("log" /* log */, [ `%cLOG`, "color: #2a323b; font-style: italic; background-color: #ccc; padding: 0 6px; border: 1px solid #ccc" ]); styleCollection.set("info" /* info */, [ `%cINFO`, "color: #fff; font-style: italic; background-color: #6b7f96; padding: 0 6px; border: 1px solid #6b7f96" ]); styleCollection.set("warn" /* warn */, [ `%cWARNING`, "color: #f0a74f; font-style: italic; padding: 0 6px; border: 1px solid #f0a74f" ]); styleCollection.set("error" /* error */, [ `%cERROR`, "color: white; font-style: italic; background-color: #8a3232; padding: 0 6px; border: 1px solid #8a3232" ]); styleCollection.set("trace" /* trace */, [ `%cTRACE`, "color: #2a323b; font-style: italic; background-color: #ccc; padding: 0 6px; border: 1px solid #ccc" ]); class LoggerBrowser { #title; #types = { desktop: true, log: false, info: false, warn: false, error: true, trace: true }; static build(title, isDevelopment = false) { const logger = new LoggerBrowser(title); if (isDevelopment) { logger.enable("log" /* log */); logger.enable("info" /* info */); logger.enable("warn" /* warn */); } return logger; } constructor(title) { this.#title = title; } // region Styles //// #getStyle(type) { const resultText = []; const resultStyle = []; if (styleCollection.has("title")) { const styleTitle = styleCollection.get("title"); if (styleTitle[0]) { resultText.push(styleTitle[0].replace("#title#", this.#title)); resultStyle.push(styleTitle[1] || ""); } } if (styleCollection.has(type)) { const styleBadge = styleCollection.get(type); if (styleBadge[0]) { resultText.push(styleBadge[0]); resultStyle.push(styleBadge[1] || ""); } } return [resultText.join(""), ...resultStyle]; } // endregion //// // region Config //// setConfig(types) { for (const type in types) { this.#types[type] = types[type]; } } enable(type) { if (typeof this.#types[type] === "undefined") { return false; } this.#types[type] = true; return true; } disable(type) { if (typeof this.#types[type] === "undefined") { return false; } this.#types[type] = false; return true; } isEnabled(type) { return this.#types[type]; } // endregion //// // region Functions //// desktop(...params) { if (this.isEnabled("desktop" /* desktop */)) { console.log(...this.#getStyle("desktop" /* desktop */), ...params); } } log(...params) { if (this.isEnabled("log" /* log */)) { console.log(...this.#getStyle("log" /* log */), ...params); } } info(...params) { if (this.isEnabled("info" /* info */)) { console.info(...this.#getStyle("info" /* info */), ...params); } } warn(...params) { if (this.isEnabled("warn" /* warn */)) { console.warn(...this.#getStyle("warn" /* warn */), ...params); } } error(...params) { if (this.isEnabled("error" /* error */)) { console.error(...this.#getStyle("error" /* error */), ...params); } } trace(...params) { if (this.isEnabled("trace" /* trace */)) { console.trace(...this.#getStyle("trace" /* trace */), ...params); } } // endregion //// } var DataType = /* @__PURE__ */ ((DataType2) => { DataType2["undefined"] = "undefined"; DataType2["any"] = "any"; DataType2["integer"] = "integer"; DataType2["boolean"] = "boolean"; DataType2["double"] = "double"; DataType2["date"] = "date"; DataType2["datetime"] = "datetime"; DataType2["string"] = "string"; DataType2["text"] = "text"; DataType2["file"] = "file"; DataType2["array"] = "array"; DataType2["object"] = "object"; DataType2["user"] = "user"; DataType2["location"] = "location"; DataType2["crmCategory"] = "crm_category"; DataType2["crmStatus"] = "crm_status"; DataType2["crmCurrency"] = "crm_currency"; return DataType2; })(DataType || {}); const OBJECT_CONSTRUCTOR_STRING = Function.prototype.toString.call(Object); class TypeManager { getTag(value) { return Object.prototype.toString.call(value); } /** * Checks that value is string * @param value * @return {boolean} * * @memo get from pull.client.Utils */ isString(value) { return typeof value === "string" || value instanceof String; } /** * Returns true if a value is not an empty string * @param value * @returns {boolean} */ isStringFilled(value) { return this.isString(value) && value !== ""; } /** * Checks that value is function * @param value * @return {boolean} * * @memo get from pull.client.Utils */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type isFunction(value) { return value === null ? false : ( // eslint-disable-next-line unicorn/no-instanceof-builtins typeof value === "function" || value instanceof Function ); } /** * Checks that value is an object * @param value * @return {boolean} */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type isObject(value) { return !!value && (typeof value === "object" || typeof value === "function"); } /** * Checks that value is object like * @param value * @return {boolean} */ isObjectLike(value) { return !!value && typeof value === "object"; } /** * Checks that value is plain object * @param value * @return {boolean} */ isPlainObject(value) { if (!this.isObjectLike(value) || this.getTag(value) !== "[object Object]") { return false; } const proto = Object.getPrototypeOf(value); if (proto === null) { return true; } const ctor = proto.hasOwnProperty("constructor") && proto.constructor; return typeof ctor === "function" && Function.prototype.toString.call(ctor) === OBJECT_CONSTRUCTOR_STRING; } isJsonRpcRequest(value) { return typeof value === "object" && value && "jsonrpc" in value && this.isStringFilled(value.jsonrpc) && "method" in value && this.isStringFilled(value.method); } isJsonRpcResponse(value) { return typeof value === "object" && value && "jsonrpc" in value && this.isStringFilled(value.jsonrpc) && "id" in value && ("result" in value || "error" in value); } /** * Checks that value is boolean * @param value * @return {boolean} */ isBoolean(value) { return value === true || value === false; } /** * Checks that value is number * @param value * @return {boolean} */ isNumber(value) { return typeof value === "number" && !Number.isNaN(value); } /** * Checks that value is integer * @param value * @return {boolean} */ isInteger(value) { return Number.isInteger(value); } /** * Checks that value is float * @param value * @return {boolean} */ isFloat(value) { return this.isNumber(value) && !this.isInteger(value); } /** * Checks that value is nil * @param value * @return {boolean} */ isNil(value) { return value === null || value === void 0; } /** * Checks that value is an array * @param value * @return {boolean} */ isArray(value) { return !this.isNil(value) && Array.isArray(value); } /** * Returns true if a value is an array, and it has at least one element * @param value * @returns {boolean} */ isArrayFilled(value) { return this.isArray(value) && value.length > 0; } /** * Checks that value is array like * @param value * @return {boolean} */ isArrayLike(value) { return !this.isNil(value) && !this.isFunction(value) && value.length > -1 && value.length <= Number.MAX_SAFE_INTEGER; } /** * Checks that value is Date * @param value * @return {boolean} */ isDate(value) { return value instanceof Date; } /** * Checks that is a DOM node * @param value * @return {boolean} */ isDomNode(value) { return this.isObjectLike(value) && !this.isPlainObject(value) && "nodeType" in value; } /** * Checks that value is element node * @param value * @return {boolean} */ isElementNode(value) { return this.isDomNode(value) && value.nodeType === Node.ELEMENT_NODE; } /** * Checks that value is a text node * @param value * @return {boolean} */ isTextNode(value) { return this.isDomNode(value) && value.nodeType === Node.TEXT_NODE; } /** * Checks that value is Map * @param value * @return {boolean} */ isMap(value) { return this.isObjectLike(value) && this.getTag(value) === "[object Map]"; } /** * Checks that value is Set * @param value * @return {boolean} */ isSet(value) { return this.isObjectLike(value) && this.getTag(value) === "[object Set]"; } /** * Checks that value is WeakMap * @param value * @return {boolean} */ isWeakMap(value) { return this.isObjectLike(value) && this.getTag(value) === "[object WeakMap]"; } /** * Checks that value is WeakSet * @param value * @return {boolean} */ isWeakSet(value) { return this.isObjectLike(value) && this.getTag(value) === "[object WeakSet]"; } /** * Checks that value is prototype * @param value * @return {boolean} */ isPrototype(value) { return (typeof (value && value.constructor) === "function" && value.constructor.prototype || Object.prototype) === value; } /** * Checks that value is regexp * @param value * @return {boolean} */ isRegExp(value) { return this.isObjectLike(value) && this.getTag(value) === "[object RegExp]"; } /** * Checks that value is null * @param value * @return {boolean} */ isNull(value) { return value === null; } /** * Checks that value is undefined * @param value * @return {boolean} */ isUndefined(value) { return typeof value === "undefined"; } /** * Checks that value is ArrayBuffer * @param value * @return {boolean} */ isArrayBuffer(value) { return this.isObjectLike(value) && this.getTag(value) === "[object ArrayBuffer]"; } /** * Checks that value is typed array * @param value * @return {boolean} */ isTypedArray(value) { const regExpTypedTag = /^\[object (?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)]$/; return this.isObjectLike(value) && regExpTypedTag.test(this.getTag(value)); } /** * Checks that value is Blob * @param value * @return {boolean} */ isBlob(value) { return this.isObjectLike(value) && this.isNumber(value.size) && this.isString(value.type) && this.isFunction(value.slice); } /** * Checks that value is File * @param value * @return {boolean} */ isFile(value) { return this.isBlob(value) && this.isString(value.name) && (this.isNumber(value.lastModified) || this.isObjectLike(value.lastModifiedDate)); } /** * Checks that value is FormData * @param value * @return {boolean} */ isFormData(value) { if (typeof FormData !== "undefined" && value instanceof FormData) { return true; } return this.isObjectLike(value) && this.getTag(value) === "[object FormData]"; } clone(obj, bCopyObj = true) { let _obj, i, l; if (this.isNil(obj) || typeof obj !== "object") { return obj; } if (this.isDomNode(obj)) { _obj = obj.cloneNode(bCopyObj); } else if (typeof obj == "object") { if (this.isArray(obj)) { _obj = []; for (i = 0, l = obj.length; i < l; i++) { if (typeof obj[i] == "object" && bCopyObj) { _obj[i] = this.clone(obj[i], bCopyObj); } else { _obj[i] = obj[i]; } } } else { _obj = {}; if (obj.constructor) { if (this.isDate(obj)) { _obj = new Date(obj); } else { _obj = new obj.constructor(); } } for (i in obj) { if (!obj.hasOwnProperty(i)) { continue; } if (typeof obj[i] === "object" && bCopyObj) { _obj[i] = this.clone(obj[i], bCopyObj); } else { _obj[i] = obj[i]; } } } } else { _obj = obj; } return _obj; } } const Type = new TypeManager(); function pick$1(data, keys) { const result = {}; for (const key of keys) { result[key] = data[key]; } return result; } function omit(data, keys) { const result = { ...data }; for (const key of keys) { delete result[key]; } return result; } function isArrayOfArray(item) { return Array.isArray(item[0]); } function getEnumValue(enumObj, value) { return Object.values(enumObj).includes(value) ? value : void 0; } // these aren't really private, but nor are they really useful to document /** * @private */ class LuxonError extends Error {} /** * @private */ class InvalidDateTimeError extends LuxonError { constructor(reason) { super(`Invalid DateTime: ${reason.toMessage()}`); } } /** * @private */ class InvalidIntervalError extends LuxonError { constructor(reason) { super(`Invalid Interval: ${reason.toMessage()}`); } } /** * @private */ class InvalidDurationError extends LuxonError { constructor(reason) { super(`Invalid Duration: ${reason.toMessage()}`); } } /** * @private */ class ConflictingSpecificationError extends LuxonError {} /** * @private */ class InvalidUnitError extends LuxonError { constructor(unit) { super(`Invalid unit ${unit}`); } } /** * @private */ class InvalidArgumentError extends LuxonError {} /** * @private */ class ZoneIsAbstractError extends LuxonError { constructor() { super("Zone is an abstract class"); } } /** * @private */ const n = "numeric", s = "short", l = "long"; const DATE_SHORT = { year: n, month: n, day: n, }; const DATE_MED = { year: n, month: s, day: n, }; const DATE_MED_WITH_WEEKDAY = { year: n, month: s, day: n, weekday: s, }; const DATE_FULL = { year: n, month: l, day: n, }; const DATE_HUGE = { year: n, month: l, day: n, weekday: l, }; const TIME_SIMPLE = { hour: n, minute: n, }; const TIME_WITH_SECONDS = { hour: n, minute: n, second: n, }; const TIME_WITH_SHORT_OFFSET = { hour: n, minute: n, second: n, timeZoneName: s, }; const TIME_WITH_LONG_OFFSET = { hour: n, minute: n, second: n, timeZoneName: l, }; const TIME_24_SIMPLE = { hour: n, minute: n, hourCycle: "h23", }; const TIME_24_WITH_SECONDS = { hour: n, minute: n, second: n, hourCycle: "h23", }; const TIME_24_WITH_SHORT_OFFSET = { hour: n, minute: n, second: n, hourCycle: "h23", timeZoneName: s, }; const TIME_24_WITH_LONG_OFFSET = { hour: n, minute: n, second: n, hourCycle: "h23", timeZoneName: l, }; const DATETIME_SHORT = { year: n, month: n, day: n, hour: n, minute: n, }; const DATETIME_SHORT_WITH_SECONDS = { year: n, month: n, day: n, hour: n, minute: n, second: n, }; const DATETIME_MED = { year: n, month: s, day: n, hour: n, minute: n, }; const DATETIME_MED_WITH_SECONDS = { year: n, month: s, day: n, hour: n, minute: n, second: n, }; const DATETIME_MED_WITH_WEEKDAY = { year: n, month: s, day: n, weekday: s, hour: n, minute: n, }; const DATETIME_FULL = { year: n, month: l, day: n, hour: n, minute: n, timeZoneName: s, }; const DATETIME_FULL_WITH_SECONDS = { year: n, month: l, day: n, hour: n, minute: n, second: n, timeZoneName: s, }; const DATETIME_HUGE = { year: n, month: l, day: n, weekday: l, hour: n, minute: n, timeZoneName: l, }; const DATETIME_HUGE_WITH_SECONDS = { year: n, month: l, day: n, weekday: l, hour: n, minute: n, second: n, timeZoneName: l, }; /** * @interface */ class Zone { /** * The type of zone * @abstract * @type {string} */ get type() { throw new ZoneIsAbstractError(); } /** * The name of this zone. * @abstract * @type {string} */ get name() { throw new ZoneIsAbstractError(); } /** * The IANA name of this zone. * Defaults to `name` if not overwritten by a subclass. * @abstract * @type {string} */ get ianaName() { return this.name; } /** * Returns whether the offset is known to be fixed for the whole year. * @abstract * @type {boolean} */ get isUniversal() { throw new ZoneIsAbstractError(); } /** * Returns the offset's common name (such as EST) at the specified timestamp * @abstract * @param {number} ts - Epoch milliseconds for which to get the name * @param {Object} opts - Options to affect the format * @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'. * @param {string} opts.locale - What locale to return the offset name in. * @return {string} */ offsetName(ts, opts) { throw new ZoneIsAbstractError(); } /** * Returns the offset's value as a string * @abstract * @param {number} ts - Epoch milliseconds for which to get the offset * @param {string} format - What style of offset to return. * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively * @return {string} */ formatOffset(ts, format) { throw new ZoneIsAbstractError(); } /** * Return the offset in minutes for this zone at the specified timestamp. * @abstract * @param {number} ts - Epoch milliseconds for which to compute the offset * @return {number} */ offset(ts) { throw new ZoneIsAbstractError(); } /** * Return whether this Zone is equal to another zone * @abstract * @param {Zone} otherZone - the zone to compare * @return {boolean} */ equals(otherZone) { throw new ZoneIsAbstractError(); } /** * Return whether this Zone is valid. * @abstract * @type {boolean} */ get isValid() { throw new ZoneIsAbstractError(); } } let singleton$1 = null; /** * Represents the local zone for this JavaScript environment. * @implements {Zone} */ class SystemZone extends Zone { /** * Get a singleton instance of the local zone * @return {SystemZone} */ static get instance() { if (singleton$1 === null) { singleton$1 = new SystemZone(); } return singleton$1; } /** @override **/ get type() { return "system"; } /** @override **/ get name() { return new Intl.DateTimeFormat().resolvedOptions().timeZone; } /** @override **/ get isUniversal() { return false; } /** @override **/ offsetName(ts, { format, locale }) { return parseZoneInfo(ts, format, locale); } /** @override **/ formatOffset(ts, format) { return formatOffset(this.offset(ts), format); } /** @override **/ offset(ts) { return -new Date(ts).getTimezoneOffset(); } /** @override **/ equals(otherZone) { return otherZone.type === "system"; } /** @override **/ get isValid() { return true; } } const dtfCache = new Map(); function makeDTF(zoneName) { let dtf = dtfCache.get(zoneName); if (dtf === undefined) { dtf = new Intl.DateTimeFormat("en-US", { hour12: false, timeZone: zoneName, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", era: "short", }); dtfCache.set(zoneName, dtf); } return dtf; } const typeToPos = { year: 0, month: 1, day: 2, era: 3, hour: 4, minute: 5, second: 6, }; function hackyOffset(dtf, date) { const formatted = dtf.format(date).replace(/\u200E/g, ""), parsed = /(\d+)\/(\d+)\/(\d+) (AD|BC),? (\d+):(\d+):(\d+)/.exec(formatted), [, fMonth, fDay, fYear, fadOrBc, fHour, fMinute, fSecond] = parsed; return [fYear, fMonth, fDay, fadOrBc, fHour, fMinute, fSecond]; } function partsOffset(dtf, date) { const formatted = dtf.formatToParts(date); const filled = []; for (let i = 0; i < formatted.length; i++) { const { type, value } = formatted[i]; const pos = typeToPos[type]; if (type === "era") { filled[pos] = value; } else if (!isUndefined$1(pos)) { filled[pos] = parseInt(value, 10); } } return filled; } const ianaZoneCache = new Map(); /** * A zone identified by an IANA identifier, like America/New_York * @implements {Zone} */ class IANAZone extends Zone { /** * @param {string} name - Zone name * @return {IANAZone} */ static create(name) { let zone = ianaZoneCache.get(name); if (zone === undefined) { ianaZoneCache.set(name, (zone = new IANAZone(name))); } return zone; } /** * Reset local caches. Should only be necessary in testing scenarios. * @return {void} */ static resetCache() { ianaZoneCache.clear(); dtfCache.clear(); } /** * Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that. * @param {string} s - The string to check validity on * @example IANAZone.isValidSpecifier("America/New_York") //=> true * @example IANAZone.isValidSpecifier("Sport~~blorp") //=> false * @deprecated For backward compatibility, this forwards to isValidZone, better use `isValidZone()` directly instead. * @return {boolean} */ static isValidSpecifier(s) { return this.isValidZone(s); } /** * Returns whether the provided string identifies a real zone * @param {string} zone - The string to check * @example IANAZone.isValidZone("America/New_York") //=> true * @example IANAZone.isValidZone("Fantasia/Castle") //=> false * @example IANAZone.isValidZone("Sport~~blorp") //=> false * @return {boolean} */ static isValidZone(zone) { if (!zone) { return false; } try { new Intl.DateTimeFormat("en-US", { timeZone: zone }).format(); return true; } catch (e) { return false; } } constructor(name) { super(); /** @private **/ this.zoneName = name; /** @private **/ this.valid = IANAZone.isValidZone(name); } /** * The type of zone. `iana` for all instances of `IANAZone`. * @override * @type {string} */ get type() { return "iana"; } /** * The name of this zone (i.e. the IANA zone name). * @override * @type {string} */ get name() { return this.zoneName; } /** * Returns whether the offset is known to be fixed for the whole year: * Always returns false for all IANA zones. * @override * @type {boolean} */ get isUniversal() { return false; } /** * Returns the offset's common name (such as EST) at the specified timestamp * @override * @param {number} ts - Epoch milliseconds for which to get the name * @param {Object} opts - Options to affect the format * @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'. * @param {string} opts.locale - What locale to return the offset name in. * @return {string} */ offsetName(ts, { format, locale }) { return parseZoneInfo(ts, format, locale, this.name); } /** * Returns the offset's value as a string * @override * @param {number} ts - Epoch milliseconds for which to get the offset * @param {string} format - What style of offset to return. * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively * @return {string} */ formatOffset(ts, format) { return formatOffset(this.offset(ts), format); } /** * Return the offset in minutes for this zone at the specified timestamp. * @override * @param {number} ts - Epoch milliseconds for which to compute the offset * @return {number} */ offset(ts) { if (!this.valid) return NaN; const date = new Date(ts); if (isNaN(date)) return NaN; const dtf = makeDTF(this.name); let [year, month, day, adOrBc, hour, minute, second] = dtf.formatToParts ? partsOffset(dtf, date) : hackyOffset(dtf, date); if (adOrBc === "BC") { year = -Math.abs(year) + 1; } // because we're using hour12 and https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat const adjustedHour = hour === 24 ? 0 : hour; const asUTC = objToLocalTS({ year, month, day, hour: adjustedHour, minute, second, millisecond: 0, }); let asTS = +date; const over = asTS % 1000; asTS -= over >= 0 ? over : 1000 + over; return (asUTC - asTS) / (60 * 1000); } /** * Return whether this Zone is equal to another zone * @override * @param {Zone} otherZone - the zone to compare * @return {boolean} */ equals(otherZone) { return otherZone.type === "iana" && otherZone.name === this.name; } /** * Return whether this Zone is valid. * @override * @type {boolean} */ get isValid() { return this.valid; } } // todo - remap caching let intlLFCache = {}; function getCachedLF(locString, opts = {}) { const key = JSON.stringify([locString, opts]); let dtf = intlLFCache[key]; if (!dtf) { dtf = new Intl.ListFormat(locString, opts); intlLFCache[key] = dtf; } return dtf; } const intlDTCache = new Map(); function getCachedDTF(locString, opts = {}) { const key = JSON.stringify([locString, opts]); let dtf = intlDTCache.get(key); if (dtf === undefined) { dtf = new Intl.DateTimeFormat(locString, opts); intlDTCache.set(key, dtf); } return dtf; } const intlNumCache = new Map(); function getCachedINF(locString, opts = {}) { const key = JSON.stringify([locString, opts]); let inf = intlNumCache.get(key); if (inf === undefined) { inf = new Intl.NumberFormat(locString, opts); intlNumCache.set(key, inf); } return inf; } const intlRelCache = new Map(); function getCachedRTF(locString, opts = {}) { const { base, ...cacheKeyOpts } = opts; // exclude `base` from the options const key = JSON.stringify([locString, cacheKeyOpts]); let inf = intlRelCache.get(key); if (inf === undefined) { inf = new Intl.RelativeTimeFormat(locString, opts); intlRelCache.set(key, inf); } return inf; } let sysLocaleCache = null; function systemLocale() { if (sysLocaleCache) { return sysLocaleCache; } else { sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale; return sysLocaleCache; } } const intlResolvedOptionsCache = new Map(); function getCachedIntResolvedOptions(locString) { let opts = intlResolvedOptionsCache.get(locString); if (opts === undefined) { opts = new Intl.DateTimeFormat(locString).resolvedOptions(); intlResolvedOptionsCache.set(locString, opts); } return opts; } const weekInfoCache = new Map(); function getCachedWeekInfo(locString) { let data = weekInfoCache.get(locString); if (!data) { const locale = new Intl.Locale(locString); // browsers currently implement this as a property, but spec says it should be a getter function data = "getWeekInfo" in locale ? locale.getWeekInfo() : locale.weekInfo; // minimalDays was removed from WeekInfo: https://github.com/tc39/proposal-intl-locale-info/issues/86 if (!("minimalDays" in data)) { data = { ...fallbackWeekSettings, ...data }; } weekInfoCache.set(locString, data); } return data; } function parseLocaleString(localeStr) { // I really want to avoid writing a BCP 47 parser // see, e.g. https://github.com/wooorm/bcp-47 // Instead, we'll do this: // a) if the string has no -u extensions, just leave it alone // b) if it does, use Intl to resolve everything // c) if Intl fails, try again without the -u // private subtags and unicode subtags have ordering requirements, // and we're not properly parsing this, so just strip out the // private ones if they exist. const xIndex = localeStr.indexOf("-x-"); if (xIndex !== -1) { localeStr = localeStr.substring(0, xIndex); } const uIndex = localeStr.indexOf("-u-"); if (uIndex === -1) { return [localeStr]; } else { let options; let selectedStr; try { options = getCachedDTF(localeStr).resolvedOptions(); selectedStr = localeStr; } catch (e) { const smaller = localeStr.substring(0, uIndex); options = getCachedDTF(smaller).resolvedOptions(); selectedStr = smaller; } const { numberingSystem, calendar } = options; return [selectedStr, numberingSystem, calendar]; } } function intlConfigString(localeStr, numberingSystem, outputCalendar) { if (outputCalendar || numberingSystem) { if (!localeStr.includes("-u-")) { localeStr += "-u"; } if (outputCalendar) { localeStr += `-ca-${outputCalendar}`; } if (numberingSystem) { localeStr += `-nu-${numberingSystem}`; } return localeStr; } else { return localeStr; } } function mapMonths(f) { const ms = []; for (let i = 1; i <= 12; i++) { const dt = DateTime.utc(2009, i, 1); ms.push(f(dt)); } return ms; } function mapWeekdays(f) { const ms = []; for (let i = 1; i <= 7; i++) { const dt = DateTime.utc(2016, 11, 13 + i); ms.push(f(dt)); } return ms; } function listStuff(loc, length, englishFn, intlFn) { const mode = loc.listingMode(); if (mode === "error") { return null; } else if (mode === "en") { return englishFn(length); } else { return intlFn(length); } } function supportsFastNumbers(loc) { if (loc.numberingSystem && loc.numberingSystem !== "latn") { return false; } else { return ( loc.numberingSystem === "latn" || !loc.locale || loc.locale.startsWith("en") || getCachedIntResolvedOptions(loc.locale).numberingSystem === "latn" ); } } /** * @private */ class PolyNumberFormatter { constructor(intl, forceSimple, opts) { this.padTo = opts.padTo || 0; this.floor = opts.floor || false; const { padTo, floor, ...otherOpts } = opts; if (!forceSimple || Object.keys(otherOpts).length > 0) { const intlOpts = { useGrouping: false, ...opts }; if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo; this.inf = getCachedINF(intl, intlOpts); } } format(i) { if (this.inf) { const fixed = this.floor ? Math.floor(i) : i; return this.inf.format(fixed); } else { // to match the browser's numberformatter defaults const fixed = this.floor ? Math.floor(i) : roundTo(i, 3); return padStart(fixed, this.padTo); } } } /** * @private */ class PolyDateFormatter { constructor(dt, intl, opts) { this.opts = opts; this.originalZone = undefined; let z = undefined; if (this.opts.timeZone) { // Don't apply any workarounds if a timeZone is explicitly provided in opts this.dt = dt; } else if (dt.zone.type === "fixed") { // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like. // That is why fixed-offset TZ is set to that unless it is: // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT. // 2. Unsupported by the browser: // - some do not support Etc/ // - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata const gmtOffset = -1 * (dt.offset / 60); const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`; if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) { z = offsetZ; this.dt = dt; } else { // Not all fixed-offset zones like Etc/+4:30 are present in tzdata so // we manually apply the offset and substitute the zone as needed. z = "UTC"; this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset }); this.originalZone = dt.zone; } } else if (dt.zone.type === "system") { this.dt = dt; } else if (dt.zone.type === "iana") { this.dt = dt; z = dt.zone.name; } else { // Custom zones can have any offset / offsetName so we just manually // apply the offset and substitute the zone as needed. z = "UTC"; this.dt = dt.setZone("UTC").plus({ minutes: dt.offset }); this.originalZone = dt.zone; } const intlOpts = { ...this.opts }; intlOpts.timeZone = intlOpts.timeZone || z; this.dtf = getCachedDTF(intl, intlOpts); } format() { if (this.originalZone) { // If we have to substitute in the actual zone name, we have to use // formatToParts so that the timezone can be replaced. return this.formatToParts() .map(({ value }) => value) .join(""); } return this.dtf.format(this.dt.toJSDate()); } formatToParts() { const parts = this.dtf.formatToParts(this.dt.toJSDate()); if (this.originalZone) { return parts.map((part) => { if (part.type === "timeZoneName") { const offsetName = this.originalZone.offsetName(this.dt.ts, { locale: this.dt.locale, format: this.opts.timeZoneName, }); return { ...part, value: offsetName, }; } else { return part; } }); } return parts; } resolvedOptions() { return this.dtf.resolvedOptions(); } } /** * @private */ class PolyRelFormatter { constructor(intl, isEnglish, opts) { this.opts = { style: "long", ...opts }; if (!isEnglish && hasRelative()) { this.rtf = getCachedRTF(intl, opts); } } format(count, unit) { if (this.rtf) { return this.rtf.format(count, unit); } else { return formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== "long"); } } formatToParts(count, unit) { if (this.rtf) { return this.rtf.formatToParts(count, unit); } else { return []; } } } const fallbackWeekSettings = { firstDay: 1, minimalDays: 4, weekend: [6, 7], }; /** * @private */ class Locale { static fromOpts(opts) { return Locale.create( opts.locale, opts.numberingSystem, opts.outputCalendar, opts.weekSettings, opts.defaultToEN ); } static create(locale, numberingSystem, outputCalendar, weekSettings, defaultToEN = false) { const specifiedLocale = locale || Settings.defaultLocale; // the system locale is useful for human-readable strings but annoying for parsing/formatting known formats const localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale()); const numberingSystemR = numberingSystem || Settings.defaultNumberingSystem; const outputCalendarR = outputCalendar || Settings.defaultOutputCalendar; const weekSettingsR = validateWeekSettings(weekSettings) || Settings.defaultWeekSettings; return new Locale(localeR, numberingSystemR, outputCalendarR, weekSettingsR, specifiedLocale); } static resetCache() { sysLocaleCache = null; intlDTCache.clear(); intlNumCache.clear(); intlRelCache.clear(); intlResolvedOptionsCache.clear(); weekInfoCache.clear(); } static fromObject({ locale, numberingSystem, outputCalendar, weekSettings } = {}) { return Locale.create(locale, numberingSystem, outputCalendar, weekSettings); } constructor(locale, numbering, outputCalendar, weekSettings, specifiedLocale) { const [parsedLocale, parsedNumberingSystem, parsedOutputCalendar] = parseLocaleString(locale); this.locale = parsedLocale; this.numberingSystem = numbering || parsedNumberingSystem || null; this.outputCalendar = outputCalendar || parsedOutputCalendar || null; this.weekSettings = weekSettings; this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar); this.weekdaysCache = { format: {}, standalone: {} }; this.monthsCache = { format: {}, standalone: {} }; this.meridiemCache = null; this.eraCache = {}; this.specifiedLocale = specifiedLocale; this.fastNumbersCached = null; } get fastNumbers() { if (this.fastNumbersCached == null) { this.fastNumbersCached = supportsFastNumbers(this); } return this.fastNumbersCached; } listingMode() { const isActuallyEn = this.isEnglish(); const hasNoWeirdness = (this.numberingSystem === null || this.numberingSystem === "latn") && (this.outputCalendar === null || this.outputCalendar === "gregory"); return isActuallyEn && hasNoWeirdness ? "en" : "intl"; } clone(alts) { if (!alts || Object.getOwnPropertyNames(alts).length === 0) { return this; } else { return Locale.create( alts.locale || this.specifiedLocale, alts.numberingSystem || this.numberingSystem, alts.outputCalendar || this.outputCalendar, validateWeekSettings(alts.weekSettings) || this.weekSettings, alts.defaultToEN || false ); } } redefaultToEN(alts = {}) { return this.clone({ ...alts, defaultToEN: true }); } redefaultToSystem(alts = {}) { return this.clone({ ...alts, defaultToEN: false }); } months(length, format = false) { return listStuff(this, length, months, () => { // Workaround for "ja" locale: formatToParts does not label all parts of the month // as "month" and for this locale there is no difference between "format" and "non-format". // As such, just use format() instead of formatToParts() and take the whole string const monthSpecialCase = this.intl === "ja" || this.intl.startsWith("ja-"); format &= !monthSpecialCase; const intl = format ? { month: length, day: "numeric" } : { month: length }, formatStr = format ? "format" : "standalone"; if (!this.monthsCache[formatStr][length]) { const mapper = !monthSpecialCase ? (dt) => this.extract(dt, intl, "month") : (dt) => this.dtFormatter(dt, intl).format(); this.monthsCache[formatStr][length] = mapMonths(mapper); } return this.monthsCache[formatStr][length]; }); } weekdays(length, format = false) { return listStuff(this, length, weekdays, () => { const intl = format ? { weekday: length, year: "numeric", month: "long", day: "numeric" } : { weekday: length }, formatStr = format ? "format" : "standalone"; if (!this.weekdaysCache[formatStr][length]) { this.weekdaysCache[formatStr][length] = mapWeekdays((dt) => this.extract(dt, intl, "weekday") ); } return this.weekdaysCache[formatStr][length]; }); } meridiems() { return listStuff( this, undefined, () => meridiems, () => { // In theory there could be aribitrary day periods. We're gonna assume there are exactly two // for AM and PM. This is probably wrong, but it's makes parsing way easier. if (!this.meridiemCache) { const intl = { hour: "numeric", hourCycle: "h12" }; this.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map( (dt) => this.extract(dt, intl, "dayperiod") ); } return this.meridiemCache; } ); } eras(length) { return listStuff(this, length, eras, () => { const intl = { era: length }; // This is problematic. Different calendars are going to define eras totally differently. What I need is the minimum set of dates // to definitely enumerate them. if (!this.eraCache[length]) { this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map((dt) => this.extract(dt, intl, "era") ); } return this.eraCache[length]; }); } extract(dt, intlOpts, field) { const df = this.dtFormatter(dt, intlOpts), results = df.formatToParts(), matching = results.find((m) => m.type.toLowerCase() === field); return matching ? matching.value : null; } numberFormatter(opts = {}) { // this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave) // (in contrast, the rest of the condition is used heavily) return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts); } dtFormatter(dt, intlOpts = {}) { return new PolyDateFormatter(dt, this.intl, intlOpts); } relFormatter(opts = {}) { return new PolyRelFormatter(this.intl, this.isEnglish(), opts); } listFormatter(opts = {}) { return getCachedLF(this.intl, opts); } isEnglish() { return ( this.locale === "en" || this.locale.toLowerCase() === "en-us" || getCachedIntResolvedOptions(this.intl).locale.startsWith("en-us") ); } getWeekSettings() { if (this.weekSettings) { return this.weekSettings; } else if (!hasLocaleWeekInfo()) { return fallbackWeekSettings; } else { return getCachedWeekInfo(this.locale); } } getStartOfWeek() { return this.getWeekSettings().firstDay; } getMinDaysInFirstWeek() { return this.getWeekSettings().minimalDays; } getWeekendDays() { return this.getWeekSettings().weekend; } equals(other) { return ( this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar ); } toString() { return `Locale(${this.locale}, ${this.numberingSystem}, ${this.outputCalendar})`; } } let singleton = null; /** * A zone with a fixed offset (meaning no DST) * @implements {Zone} */ class FixedOffsetZone extends Zone { /** * Get a singleton instance of UTC * @return {FixedOffsetZone} */ static get utcInstance() { if (singleton === null) { singleton = new FixedOffsetZone(0); } return singleton; } /** * Get an instance with a specified offset * @param {number} offset - The offset in minutes * @return {FixedOffsetZone} */ static instance(offset) { return offset === 0 ? FixedOffsetZone.utcInstance : new FixedOffsetZone(offset); } /** * Get an instance of FixedOffsetZone from a UTC offset string, like "UTC+6" * @param {string} s - The offset string to parse * @example FixedOffsetZone.parseSpecifier("UTC+6") * @example FixedOffsetZone.parseSpecifier("UTC+06") * @example FixedOffsetZone.parseSpecifier("UTC-6:00") * @return {FixedOffsetZone} */ static parseSpecifier(s) { if (s) { const r = s.match(/^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$/i); if (r) { return new FixedOffsetZone(signedOffset(r[1], r[2])); } } return null; } constructor(offset) { super(); /** @private **/ this.fixed = offset; } /** * The type of zone. `fixed` for all instances of `FixedOffsetZone`. * @override * @type {string} */ get type() { return "fixed"; } /** * The name of this zone. * All fixed zones' names always start with "UTC" (plus optional offset) * @override * @type {string} */ get name() { return this.fixed === 0 ? "UTC" : `UTC${formatOffset(this.fixed, "narrow")}`; } /** * The IANA name of this zone, i.e. `Etc/UTC` or `Etc/GMT+/-nn` * * @override * @type {string} */ get ianaName() { if (this.fixed === 0) { return "Etc/UTC"; } else { return `Etc/GMT${formatOffset(-this.fixed, "narrow")}`; } } /** * Returns the offset's common name at the specified timestamp. * * For fixed offset zones this equals to the zone name. * @override */ offsetName() { return this.name; } /** * Returns the offset's value as a string * @override * @param {number} ts - Epoch milliseconds for which to get the offset * @param {string} format - What style of offset to return. * Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively * @return {string} */ formatOffset(ts, format) { return formatOffset(this.fixed, format); } /** * Returns whether the offset is known to be fixed for the whole year: * Always returns true for all fixed offset zones. * @override * @type {boolean} */ get isUniversal() { return true; } /** * Return the offset in minutes for this zone at the specified timestamp. * * For fixed offset zones, this is constant and does not depend on a timestamp. * @override * @return {number} */ offset() { return this.fixed; } /** * Return whether this Zone is equal to another zone (i.e. also fixed and same offset) * @override * @param {Zone} otherZone - the zone to compare * @return {boolean} */ equals(otherZone) { return otherZone.type === "fixed" && otherZone.fixed === this.fixed; } /** * Return whether this Zone is valid: * All fixed offset zones are valid. * @override * @type {boolean} */ get isValid() { return true; } } /** * A zone that failed to parse. You should never need to instantiate this. * @implements {Zone} */ class InvalidZone extends Zone { constructor(zoneName) { super(); /** @private */ this.zoneName = zoneName; } /** @override **/ get type() { return "invalid"; } /** @override **/ get name() { return this.zoneName; } /** @override **/ get isUniversal() { return false; } /** @override **/ offsetName() { return null; } /** @override **/ formatOffset() { return ""; } /** @override **/ offset() { return NaN; } /** @override **/ equals() { return false; } /** @override **/ get isValid() { return false; } } /** * @private */ function normalizeZone(input, defaultZone) { if (isUndefined$1(input) || input === null) { return defaultZone; } else if (input instanceof Zone) { return input; } else if (isString$1(input)) { const lowered = input.toLowerCase(); if (lowered === "default") return defaultZone; else if (lowered === "local" || lowered === "system") return SystemZone.instance; else if (lowered === "utc" || lowered === "gmt") return FixedOffsetZone.utcInstance; else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input); } else if (isNumber$1(input)) { return FixedOffsetZone.instance(input); } else if (typeof input === "object" && "offset" in input && typeof input.offset === "function") { // This is dumb, but the instanceof check above doesn't seem to really work // so we're duck checking it return input; } else { return new InvalidZone(input); } } const numberingSystems = { arab: "[\u0660-\u0669]", arabext: "[\u06F0-\u06F9]", bali: "[\u1B50-\