Skip to content

Commit

Permalink
Initial ZonedDateTime implementation
Browse files Browse the repository at this point in the history
This contains all the "easy" ZonedDateTime methods, and their tests, as
well as all the methods to convert from other types to ZonedDateTime and
their tests and specification.

See: #569
  • Loading branch information
ptomato committed Oct 29, 2020
1 parent f03b29f commit 01af565
Show file tree
Hide file tree
Showing 21 changed files with 1,773 additions and 5 deletions.
230 changes: 230 additions & 0 deletions polyfill/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ export namespace Temporal {
disambiguation: 'compatible' | 'earlier' | 'later' | 'reject';
};

type OffsetDisambiguationOptions = {
/**
* Time zone definitions can change. If an application stores data about
* events in the future, then stored data about future events may become
* ambiguous, for example if a country permanently abolishes DST. The
* `offset` option controls this unusual case.
*
* - `'use'` always uses the offset (if it's provided) to calculate the
* instant. This ensures that the result will match the instant that was
* originally stored, even if local clock time is different.
* - `'prefer'` uses the offset if it's valid for the date/time in this time
* zone, but if it's not valid then the time zone will be used as a
* fallback to calculate the instant.
* - `'ignore'` will disregard any provided offset. Instead, the time zone
* and date/time value are used to calculate the instant. This will keep
* local clock time unchanged but may result in a different real-world
* instant.
* - `'reject'` acts like `'prefer'`, except it will throw a RangeError if
* the offset is not valid for the given time zone identifier and
* date/time value.
*
* If the ISO string ends in 'Z' then this option is ignored because there
* is no possibility of ambiguity.
*
* If a time zone offset is not present in the input, then this option is
* ignored because the time zone will always be used to calculate the
* offset.
*
* If the offset is not used, and if the date/time and time zone don't
* uniquely identify a single instant, then the `disambiguation` option will
* be used to choose the correct instant. However, if the offset is used
* then the `disambiguation` option will be ignored.
*/
offset: 'use' | 'prefer' | 'ignore' | 'reject';
};

export type ZonedDateTimeAssignmentOptions = Partial<
AssignmentOptions & ToInstantOptions & OffsetDisambiguationOptions
>;

/**
* Options for arithmetic operations like `add()` and `subtract()`
* */
Expand Down Expand Up @@ -441,6 +481,8 @@ export namespace Temporal {
): Temporal.Instant;
toDateTime(tzLike: TimeZoneProtocol | string, calendar: CalendarProtocol | string): Temporal.DateTime;
toDateTimeISO(tzLike: TimeZoneProtocol | string): Temporal.DateTime;
toZonedDateTime(tzLike: TimeZoneProtocol | string, calendar: CalendarProtocol | string): Temporal.ZonedDateTime;
toZonedDateTimeISO(tzLike: TimeZoneProtocol | string): Temporal.ZonedDateTime;
toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
toJSON(): string;
toString(tzLike?: TimeZoneProtocol | string, options?: ToStringOptions): string;
Expand Down Expand Up @@ -650,6 +692,11 @@ export namespace Temporal {
>
): Temporal.Duration;
toDateTime(temporalTime?: Temporal.Time | TimeLike | string): Temporal.DateTime;
toZonedDateTime(
timeZone: TimeZoneProtocol | string,
temporalTime?: Temporal.Time | TimeLike | string,
options?: ToInstantOptions
): Temporal.ZonedDateTime;
toYearMonth(): Temporal.YearMonth;
toMonthDay(): Temporal.MonthDay;
getFields(): DateFields;
Expand Down Expand Up @@ -817,6 +864,7 @@ export namespace Temporal {
>
): Temporal.DateTime;
toInstant(tzLike: TimeZoneProtocol | string, options?: ToInstantOptions): Temporal.Instant;
toZonedDateTime(tzLike: TimeZoneProtocol | string, options?: ToInstantOptions): Temporal.ZonedDateTime;
toDate(): Temporal.Date;
toYearMonth(): Temporal.YearMonth;
toMonthDay(): Temporal.MonthDay;
Expand Down Expand Up @@ -976,6 +1024,11 @@ export namespace Temporal {
>
): Temporal.Time;
toDateTime(temporalDate: Temporal.Date | DateLike | string): Temporal.DateTime;
toZonedDateTime(
timeZoneLike: TimeZoneProtocol | string,
temporalDate: Temporal.Date | DateLike | string,
options?: ToInstantOptions
): Temporal.ZonedDateTime;
getFields(): TimeFields;
toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
toJSON(): string;
Expand Down Expand Up @@ -1082,6 +1135,183 @@ export namespace Temporal {
valueOf(): never;
}

