@formatjs/intl-datetimeformat
Version:
Intl.DateTimeFormat polyfill
267 lines (266 loc) • 9.77 kB
JavaScript
import { CanonicalizeLocaleList, CanonicalizeTimeZoneName, IsValidTimeZoneName, OrdinaryHasInstance, SupportedLocales, ToNumber, defineProperty, invariant } from "@formatjs/ecma402-abstract";
import Decimal from "decimal.js";
import { FormatDateTime } from "./abstract/FormatDateTime.js";
import { FormatDateTimeRange } from "./abstract/FormatDateTimeRange.js";
import { FormatDateTimeRangeToParts } from "./abstract/FormatDateTimeRangeToParts.js";
import { FormatDateTimeToParts } from "./abstract/FormatDateTimeToParts.js";
import { InitializeDateTimeFormat } from "./abstract/InitializeDateTimeFormat.js";
import { parseDateTimeSkeleton } from "./abstract/skeleton.js";
import { DATE_TIME_PROPS } from "./abstract/utils.js";
import links from "./data/links.generated.js";
import getInternalSlots from "./get_internal_slots.js";
import { unpack } from "./packer.js";
import "./types.js";
const UPPERCASED_LINKS = Object.keys(links).reduce((all, l) => {
all[l.toUpperCase()] = links[l];
return all;
}, {});
const RESOLVED_OPTIONS_KEYS = [
"locale",
"calendar",
"numberingSystem",
"dateStyle",
"timeStyle",
"timeZone",
"hourCycle",
"weekday",
"era",
"year",
"month",
"day",
"hour",
"minute",
"second",
"timeZoneName"
];
const formatDescriptor = {
enumerable: false,
configurable: true,
get() {
if (typeof this !== "object" || !OrdinaryHasInstance(DateTimeFormat, this)) {
throw TypeError("Intl.DateTimeFormat format property accessor called on incompatible receiver");
}
const internalSlots = getInternalSlots(this);
// eslint-disable-next-line @typescript-eslint/no-this-alias
const dtf = this;
let boundFormat = internalSlots.boundFormat;
if (boundFormat === undefined) {
// https://tc39.es/proposal-unified-intl-numberformat/section11/numberformat_diff_out.html#sec-number-format-functions
boundFormat = (date) => {
let x;
if (date === undefined) {
x = new Decimal(Date.now());
} else {
x = ToNumber(date);
}
return FormatDateTime(dtf, x, {
getInternalSlots,
localeData: DateTimeFormat.localeData,
tzData: DateTimeFormat.tzData,
getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
});
};
try {
// https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/format-function-name.js
Object.defineProperty(boundFormat, "name", {
configurable: true,
enumerable: false,
writable: false,
value: ""
});
} catch {}
internalSlots.boundFormat = boundFormat;
}
return boundFormat;
}
};
try {
// https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/name.js
Object.defineProperty(formatDescriptor.get, "name", {
configurable: true,
enumerable: false,
writable: false,
value: "get format"
});
} catch {}
export const DateTimeFormat = function(locales, options) {
// Cannot use `new.target` bc of IE11 & TS transpiles it to something else
if (!this || !OrdinaryHasInstance(DateTimeFormat, this)) {
return new DateTimeFormat(locales, options);
}
InitializeDateTimeFormat(this, locales, options, {
tzData: DateTimeFormat.tzData,
uppercaseLinks: UPPERCASED_LINKS,
availableLocales: DateTimeFormat.availableLocales,
relevantExtensionKeys: DateTimeFormat.relevantExtensionKeys,
getDefaultLocale: DateTimeFormat.getDefaultLocale,
getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone,
getInternalSlots,
localeData: DateTimeFormat.localeData
});
/** IMPL START */
const internalSlots = getInternalSlots(this);
const dataLocale = internalSlots.dataLocale;
const dataLocaleData = DateTimeFormat.localeData[dataLocale];
invariant(dataLocaleData !== undefined, `Cannot load locale-dependent data for ${dataLocale}.`);
/** IMPL END */
};
// Static properties
defineProperty(DateTimeFormat, "supportedLocalesOf", { value: function supportedLocalesOf(locales, options) {
return SupportedLocales(DateTimeFormat.availableLocales, CanonicalizeLocaleList(locales), options);
} });
defineProperty(DateTimeFormat.prototype, "resolvedOptions", { value: function resolvedOptions() {
if (typeof this !== "object" || !OrdinaryHasInstance(DateTimeFormat, this)) {
throw TypeError("Method Intl.DateTimeFormat.prototype.resolvedOptions called on incompatible receiver");
}
const internalSlots = getInternalSlots(this);
const ro = {};
for (const key of RESOLVED_OPTIONS_KEYS) {
let value = internalSlots[key];
if (key === "hourCycle") {
const hour12 = value === "h11" || value === "h12" ? true : value === "h23" || value === "h24" ? false : undefined;
if (hour12 !== undefined) {
ro.hour12 = hour12;
}
}
if (DATE_TIME_PROPS.indexOf(key) > -1) {
if (internalSlots.dateStyle !== undefined || internalSlots.timeStyle !== undefined) {
value = undefined;
}
}
if (value !== undefined) {
ro[key] = value;
}
}
return ro;
} });
defineProperty(DateTimeFormat.prototype, "formatToParts", { value: function formatToParts(date) {
let x;
if (date === undefined) {
x = new Decimal(Date.now());
} else {
x = ToNumber(date);
}
return FormatDateTimeToParts(this, x, {
getInternalSlots,
localeData: DateTimeFormat.localeData,
tzData: DateTimeFormat.tzData,
getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
});
} });
defineProperty(DateTimeFormat.prototype, "formatRangeToParts", { value: function formatRangeToParts(startDate, endDate) {
// oxlint-disable-next-line no-this-alias
const dtf = this;
invariant(typeof dtf === "object", "receiver is not an object", TypeError);
invariant(startDate !== undefined && endDate !== undefined, "startDate/endDate cannot be undefined", TypeError);
return FormatDateTimeRangeToParts(dtf, ToNumber(startDate), ToNumber(endDate), {
getInternalSlots,
localeData: DateTimeFormat.localeData,
tzData: DateTimeFormat.tzData,
getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
});
} });
defineProperty(DateTimeFormat.prototype, "formatRange", { value: function formatRange(startDate, endDate) {
// oxlint-disable-next-line no-this-alias
const dtf = this;
invariant(typeof dtf === "object", "receiver is not an object", TypeError);
invariant(startDate !== undefined && endDate !== undefined, "startDate/endDate cannot be undefined", TypeError);
return FormatDateTimeRange(dtf, ToNumber(startDate), ToNumber(endDate), {
getInternalSlots,
localeData: DateTimeFormat.localeData,
tzData: DateTimeFormat.tzData,
getDefaultTimeZone: DateTimeFormat.getDefaultTimeZone
});
} });
const DEFAULT_TIMEZONE = "UTC";
DateTimeFormat.__setDefaultTimeZone = (timeZone) => {
if (timeZone !== undefined) {
timeZone = String(timeZone);
if (!IsValidTimeZoneName(timeZone, {
zoneNamesFromData: Object.keys(DateTimeFormat.tzData),
uppercaseLinks: UPPERCASED_LINKS
})) {
throw new RangeError("Invalid timeZoneName");
}
timeZone = CanonicalizeTimeZoneName(timeZone, {
zoneNames: Object.keys(DateTimeFormat.tzData),
uppercaseLinks: UPPERCASED_LINKS
});
} else {
timeZone = DEFAULT_TIMEZONE;
}
DateTimeFormat.__defaultTimeZone = timeZone;
};
DateTimeFormat.relevantExtensionKeys = [
"nu",
"ca",
"hc"
];
DateTimeFormat.__defaultTimeZone = DEFAULT_TIMEZONE;
DateTimeFormat.getDefaultTimeZone = () => DateTimeFormat.__defaultTimeZone;
DateTimeFormat.__addLocaleData = function __addLocaleData(...data) {
for (const { data: d, locale } of data) {
const { dateFormat, timeFormat, dateTimeFormat, formats, intervalFormats, ...rawData } = d;
const processedData = {
...rawData,
dateFormat: {
full: parseDateTimeSkeleton(dateFormat.full),
long: parseDateTimeSkeleton(dateFormat.long),
medium: parseDateTimeSkeleton(dateFormat.medium),
short: parseDateTimeSkeleton(dateFormat.short)
},
timeFormat: {
full: parseDateTimeSkeleton(timeFormat.full),
long: parseDateTimeSkeleton(timeFormat.long),
medium: parseDateTimeSkeleton(timeFormat.medium),
short: parseDateTimeSkeleton(timeFormat.short)
},
dateTimeFormat: {
full: parseDateTimeSkeleton(dateTimeFormat.full).pattern,
long: parseDateTimeSkeleton(dateTimeFormat.long).pattern,
medium: parseDateTimeSkeleton(dateTimeFormat.medium).pattern,
short: parseDateTimeSkeleton(dateTimeFormat.short).pattern
},
intervalFormatFallback: intervalFormats.intervalFormatFallback,
formats: {}
};
for (const calendar in formats) {
processedData.formats[calendar] = Object.keys(formats[calendar]).map((skeleton) => parseDateTimeSkeleton(skeleton, formats[calendar][skeleton], intervalFormats[skeleton], intervalFormats.intervalFormatFallback));
}
const minimizedLocale = new Intl.Locale(locale).minimize().toString();
DateTimeFormat.localeData[locale] = DateTimeFormat.localeData[minimizedLocale] = processedData;
DateTimeFormat.availableLocales.add(locale);
DateTimeFormat.availableLocales.add(minimizedLocale);
if (!DateTimeFormat.__defaultLocale) {
DateTimeFormat.__defaultLocale = minimizedLocale;
}
}
};
Object.defineProperty(DateTimeFormat.prototype, "format", formatDescriptor);
DateTimeFormat.__defaultLocale = "";
DateTimeFormat.localeData = {};
DateTimeFormat.availableLocales = new Set();
DateTimeFormat.getDefaultLocale = () => {
return DateTimeFormat.__defaultLocale;
};
DateTimeFormat.polyfilled = true;
DateTimeFormat.tzData = {};
DateTimeFormat.__addTZData = function(d) {
DateTimeFormat.tzData = unpack(d);
};
try {
if (typeof Symbol !== "undefined") {
Object.defineProperty(DateTimeFormat.prototype, Symbol.toStringTag, {
value: "Intl.DateTimeFormat",
writable: false,
enumerable: false,
configurable: true
});
}
Object.defineProperty(DateTimeFormat.prototype.constructor, "length", {
value: 1,
writable: false,
enumerable: false,
configurable: true
});
} catch {}