@jauntywunderkind/tc39-temporal
Version:
TC39 Proposal: Temporal
321 lines (312 loc) • 11.5 kB
JavaScript
import { ES } from './ecmascript.mjs';
import { assign as ObjectAssign } from './compat.mjs';
import { IDENTIFIER, GetSlot } from './slots.mjs';
import { TimeZone } from './timezone.mjs';
const Absolute = ES.GetIntrinsic('%Temporal.Absolute%');
const DateTime = ES.GetIntrinsic('%Temporal.DateTime%');
const Date = ES.GetIntrinsic('%Temporal.Date%');
const Time = ES.GetIntrinsic('%Temporal.Time%');
const YearMonth = ES.GetIntrinsic('%Temporal.YearMonth%');
const MonthDay = ES.GetIntrinsic('%Temporal.MonthDay%');
const Duration = ES.GetIntrinsic('%Temporal.Duration%');
const DATE = Symbol('date');
const YM = Symbol('ym');
const MD = Symbol('md');
const TIME = Symbol('time');
const DATETIME = Symbol('datetime');
const DURATION = Symbol('duration');
const ORIGINAL = Symbol('original');
const TIMEZONE = Symbol('timezone');
const IntlDateTimeFormat = Intl.DateTimeFormat;
class DurationFormat {
constructor(locale, options = {}) {
this.locale = locale;
this.options = options;
}
format(duration) {
console.error('Intl.DateTimeFormat.format with a Duration is not fully polyfilled!');
return this.formatToParts(duration)
.map((item) => item.value)
.join('');
}
formatToParts(duration) {
console.error('Intl.DateTimeFormat.formatToParts with a Duration is not fully polyfilled!');
const locale = this.locale
.split('-')
.shift()
.toLowerCase();
const strings = DurationFormat[locale] || DurationFormat.en;
const parts = [];
if (duration.years && this.options.year) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'years', value: `${duration.years}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.years === 1 ? strings.year : strings.years });
}
if (duration.months && this.options.month) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'months', value: `${duration.months}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.months === 1 ? strings.month : strings.months });
}
if (duration.days && this.options.day) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'days', value: `${duration.days}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.days === 1 ? strings.day : strings.days });
}
if (duration.hours && this.options.hour) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'hours', value: `${duration.hours}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.hours === 1 ? strings.hour : strings.hours });
}
if (duration.minutes && this.options.minute) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'minutes', value: `${duration.minutes}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.minutes === 1 ? strings.minute : strings.minutes });
}
if (duration.seconds && this.options.second) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'seconds', value: `${duration.seconds}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.seconds === 1 ? strings.second : strings.seconds });
}
if (duration.milliseconds && this.options.millisecond) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'milliseconds', value: `${duration.milliseconds}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.milliseconds === 1 ? strings.millisecond : strings.milliseconds });
}
if (duration.microseconds && this.options.microsecond) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'microseconds', value: `${duration.microseconds}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.microseconds === 1 ? strings.microsecond : strings.microseconds });
}
if (duration.nanoseconds && this.options.nanosecond) {
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'nanoseconds', value: `${duration.nanoseconds}` });
parts.push({ type: 'literal', value: ' ' });
parts.push({ type: 'literal', value: duration.nanoseconds === 1 ? strings.nanosecond : strings.nanoseconds });
}
return parts.slice(1);
}
static get en() {
return {
year: 'year',
years: 'years',
month: 'month',
months: 'months',
day: 'day',
days: 'days',
hour: 'hour',
hours: 'hours',
minute: 'minute',
minutes: 'minutes',
second: 'second',
seconds: 'seconds',
millisecond: 'millisecond',
milliseconds: 'milliseconds',
microsecond: 'microsecond',
microseconds: 'microseconds',
nanosecond: 'nanosecond',
nanoseconds: 'nanoseconds'
};
}
static get de() {
return {
year: 'Jahr',
years: 'Jahre',
month: 'Monat',
months: 'Monate',
day: 'Tag',
days: 'Tage',
hour: 'Stunde',
hours: 'Stunden',
minute: 'Minute',
minutes: 'Minuten',
second: 'Sekunde',
seconds: 'Sekunden',
millisecond: 'Millisekunde',
milliseconds: 'Millisekunden',
microsecond: 'Mikrosekunde',
microseconds: 'Mikrosekunden',
nanosecond: 'Nanosekunde',
nanoseconds: 'Nanosekunden'
};
}
}
export function DateTimeFormat(locale = IntlDateTimeFormat().resolvedOptions().locale, options = {}) {
if (!(this instanceof DateTimeFormat)) return new DateTimeFormat(locale, options);
this[ORIGINAL] = new IntlDateTimeFormat(locale, datetimeAmend(options));
this[TIMEZONE] = new TimeZone(this.resolvedOptions().timeZone);
this[DATE] = new IntlDateTimeFormat(locale, dateAmend(options, {}));
this[YM] = new IntlDateTimeFormat(locale, dateAmend(options, { day: false }));
this[MD] = new IntlDateTimeFormat(locale, dateAmend(options, { year: false }));
this[TIME] = new IntlDateTimeFormat(locale, timeAmend(options));
this[DATETIME] = new IntlDateTimeFormat(locale, datetimeAmend(options));
this[DURATION] = new DurationFormat(locale, datetimeAmend(options));
}
DateTimeFormat.supportedLocalesOf = function(...args) {
return IntlDateTimeFormat.supportedLocalesOf(...args);
};
const properties = {
resolvedOptions: {
value: resolvedOptions,
enumerable: true,
writable: false,
configurable: true
},
format: {
value: format,
enumerable: true,
writable: false,
configurable: true
},
formatRange: {
value: formatRange,
enumerable: true,
writable: false,
configurable: true
}
};
if (formatToParts in IntlDateTimeFormat.prototype) {
properties.formatToParts = {
value: formatToParts,
enumerable: true,
writable: false,
configurable: true
};
}
if (formatRangeToParts in IntlDateTimeFormat.prototype) {
properties.formatRangeToParts = {
value: formatToParts,
enumerable: true,
writable: false,
configurable: true
};
}
DateTimeFormat.prototype = Object.create(IntlDateTimeFormat.prototype, properties);
function resolvedOptions() {
return this[ORIGINAL].resolvedOptions();
}
function format(datetime, ...rest) {
const { absolute, formatter } = extractOverrides(datetime, this);
if (absolute && formatter) {
return formatter.format(absolute.getEpochMilliseconds());
}
if (datetime instanceof Duration) {
return this[DURATION].format(datetime, ...rest);
}
return this[ORIGINAL].format(datetime, ...rest);
}
function formatToParts(datetime, ...rest) {
const { absolute, formatter } = extractOverrides(datetime, this);
if (absolute && formatter) return formatter.formatToParts(absolute.getEpochMilliseconds());
if (datetime instanceof Duration) {
return this[DURATION].formatToParts(datetime, ...rest);
}
return this[ORIGINAL].formatToParts(datetime, ...rest);
}
function formatRange(a, b) {
if ('object' === typeof a && 'object' === typeof b && Object.getPrototypeOf(a) === Object.getPrototypeOf(b)) {
const { absolute: aa, formatter: aformatter } = extractOverrides(a, this);
const { absolute: bb, formatter: bformatter } = extractOverrides(b, this);
if (aa && bb && aformatter && bformatter && aformatter === bformatter) {
return formatter.formatRange(aa, ba);
}
}
return this[ORIGINAL].formatRange(a, b);
}
function formatRangeToParts(a, b) {
if ('object' === typeof a && 'object' === typeof b && Object.getPrototypeOf(a) === Object.getPrototypeOf(b)) {
const { absolute: aa, formatter: aformatter } = extractOverrides(a, this);
const { absolute: bb, formatter: bformatter } = extractOverrides(a, this);
if (aa && bb && aformatter && bformatter && aformatter === bformatter) {
return formatter.formatRangeToParts(aa, ba);
}
}
return this[ORIGINAL].formatRangeToParts(a, b);
}
function amend(options = {}, amended = {}) {
options = ObjectAssign({}, options);
for (let opt of ['year', 'month', 'day', 'hour', 'minute', 'second']) {
options[opt] = opt in amended ? amended[opt] : options[opt];
if (options[opt] === false || options[opt] === undefined) delete options[opt];
}
return options;
}
function timeAmend(options) {
options = amend(options, { year: false, month: false, day: false });
if (!hasTimeOptions(options)) {
options = ObjectAssign(options, {
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
}
return options;
}
function dateAmend(options, amendments) {
options = amend(options, { hour: false, minute: false, second: false });
if (!hasDateOptions(options)) {
options = ObjectAssign(options, {
year: 'numeric',
month: 'numeric',
day: 'numeric'
});
}
options = amend(options, amendments);
return options;
}
function datetimeAmend(options) {
options = ObjectAssign({}, options);
if (!hasTimeOptions(options) && !hasDateOptions(options)) {
ObjectAssign(options, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
});
}
return options;
}
function hasDateOptions(options) {
return 'year' in options || 'month' in options || 'day' in options;
}
function hasTimeOptions(options) {
return 'hour' in options || 'minute' in options || 'second' in options;
}
function extractOverrides(datetime, main) {
let formatter;
if (datetime instanceof Time) {
datetime = datetime.withDate(new Date(1970, 1, 1));
formatter = main[TIME];
}
if (datetime instanceof YearMonth) {
datetime = datetime.withDay(1);
formatter = main[YM];
}
if (datetime instanceof MonthDay) {
datetime = datetime.withYear(2004); // use a leap-year for maximum range
formatter = main[MD];
}
if (datetime instanceof Date) {
datetime = datetime.withTime(new Time(12, 0));
formatter = formatter || main[DATE];
}
if (datetime instanceof DateTime) {
formatter = formatter || main[DATETIME];
datetime = main[TIMEZONE].getAbsoluteFor(datetime, 'earlier');
}
if (datetime instanceof Absolute) {
formatter = formatter || main[DATETIME];
return { absolute: datetime, formatter };
} else {
return {};
}
}