export type ZonedDateTimeLike = {
era?: string | undefined;
year?: number;
month?: number;
day?: number;
hour?: number;
minute?: number;
second?: number;
millisecond?: number;
microsecond?: number;
nanosecond?: number;
offset?: string;
timeZone?: TimeZoneProtocol | string;
calendar?: CalendarProtocol | string;
};

type ZonedDateTimeFields = {
era: string | undefined;
year: number;
month: number;
day: number;
hour: number;
minute: number;
second: number;
millisecond: number;
microsecond: number;
nanosecond: number;
offset: string;
timeZone: TimeZoneProtocol;
calendar: CalendarProtocol;
};

type ZonedDateTimeISOFields = {
isoYear: number;
isoMonth: number;
isoDay: number;
hour: number;
minute: number;
second: number;
millisecond: number;
microsecond: number;
nanosecond: number;
offsetNanoseconds: number;
timeZone: TimeZoneProtocol;
calendar: CalendarProtocol;
};

export class ZonedDateTime {
static from(
item: Temporal.ZonedDateTime | ZonedDateTimeLike | string,
options?: ZonedDateTimeAssignmentOptions
): ZonedDateTime;
static compare(
one: Temporal.ZonedDateTime | ZonedDateTimeLike | string,
two: Temporal.ZonedDateTime | ZonedDateTimeLike | string
): ComparisonResult;
constructor(epochNanoseconds: bigint, timeZone: TimeZoneProtocol | string, calendar?: CalendarProtocol | string);
readonly year: number;
readonly month: number;
readonly day: number;
readonly hour: number;
readonly minute: number;
readonly second: number;
readonly millisecond: number;
readonly microsecond: number;
readonly nanosecond: number;
readonly timeZone: TimeZoneProtocol;
readonly calendar: CalendarProtocol;
readonly era: string | undefined;
readonly dayOfWeek: number;
readonly dayOfYear: number;
readonly weekOfYear: number;
readonly hoursInDay: number;
readonly daysInWeek: number;
readonly daysInMonth: number;
readonly daysInYear: number;
readonly monthsInYear: number;
readonly inLeapYear: boolean;
readonly startOfDay: Temporal.ZonedDateTime;
readonly offsetNanoseconds: number;
readonly offset: string;
readonly epochSeconds: number;
readonly epochMilliseconds: number;
readonly epochMicroseconds: bigint;
readonly epochNanoseconds: bigint;
equals(other: Temporal.ZonedDateTime | ZonedDateTimeLike | string): boolean;
with(
zonedDateTimeLike: ZonedDateTimeLike | string,
options?: ZonedDateTimeAssignmentOptions
): Temporal.ZonedDateTime;
withCalendar(calendar: CalendarProtocol | string): Temporal.ZonedDateTime;
withTimeZone(timeZone: TimeZoneProtocol | string): Temporal.ZonedDateTime;
add(durationLike: Temporal.Duration | DurationLike | string, options?: ArithmeticOptions): Temporal.ZonedDateTime;
subtract(
durationLike: Temporal.Duration | DurationLike | string,
options?: ArithmeticOptions
): Temporal.ZonedDateTime;
until(
other: Temporal.ZonedDateTime | ZonedDateTimeLike | string,
options?: Temporal.DifferenceOptions<
| 'years'
| 'months'
| 'weeks'
| 'days'
| 'hours'
| 'minutes'
| 'seconds'
| 'milliseconds'
| 'microseconds'
| 'nanoseconds'
| /** @deprecated */ 'year'
| /** @deprecated */ 'month'
| /** @deprecated */ 'day'
| /** @deprecated */ 'hour'
| /** @deprecated */ 'minute'
| /** @deprecated */ 'second'
| /** @deprecated */ 'millisecond'
| /** @deprecated */ 'microsecond'
| /** @deprecated */ 'nanosecond'
>
): Temporal.Duration;
since(
other: Temporal.ZonedDateTime | ZonedDateTimeLike | string,
options?: Temporal.DifferenceOptions<
| 'years'
| 'months'
| 'weeks'
| 'days'
| 'hours'
| 'minutes'
| 'seconds'
| 'milliseconds'
| 'microseconds'
| 'nanoseconds'
| /** @deprecated */ 'year'
| /** @deprecated */ 'month'
| /** @deprecated */ 'day'
| /** @deprecated */ 'hour'
| /** @deprecated */ 'minute'
| /** @deprecated */ 'second'
| /** @deprecated */ 'millisecond'
| /** @deprecated */ 'microsecond'
| /** @deprecated */ 'nanosecond'
>
): Temporal.Duration;
round(
options: Temporal.RoundOptions<
| 'day'
| 'hour'
| 'minute'
| 'second'
| 'millisecond'
| 'microsecond'
| 'nanosecond'
| /** @deprecated */ 'days'
| /** @deprecated */ 'hours'
| /** @deprecated */ 'minutes'
| /** @deprecated */ 'seconds'
| /** @deprecated */ 'milliseconds'
| /** @deprecated */ 'microseconds'
| /** @deprecated */ 'nanoseconds'
>
): Temporal.ZonedDateTime;
toInstant(): Temporal.Instant;
toDateTime(): Temporal.DateTime;
toDate(): Temporal.Date;
toYearMonth(): Temporal.YearMonth;
toMonthDay(): Temporal.MonthDay;
toTime(): Temporal.Time;
getFields(): ZonedDateTimeFields;
getISOFields(): ZonedDateTimeISOFields;
toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
toJSON(): string;
toString(): string;
valueOf(): never;
}

