Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
568d6be
Normative: Fast-path ToTemporalCalendar when the argument is a Calendar
ptomato Aug 29, 2022
f31abc2
Normative: Fast-path ToTemporalTimeZone when the argument is a TimeZone
ptomato Aug 29, 2022
5339aa0
Normative: Remove extra property check from Instant.p.toZonedDateTimeISO
ptomato Oct 12, 2022
84146c4
Normative: Fast-path conversion to TimeZone in PlainDate.p.toZonedDat…
ptomato Oct 12, 2022
c8912b0
Polyfill: Implement early return in UnbalanceDurationRelative as per …
ptomato Oct 11, 2022
d1e939f
Polyfill: Remove MergeLargestUnitOption
gibson042 Jun 6, 2022
efca474
Polyfill: Replace manual property enumeration with CopyDataProperties
gibson042 Jun 6, 2022
6bffeef
Normative: Require that NanosecondsToDays doesn't flip sign
ptomato Aug 26, 2022
eaf4c2d
Normative: Require NanosecondsToDays remainder less than day length
ptomato Aug 26, 2022
6d5e2ed
Normative: Accept calendar names case-insensitively
ptomato Aug 30, 2022
0f9e9bd
Editorial: Rename ToIntegerThrowOnInfinity to ToIntegerWithTruncation
ptomato Oct 25, 2022
cc2ae0f
Editorial: Rename ToPositiveInteger to ToPositiveIntegerWithTruncation
ptomato Oct 25, 2022
ac39fbd
Editorial: Rename ToIntegerWithoutRounding to ToIntegerIfIntegral
ptomato Oct 25, 2022
a1d8db9
Editorial: Use ToIntegerWithTruncation in ParseTemporalDurationString
ptomato Oct 26, 2022
0613780
Polyfill: Move property accesses before InterpretTemporalDateTimeFields
ptomato Nov 1, 2022
560d5ab
Editorial: Rename ToShowTimeZoneNameOption → ToTimeZoneNameOption
ptomato Nov 2, 2022
30952af
Editorial: Rename ToShowCalendarOption → ToCalendarNameOption
ptomato Nov 2, 2022
d28c3f8
Polyfill: Bring ZonedDateTime.p.with observable ops in line with spec
ptomato Nov 3, 2022
92334c7
Polyfill: Bring PlainTime since/until observable ops in line with spec
ptomato Nov 3, 2022
0f76912
Polyfill: Bring PlainDateTime.p.toString observable ops in line with …
ptomato Nov 3, 2022
2c0b11d
Editorial: Remove redundant ToString from ToTemporalZonedDateTime
ptomato Nov 15, 2022
11cd552
Polyfill: Align {Calendar,TimeZone} id getters with spec
gibson042 Sep 30, 2022
690186a
Normative: Throw on conversion from TimeZone to Calendar and vice versa
ptomato Nov 10, 2022
47f903e
Polyfill: Implement yearOfWeek APIs
ptomato Oct 21, 2022
be426a2
Editorial: Separate validation of roundingIncrement option
ptomato Nov 17, 2022
db7babd
Polyfill: Implement GetDifferenceSettings operation
ptomato Nov 18, 2022
c3ed52e
Editorial: Remove ToTemporalDateTimeRoundingIncrement
ptomato Nov 18, 2022
9a25f39
Polyfill: Implement annotations after YYYY-MM and MM-DD
ptomato Oct 24, 2022
476d897
Normative: Sort field access in PrepareTemporalFields
ptomato Nov 1, 2022
a5ddd92
Editorial: Remove redundant sort before PrepareTemporalFields
ptomato Nov 1, 2022
a95fdb4
Normative: Sort field access in ToRelativeTemporalObject
ptomato Nov 1, 2022
1c8bd94
Normative: Split fractionalSecondDigits and smallestUnit validation
ptomato Nov 18, 2022
c4ee427
Normative: Truncate roundingIncrement before range check
ptomato Nov 18, 2022
35de7b8
Normative: Alphabetize order of options bag property accesses
ptomato Nov 18, 2022
e65d6bf
Editorial: Rename ToSecondsStringPrecision→ToSecondsStringPrecisionRe…
ptomato Nov 30, 2022
dd67b85
Editorial: Move special case out of ValidateTemporalRoundingIncrement
ptomato Nov 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ export namespace Temporal {
dayOfWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
dayOfYear(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
weekOfYear(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
yearOfWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
daysInWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
daysInMonth(
date: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainYearMonth | PlainDateLike | string
Expand Down Expand Up @@ -720,6 +721,7 @@ export namespace Temporal {
dayOfWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
dayOfYear(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
weekOfYear(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
yearOfWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
daysInWeek(date: Temporal.PlainDate | Temporal.PlainDateTime | PlainDateLike | string): number;
daysInMonth(
date: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainYearMonth | PlainDateLike | string
Expand Down Expand Up @@ -805,6 +807,7 @@ export namespace Temporal {
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
readonly yearOfWeek: number;
readonly daysInWeek: number;
readonly daysInYear: number;
readonly daysInMonth: number;
Expand Down Expand Up @@ -919,6 +922,7 @@ export namespace Temporal {
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
readonly yearOfWeek: number;
readonly daysInWeek: number;
readonly daysInYear: number;
readonly daysInMonth: number;
Expand Down Expand Up @@ -1266,6 +1270,7 @@ export namespace Temporal {
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
readonly yearOfWeek: number;
readonly hoursInDay: number;
readonly daysInWeek: number;
readonly daysInMonth: number;
Expand Down
69 changes: 41 additions & 28 deletions lib/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ import type {

const ArrayIncludes = Array.prototype.includes;
const ArrayPrototypePush = Array.prototype.push;
const ArrayPrototypeSort = Array.prototype.sort;
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
const ArraySort = Array.prototype.sort;
const MathAbs = Math.abs;
const MathFloor = Math.floor;
const ObjectCreate = Object.create;
const ObjectEntries = Object.entries;
const ObjectKeys = Object.keys;

/**
* Shape of internal implementation of each built-in calendar. Note that
Expand All @@ -59,6 +58,7 @@ interface CalendarImpl {
dayOfWeek(date: Temporal.PlainDate): number;
dayOfYear(date: Temporal.PlainDate): number;
weekOfYear(date: Temporal.PlainDate): number;
yearOfWeek(date: Temporal.PlainDate): number;
daysInWeek(date: Temporal.PlainDate): number;
daysInMonth(date: Temporal.PlainDate | Temporal.PlainYearMonth): number;
daysInYear(date: Temporal.PlainDate | Temporal.PlainYearMonth): number;
Expand Down Expand Up @@ -132,7 +132,7 @@ export class Calendar implements Temporal.Calendar {
const id = ES.ToString(idParam);
if (!ES.IsBuiltinCalendar(id)) throw new RangeError(`invalid calendar identifier ${id}`);
CreateSlots(this);
SetSlot(this, CALENDAR_ID, id);
SetSlot(this, CALENDAR_ID, ES.ASCIILowercase(id));

if (DEBUG) {
Object.defineProperty(this, '_repr_', {
Expand All @@ -145,7 +145,7 @@ export class Calendar implements Temporal.Calendar {
}
get id(): Return['id'] {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
return ES.ToString(this);
return GetSlot(this, CALENDAR_ID);
}
dateFromFields(
fields: Params['dateFromFields'][0],
Expand Down Expand Up @@ -300,6 +300,11 @@ export class Calendar implements Temporal.Calendar {
const date = ES.ToTemporalDate(dateParam);
return impl[GetSlot(this, CALENDAR_ID)].weekOfYear(date);
}
yearOfWeek(dateParam: Params['yearOfWeek'][0]): Return['yearOfWeek'] {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
const date = ES.ToTemporalDate(dateParam);
return impl[GetSlot(this, CALENDAR_ID)].yearOfWeek(date);
}
daysInWeek(dateParam: Params['daysInWeek'][0]): Return['daysInWeek'] {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
const date = ES.ToTemporalDate(dateParam);
Expand Down Expand Up @@ -384,21 +389,18 @@ impl['iso8601'] = {
fields(fields) {
return fields;
},
mergeFields(fields, additionalFields) {
const merged: typeof fields = {};
for (const nextKey of ObjectKeys(fields)) {
if (nextKey === 'month' || nextKey === 'monthCode') continue;
merged[nextKey] = fields[nextKey];
}
const newKeys = ObjectKeys(additionalFields);
for (const nextKey of newKeys) {
merged[nextKey] = additionalFields[nextKey];
}
if (!ArrayIncludes.call(newKeys, 'month') && !ArrayIncludes.call(newKeys, 'monthCode')) {
const { month, monthCode } = fields;
if (month !== undefined) merged.month = month;
if (monthCode !== undefined) merged.monthCode = monthCode;
mergeFields(fieldsParam, additionalFieldsParam) {
const fields = ES.ToObject(fieldsParam);
const additionalFields = ES.ToObject(additionalFieldsParam);
const merged = ObjectCreate(null);
ES.CopyDataProperties(merged, fields, [], [undefined]);
const additionalFieldsCopy = ObjectCreate(null);
ES.CopyDataProperties(additionalFieldsCopy, additionalFields, [], [undefined]);
if ('month' in additionalFieldsCopy || 'monthCode' in additionalFieldsCopy) {
delete merged.month;
delete merged.monthCode;
}
ES.CopyDataProperties(merged, additionalFieldsCopy, []);
return merged;
},
dateAdd(date, years, months, weeks, days, overflow, calendar) {
Expand Down Expand Up @@ -444,7 +446,10 @@ impl['iso8601'] = {
return ES.DayOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY));
},
weekOfYear(date) {
return ES.WeekOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY));
return ES.WeekOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY)).week;
},
yearOfWeek(date) {
return ES.WeekOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY)).year;
},
daysInWeek() {
return 7;
Expand Down Expand Up @@ -2291,8 +2296,7 @@ class NonIsoCalendar implements CalendarImpl {
calendar: Temporal.Calendar
): Temporal.PlainDate {
const cache = new OneObjectCache();
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as readonly AnyTemporalKey[];
ArrayPrototypeSort.call(fieldNames);
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.calendarToIsoDate(fields, overflow, cache);
Expand All @@ -2306,8 +2310,7 @@ class NonIsoCalendar implements CalendarImpl {
calendar: Temporal.Calendar
): Temporal.PlainYearMonth {
const cache = new OneObjectCache();
const fieldNames = this.fields(['month', 'monthCode', 'year']) as readonly AnyTemporalKey[];
ArrayPrototypeSort.call(fieldNames);
const fieldNames = this.fields(['month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.calendarToIsoDate({ ...fields, day: 1 }, overflow, cache);
Expand All @@ -2323,8 +2326,7 @@ class NonIsoCalendar implements CalendarImpl {
const cache = new OneObjectCache();
// For lunisolar calendars, either `monthCode` or `year` must be provided
// because `month` is ambiguous without a year or a code.
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as readonly AnyTemporalKey[];
ArrayPrototypeSort.call(fieldNames);
const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']) as AnyTemporalKey[];
const fields = ES.PrepareTemporalFields(fieldsParam, fieldNames, []);
const overflow = ES.ToTemporalOverflow(options);
const { year, month, day } = this.helper.monthDayFromFields(fields, overflow, cache);
Expand All @@ -2338,9 +2340,17 @@ class NonIsoCalendar implements CalendarImpl {
if (ArrayIncludes.call(fields, 'year')) fields = [...fields, 'era', 'eraYear'];
return fields;
}
mergeFields(fields: Record<string, unknown>, additionalFields: Record<string, unknown>): Record<string, unknown> {
const fieldsCopy = { ...fields };
const additionalFieldsCopy = { ...additionalFields };
mergeFields(
fieldsParam: Record<string, unknown>,
additionalFieldsParam: Record<string, unknown>
): Record<string, unknown> {
const fields = ES.ToObject(fieldsParam);
const additionalFields = ES.ToObject(additionalFieldsParam);
const fieldsCopy = {} as typeof fieldsParam;
ES.CopyDataProperties(fieldsCopy, fields, [], [undefined]);
const additionalFieldsCopy = {} as typeof additionalFieldsParam;
ES.CopyDataProperties(additionalFieldsCopy, additionalFields, [], [undefined]);

// era and eraYear are intentionally unused
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { month, monthCode, year, era, eraYear, ...original } = fieldsCopy;
Expand Down Expand Up @@ -2436,6 +2446,9 @@ class NonIsoCalendar implements CalendarImpl {
weekOfYear(date: Temporal.PlainDate): number {
return impl['iso8601'].weekOfYear(date);
}
yearOfWeek(date: Temporal.PlainDate): number {
return impl['iso8601'].yearOfWeek(date);
}
daysInWeek(date: Temporal.PlainDate): number {
return impl['iso8601'].daysInWeek(date);
}
Expand Down
67 changes: 44 additions & 23 deletions lib/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ export class Duration implements Temporal.Duration {
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);
const years = ES.ToIntegerIfIntegral(yearsParam);
const months = ES.ToIntegerIfIntegral(monthsParam);
const weeks = ES.ToIntegerIfIntegral(weeksParam);
const days = ES.ToIntegerIfIntegral(daysParam);
const hours = ES.ToIntegerIfIntegral(hoursParam);
const minutes = ES.ToIntegerIfIntegral(minutesParam);
const seconds = ES.ToIntegerIfIntegral(secondsParam);
const milliseconds = ES.ToIntegerIfIntegral(millisecondsParam);
const microseconds = ES.ToIntegerIfIntegral(microsecondsParam);
const nanoseconds = ES.ToIntegerIfIntegral(nanosecondsParam);

ES.RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);

Expand Down Expand Up @@ -202,9 +202,9 @@ export class Duration implements Temporal.Duration {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
return ES.AddDurationToOrSubtractDurationFromDuration('subtract', this, other, options);
}
round(optionsParam: Params['round'][0]): Return['round'] {
round(roundToParam: Params['round'][0]): Return['round'] {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
if (optionsParam === undefined) throw new TypeError('options parameter is required');
if (roundToParam === undefined) throw new TypeError('options parameter is required');
let years = GetSlot(this, YEARS);
let months = GetSlot(this, MONTHS);
let weeks = GetSlot(this, WEEKS);
Expand All @@ -228,18 +228,23 @@ export class Duration implements Temporal.Duration {
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);
const roundTo =
typeof roundToParam === 'string'
? (ES.CreateOnePropObject('smallestUnit', roundToParam) as Exclude<typeof roundToParam, string>)
: ES.GetOptionsObject(roundToParam);

let largestUnit = ES.GetTemporalUnit(roundTo, 'largestUnit', 'datetime', undefined, ['auto']);
let relativeTo = ES.ToRelativeTemporalObject(roundTo);
const roundingIncrement = ES.ToTemporalRoundingIncrement(roundTo);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
let smallestUnit = ES.GetTemporalUnit(roundTo, '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;
Expand All @@ -252,9 +257,17 @@ export class Duration implements Temporal.Duration {
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);

const maximumIncrements = {
hour: 24,
minute: 60,
second: 60,
millisecond: 1000,
microsecond: 1000,
nanosecond: 1000
} as { [k in Temporal.DateTimeUnit]?: number };
const maximum = maximumIncrements[smallestUnit];
if (maximum !== undefined) ES.ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false);

({ years, months, weeks, days } = ES.UnbalanceDurationRelative(
years,
Expand Down Expand Up @@ -383,9 +396,17 @@ export class Duration implements Temporal.Duration {
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 digits = ES.ToFractionalSecondDigits(options);
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'time', undefined);
if (smallestUnit === 'hour' || smallestUnit === 'minute') {
throw new RangeError('smallestUnit must be a time unit other than "hours" or "minutes"');
}
const { precision, unit, increment } = ES.ToSecondsStringPrecisionRecord(smallestUnit, digits);
ES.uncheckedAssertNarrowedType<Exclude<typeof precision, 'minute'>>(
precision,
'Precision cannot be "minute" because of RangeError above'
);
return ES.TemporalDurationToString(this, precision, { unit, increment, roundingMode });
}
toJSON(): Return['toJSON'] {
Expand Down
Loading