@bitrix24/b24jssdk
Version:
Bitrix24 REST API JS SDK
1,940 lines (1,764 loc) • 863 kB
JavaScript
/**
* @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-\