/**
* The `Temporal.now` object has several methods which give information about
* the current date, time, and time zone.
Expand Down
34 changes: 34 additions & 0 deletions polyfill/lib/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
NANOSECOND,
DATE_BRAND,
CALENDAR,
EPOCHNANOSECONDS,
CreateSlots,
GetSlot,
SetSlot
Expand Down Expand Up @@ -323,6 +324,39 @@ export class Date {
const nanosecond = GetSlot(temporalTime, NANOSECOND);
return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
}
toZonedDateTime(timeZoneLike, temporalTime = undefined, options = undefined) {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const timeZone = ES.ToTemporalTimeZone(timeZoneLike);
options = ES.NormalizeOptionsObject(options);
const disambiguation = ES.ToTemporalDisambiguation(options);

const year = GetSlot(this, ISO_YEAR);
const month = GetSlot(this, ISO_MONTH);
const day = GetSlot(this, ISO_DAY);
const calendar = GetSlot(this, CALENDAR);
const DateTime = GetIntrinsic('%Temporal.DateTime%');

let hour = 0,
minute = 0,
second = 0,
millisecond = 0,
microsecond = 0,
nanosecond = 0;
if (temporalTime !== undefined) {
temporalTime = ES.ToTemporalTime(temporalTime, GetIntrinsic('%Temporal.Time%'));
hour = GetSlot(temporalTime, HOUR);
minute = GetSlot(temporalTime, MINUTE);
second = GetSlot(temporalTime, SECOND);
millisecond = GetSlot(temporalTime, MILLISECOND);
microsecond = GetSlot(temporalTime, MICROSECOND);
nanosecond = GetSlot(temporalTime, NANOSECOND);
}

const dt = new DateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
const instant = ES.GetTemporalInstantFor(timeZone, dt, disambiguation);
const ZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new ZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, calendar);
}
toYearMonth() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const YearMonth = GetIntrinsic('%Temporal.YearMonth%');
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
MICROSECOND,
NANOSECOND,
CALENDAR,
EPOCHNANOSECONDS,
CreateSlots,
GetSlot,
SetSlot
Expand Down Expand Up @@ -720,6 +721,15 @@ export class DateTime {
const disambiguation = ES.ToTemporalDisambiguation(options);
return ES.GetTemporalInstantFor(timeZone, this, disambiguation);
}
toZonedDateTime(temporalTimeZoneLike, options = undefined) {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
options = ES.NormalizeOptionsObject(options);
const disambiguation = ES.ToTemporalDisambiguation(options);
const instant = ES.GetTemporalInstantFor(timeZone, this, disambiguation);
const ZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new ZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZone, GetSlot(this, CALENDAR));
}
toDate() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
return ES.TemporalDateTimeToDate(this);
Expand Down
Loading

0 comments on commit 01af565

Please sign in to comment.