Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of ZonedDateTime #1073

Merged
merged 6 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ date.toString() // => 2020-06-28[c=islamic]

**Returns:** a `Temporal.Duration` representing the time elapsed after `one` and until `two`.

If either of `smaller` or `larger` are not `Temporal.Date` objects, then they will be converted to one as if they were passed to `Temporal.Date.from()`.
If either of `one` or `two` are not `Temporal.Date` objects, then they will be converted to one as if they were passed to `Temporal.Date.from()`.

This method does not need to be called directly except in specialized code.
It is called indirectly when using the `until()` and `since()` methods of `Temporal.DateTime`, `Temporal.Date`, and `Temporal.YearMonth`.
Expand Down
2 changes: 1 addition & 1 deletion docs/time.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ This method overrides `Object.prototype.valueOf()` and always throws an exceptio
This is because it's not possible to compare `Temporal.Time` objects with the relational operators `<`, `<=`, `>`, or `>=`.
Use `Temporal.Time.compare()` for this, or `time.equals()` for equality.

### time.**toZonedDateTime**(_timeZone_?: Temporal.TimeZone | object | string, _date_: Temporal.Date | object | string) : Temporal.ZonedDateTime
### time.**toZonedDateTime**(_timeZone_?: Temporal.TimeZone | object | string, _date_: Temporal.Date | object | string, _options_?: object) : Temporal.ZonedDateTime

**Parameters:**

Expand Down
229 changes: 228 additions & 1 deletion 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 @@ -649,7 +691,12 @@ export namespace Temporal {
| /** @deprecated */ 'day'
>
): Temporal.Duration;
toDateTime(temporalTime: Temporal.Time | TimeLike | string): Temporal.DateTime;
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,180 @@ export namespace Temporal {
valueOf(): never;
}

export type ZonedDateTimeLike = {
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 = {
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 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, crap. This may run afoul of the guidelines we've been using that object-valued property getters must return the same (===) object every time. That'd imply a slot to cache this object. Probably not worth the hassle and potential runtime performance impact to keep it a property. Should we change this to a startOfDay() method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this is better off as a method.

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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor question: why is it TIME_ZONE but EPOCHNANOSECONDS? Is there some meaning intended by not splitting EPOCH and NANOSECONDS with an underscore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty arbitrary. Splitting with an underscore seems proper, but I didn't want to rename all of the existing EPOCHNANOSECONDS names used in Instant.

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
13 changes: 11 additions & 2 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 @@ -260,8 +261,7 @@ export class DateTime {
}
withCalendar(calendar) {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const TemporalCalendar = GetIntrinsic('%Temporal.Calendar%');
calendar = TemporalCalendar.from(calendar);
calendar = ES.ToTemporalCalendar(calendar);
const Construct = ES.SpeciesConstructor(this, DateTime);
const result = new Construct(
GetSlot(this, ISO_YEAR),
Expand Down Expand Up @@ -721,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