@js-temporal/polyfill
Version:
Polyfill for Tc39 Stage 3 proposal Temporal (https://github.com/tc39/proposal-temporal)
463 lines (452 loc) • 17 kB
text/typescript
import { DEBUG } from './debug';
import * as ES from './ecmascript';
import { MakeIntrinsicClass } from './intrinsicclass';
import {
YEARS,
MONTHS,
WEEKS,
DAYS,
HOURS,
MINUTES,
SECONDS,
MILLISECONDS,
MICROSECONDS,
NANOSECONDS,
CreateSlots,
GetSlot,
SetSlot
} from './slots';
import type { Temporal } from '..';
import type { DurationParams as Params, DurationReturn as Return } from './internaltypes';
import JSBI from 'jsbi';
export class Duration implements Temporal.Duration {
constructor(
yearsParam: Params['constructor'][0] = 0,
monthsParam: Params['constructor'][1] = 0,
weeksParam: Params['constructor'][2] = 0,
daysParam: Params['constructor'][3] = 0,
hoursParam: Params['constructor'][4] = 0,
minutesParam: Params['constructor'][5] = 0,
secondsParam: Params['constructor'][6] = 0,
millisecondsParam: Params['constructor'][7] = 0,
microsecondsParam: Params['constructor'][8] = 0,
nanosecondsParam: Params['constructor'][9] = 0
) {
const years = ES.ToIntegerWithoutRounding(yearsParam);
const months = ES.ToIntegerWithoutRounding(monthsParam);
const weeks = ES.ToIntegerWithoutRounding(weeksParam);
const days = ES.ToIntegerWithoutRounding(daysParam);
const hours = ES.ToIntegerWithoutRounding(hoursParam);
const minutes = ES.ToIntegerWithoutRounding(minutesParam);
const seconds = ES.ToIntegerWithoutRounding(secondsParam);
const milliseconds = ES.ToIntegerWithoutRounding(millisecondsParam);
const microseconds = ES.ToIntegerWithoutRounding(microsecondsParam);
const nanoseconds = ES.ToIntegerWithoutRounding(nanosecondsParam);
ES.RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
CreateSlots(this);
SetSlot(this, YEARS, years);
SetSlot(this, MONTHS, months);
SetSlot(this, WEEKS, weeks);
SetSlot(this, DAYS, days);
SetSlot(this, HOURS, hours);
SetSlot(this, MINUTES, minutes);
SetSlot(this, SECONDS, seconds);
SetSlot(this, MILLISECONDS, milliseconds);
SetSlot(this, MICROSECONDS, microseconds);
SetSlot(this, NANOSECONDS, nanoseconds);
if (DEBUG) {
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${ES.TemporalDurationToString(this)}>`,
writable: false,
enumerable: false,
configurable: false
});
}
}
get years(): Return['years'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, YEARS);
}
get months(): Return['months'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, MONTHS);
}
get weeks(): Return['weeks'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, WEEKS);
}
get days(): Return['days'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, DAYS);
}
get hours(): Return['hours'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, HOURS);
}
get minutes(): Return['minutes'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, MINUTES);
}
get seconds(): Return['seconds'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, SECONDS);
}
get milliseconds(): Return['milliseconds'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, MILLISECONDS);
}
get microseconds(): Return['microseconds'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, MICROSECONDS);
}
get nanoseconds(): Return['nanoseconds'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return GetSlot(this, NANOSECONDS);
}
get sign(): Return['sign'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.DurationSign(
GetSlot(this, YEARS),
GetSlot(this, MONTHS),
GetSlot(this, WEEKS),
GetSlot(this, DAYS),
GetSlot(this, HOURS),
GetSlot(this, MINUTES),
GetSlot(this, SECONDS),
GetSlot(this, MILLISECONDS),
GetSlot(this, MICROSECONDS),
GetSlot(this, NANOSECONDS)
);
}
get blank(): Return['blank'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return (
ES.DurationSign(
GetSlot(this, YEARS),
GetSlot(this, MONTHS),
GetSlot(this, WEEKS),
GetSlot(this, DAYS),
GetSlot(this, HOURS),
GetSlot(this, MINUTES),
GetSlot(this, SECONDS),
GetSlot(this, MILLISECONDS),
GetSlot(this, MICROSECONDS),
GetSlot(this, NANOSECONDS)
) === 0
);
}
with(durationLike: Params['with'][0]): Return['with'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
const props = ES.PrepareTemporalFields(
durationLike,
// NOTE: Field order here is important.
[
'days',
'hours',
'microseconds',
'milliseconds',
'minutes',
'months',
'nanoseconds',
'seconds',
'weeks',
'years'
],
'partial'
);
if (!props) {
throw new TypeError('invalid duration-like');
}
const {
years = GetSlot(this, YEARS),
months = GetSlot(this, MONTHS),
weeks = GetSlot(this, WEEKS),
days = GetSlot(this, DAYS),
hours = GetSlot(this, HOURS),
minutes = GetSlot(this, MINUTES),
seconds = GetSlot(this, SECONDS),
milliseconds = GetSlot(this, MILLISECONDS),
microseconds = GetSlot(this, MICROSECONDS),
nanoseconds = GetSlot(this, NANOSECONDS)
} = props;
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
negated(): Return['negated'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.CreateNegatedTemporalDuration(this);
}
abs(): Return['abs'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return new Duration(
Math.abs(GetSlot(this, YEARS)),
Math.abs(GetSlot(this, MONTHS)),
Math.abs(GetSlot(this, WEEKS)),
Math.abs(GetSlot(this, DAYS)),
Math.abs(GetSlot(this, HOURS)),
Math.abs(GetSlot(this, MINUTES)),
Math.abs(GetSlot(this, SECONDS)),
Math.abs(GetSlot(this, MILLISECONDS)),
Math.abs(GetSlot(this, MICROSECONDS)),
Math.abs(GetSlot(this, NANOSECONDS))
);
}
add(other: Params['add'][0], options: Params['add'][1] = undefined): Return['add'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.AddDurationToOrSubtractDurationFromDuration('add', this, other, options);
}
subtract(other: Params['subtract'][0], options: Params['subtract'][1] = undefined): Return['subtract'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.AddDurationToOrSubtractDurationFromDuration('subtract', this, other, options);
}
round(optionsParam: Params['round'][0]): Return['round'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
if (optionsParam === undefined) throw new TypeError('options parameter is required');
let years = GetSlot(this, YEARS);
let months = GetSlot(this, MONTHS);
let weeks = GetSlot(this, WEEKS);
let days = GetSlot(this, DAYS);
let hours = GetSlot(this, HOURS);
let minutes = GetSlot(this, MINUTES);
let seconds = GetSlot(this, SECONDS);
let milliseconds = GetSlot(this, MILLISECONDS);
let microseconds = GetSlot(this, MICROSECONDS);
let nanoseconds = GetSlot(this, NANOSECONDS);
let defaultLargestUnit = ES.DefaultTemporalLargestUnit(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds
);
const options =
typeof optionsParam === 'string'
? (ES.CreateOnePropObject('smallestUnit', optionsParam) as Exclude<typeof optionsParam, string>)
: ES.GetOptionsObject(optionsParam);
let smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'datetime', undefined);
let smallestUnitPresent = true;
if (!smallestUnit) {
smallestUnitPresent = false;
smallestUnit = 'nanosecond';
}
defaultLargestUnit = ES.LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'datetime', undefined, ['auto']);
let largestUnitPresent = true;
if (!largestUnit) {
largestUnitPresent = false;
largestUnit = defaultLargestUnit;
}
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
if (!smallestUnitPresent && !largestUnitPresent) {
throw new RangeError('at least one of smallestUnit or largestUnit is required');
}
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
const roundingMode = ES.ToTemporalRoundingMode(options, 'halfExpand');
const roundingIncrement = ES.ToTemporalDateTimeRoundingIncrement(options, smallestUnit);
let relativeTo = ES.ToRelativeTemporalObject(options);
({ years, months, weeks, days } = ES.UnbalanceDurationRelative(
years,
months,
weeks,
days,
largestUnit,
relativeTo
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.RoundDuration(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.AdjustRoundedDurationDays(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
roundingIncrement,
smallestUnit,
roundingMode,
relativeTo
));
({ years, months, weeks, days } = ES.BalanceDurationRelative(years, months, weeks, days, largestUnit, relativeTo));
if (ES.IsTemporalZonedDateTime(relativeTo)) {
relativeTo = ES.MoveRelativeZonedDateTime(relativeTo, years, months, weeks, 0);
}
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceDuration(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
largestUnit,
relativeTo
));
return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
total(optionsParam: Params['total'][0]): Return['total'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
let years = GetSlot(this, YEARS);
let months = GetSlot(this, MONTHS);
let weeks = GetSlot(this, WEEKS);
let days = GetSlot(this, DAYS);
let hours = GetSlot(this, HOURS);
let minutes = GetSlot(this, MINUTES);
let seconds = GetSlot(this, SECONDS);
let milliseconds = GetSlot(this, MILLISECONDS);
let microseconds = GetSlot(this, MICROSECONDS);
let nanoseconds = GetSlot(this, NANOSECONDS);
if (optionsParam === undefined) throw new TypeError('options argument is required');
const options =
typeof optionsParam === 'string'
? (ES.CreateOnePropObject('unit', optionsParam) as Exclude<typeof optionsParam, string>)
: ES.GetOptionsObject(optionsParam);
const unit = ES.GetTemporalUnit(options, 'unit', 'datetime', ES.REQUIRED);
const relativeTo = ES.ToRelativeTemporalObject(options);
// Convert larger units down to days
({ years, months, weeks, days } = ES.UnbalanceDurationRelative(years, months, weeks, days, unit, relativeTo));
// If the unit we're totalling is smaller than `days`, convert days down to that unit.
let intermediate;
if (ES.IsTemporalZonedDateTime(relativeTo)) {
intermediate = ES.MoveRelativeZonedDateTime(relativeTo, years, months, weeks, 0);
}
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceDuration(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
unit,
intermediate
));
// Finally, truncate to the correct unit and calculate remainder
const { total } = ES.RoundDuration(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
1,
unit,
'trunc',
relativeTo
);
return total;
}
toString(optionsParam: Params['toString'][0] = undefined): string {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
const options = ES.GetOptionsObject(optionsParam);
const { precision, unit, increment } = ES.ToSecondsStringPrecision(options);
if (precision === 'minute') throw new RangeError('smallestUnit must not be "minute"');
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
return ES.TemporalDurationToString(this, precision, { unit, increment, roundingMode });
}
toJSON(): Return['toJSON'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.TemporalDurationToString(this);
}
toLocaleString(
locales: Params['toLocaleString'][0] = undefined,
options: Params['toLocaleString'][1] = undefined
): string {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
if (typeof Intl !== 'undefined' && typeof (Intl as any).DurationFormat !== 'undefined') {
return new (Intl as any).DurationFormat(locales, options).format(this);
}
console.warn('Temporal.Duration.prototype.toLocaleString() requires Intl.DurationFormat.');
return ES.TemporalDurationToString(this);
}
valueOf(): never {
throw new TypeError('use compare() to compare Temporal.Duration');
}
static from(item: Params['from'][0]): Return['from'] {
if (ES.IsTemporalDuration(item)) {
return new Duration(
GetSlot(item, YEARS),
GetSlot(item, MONTHS),
GetSlot(item, WEEKS),
GetSlot(item, DAYS),
GetSlot(item, HOURS),
GetSlot(item, MINUTES),
GetSlot(item, SECONDS),
GetSlot(item, MILLISECONDS),
GetSlot(item, MICROSECONDS),
GetSlot(item, NANOSECONDS)
);
}
return ES.ToTemporalDuration(item);
}
static compare(
oneParam: Params['compare'][0],
twoParam: Params['compare'][1],
optionsParam: Params['compare'][2] = undefined
) {
const one = ES.ToTemporalDuration(oneParam);
const two = ES.ToTemporalDuration(twoParam);
const options = ES.GetOptionsObject(optionsParam);
const relativeTo = ES.ToRelativeTemporalObject(options);
const y1 = GetSlot(one, YEARS);
const mon1 = GetSlot(one, MONTHS);
const w1 = GetSlot(one, WEEKS);
let d1 = GetSlot(one, DAYS);
const h1 = GetSlot(one, HOURS);
const min1 = GetSlot(one, MINUTES);
const s1 = GetSlot(one, SECONDS);
const ms1 = GetSlot(one, MILLISECONDS);
const µs1 = GetSlot(one, MICROSECONDS);
let ns1 = GetSlot(one, NANOSECONDS);
const y2 = GetSlot(two, YEARS);
const mon2 = GetSlot(two, MONTHS);
const w2 = GetSlot(two, WEEKS);
let d2 = GetSlot(two, DAYS);
const h2 = GetSlot(two, HOURS);
const min2 = GetSlot(two, MINUTES);
const s2 = GetSlot(two, SECONDS);
const ms2 = GetSlot(two, MILLISECONDS);
const µs2 = GetSlot(two, MICROSECONDS);
let ns2 = GetSlot(two, NANOSECONDS);
const shift1 = ES.CalculateOffsetShift(relativeTo, y1, mon1, w1, d1);
const shift2 = ES.CalculateOffsetShift(relativeTo, y2, mon2, w2, d2);
if (y1 !== 0 || y2 !== 0 || mon1 !== 0 || mon2 !== 0 || w1 !== 0 || w2 !== 0) {
({ days: d1 } = ES.UnbalanceDurationRelative(y1, mon1, w1, d1, 'day', relativeTo));
({ days: d2 } = ES.UnbalanceDurationRelative(y2, mon2, w2, d2, 'day', relativeTo));
}
const totalNs1 = ES.TotalDurationNanoseconds(d1, h1, min1, s1, ms1, µs1, ns1, shift1);
const totalNs2 = ES.TotalDurationNanoseconds(d2, h2, min2, s2, ms2, µs2, ns2, shift2);
return ES.ComparisonResult(JSBI.toNumber(JSBI.subtract(totalNs1, totalNs2)));
}
[Symbol.toStringTag]!: 'Temporal.Duration';
}
MakeIntrinsicClass(Duration, 'Temporal.Duration');