Skip to content
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
24 changes: 16 additions & 8 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export const ES = ObjectAssign({}, ES2020, {
let canonicalIdent = ES.GetCanonicalTimeZoneIdentifier(stringIdent);
if (canonicalIdent) {
canonicalIdent = canonicalIdent.toString();
if (ES.ParseOffsetString(canonicalIdent) !== null) return { offset: canonicalIdent };
if (ES.TestTimeZoneOffsetString(canonicalIdent)) return { offset: canonicalIdent };
return { ianaName: canonicalIdent };
}
} catch {
Expand Down Expand Up @@ -447,7 +447,8 @@ export const ES = ObjectAssign({}, ES2020, {
nanosecond
);
if (epochNs === null) throw new RangeError('DateTime outside of supported range');
const offsetNs = z ? 0 : ES.ParseOffsetString(offset);
if (!z && !offset) throw new RangeError('Temporal.Instant requires a time zone offset');
const offsetNs = z ? 0 : ES.ParseTimeZoneOffsetString(offset);
return epochNs.subtract(offsetNs);
},
RegulateISODateTime: (year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, overflow) => {
Expand Down Expand Up @@ -792,7 +793,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (timeZone) {
timeZone = ES.ToTemporalTimeZone(timeZone);
let offsetNs = 0;
if (offsetBehaviour === 'option') offsetNs = ES.ParseOffsetString(ES.ToString(offset));
if (offsetBehaviour === 'option') offsetNs = ES.ParseTimeZoneOffsetString(ES.ToString(offset));
const epochNanoseconds = ES.InterpretISODateTimeOffset(
year,
month,
Expand Down Expand Up @@ -1373,7 +1374,7 @@ export const ES = ObjectAssign({}, ES2020, {
matchMinute = true; // ISO strings may specify offset with less precision
}
let offsetNs = 0;
if (offsetBehaviour === 'option') offsetNs = ES.ParseOffsetString(offset);
if (offsetBehaviour === 'option') offsetNs = ES.ParseTimeZoneOffsetString(offset);
const disambiguation = ES.ToTemporalDisambiguation(options);
const offsetOpt = ES.ToTemporalOffset(options, 'reject');
const epochNanoseconds = ES.InterpretISODateTimeOffset(
Expand Down Expand Up @@ -2177,9 +2178,14 @@ export const ES = ObjectAssign({}, ES2020, {
return result;
},

ParseOffsetString: (string) => {
TestTimeZoneOffsetString: (string) => {
return OFFSET.test(String(string));
},
ParseTimeZoneOffsetString: (string) => {
const match = OFFSET.exec(String(string));
if (!match) return null;
if (!match) {
throw new RangeError(`invalid time zone offset: ${string}`);
}
const sign = match[1] === '-' || match[1] === '\u2212' ? -1 : +1;
const hours = +match[2];
const minutes = +(match[3] || 0);
Expand All @@ -2188,8 +2194,10 @@ export const ES = ObjectAssign({}, ES2020, {
return sign * (((hours * 60 + minutes) * 60 + seconds) * 1e9 + nanoseconds);
},
GetCanonicalTimeZoneIdentifier: (timeZoneIdentifier) => {
const offsetNs = ES.ParseOffsetString(timeZoneIdentifier);
if (offsetNs !== null) return ES.FormatTimeZoneOffsetString(offsetNs);
if (ES.TestTimeZoneOffsetString(timeZoneIdentifier)) {
const offsetNs = ES.ParseTimeZoneOffsetString(timeZoneIdentifier);
return ES.FormatTimeZoneOffsetString(offsetNs);
}
const formatter = getIntlDateTimeFormatEnUsForTimeZone(String(timeZoneIdentifier));
return formatter.resolvedOptions().timeZone;
},
Expand Down
13 changes: 7 additions & 6 deletions polyfill/lib/timezone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export class TimeZone {
instant = ES.ToTemporalInstant(instant);
const id = GetSlot(this, TIMEZONE_ID);

const offsetNs = ES.ParseOffsetString(id);
if (offsetNs !== null) return offsetNs;
if (ES.TestTimeZoneOffsetString(id)) {
return ES.ParseTimeZoneOffsetString(id);
}

return ES.GetIANATimeZoneOffsetNanoseconds(GetSlot(instant, EPOCHNANOSECONDS), id);
}
Expand All @@ -76,8 +77,7 @@ export class TimeZone {
const Instant = GetIntrinsic('%Temporal.Instant%');
const id = GetSlot(this, TIMEZONE_ID);

const offsetNs = ES.ParseOffsetString(id);
if (offsetNs !== null) {
if (ES.TestTimeZoneOffsetString(id)) {
const epochNs = ES.GetEpochFromISOParts(
GetSlot(dateTime, ISO_YEAR),
GetSlot(dateTime, ISO_MONTH),
Expand All @@ -90,6 +90,7 @@ export class TimeZone {
GetSlot(dateTime, ISO_NANOSECOND)
);
if (epochNs === null) throw new RangeError('DateTime outside of supported range');
const offsetNs = ES.ParseTimeZoneOffsetString(id);
return [new Instant(epochNs.minus(offsetNs))];
}

Expand All @@ -113,7 +114,7 @@ export class TimeZone {
const id = GetSlot(this, TIMEZONE_ID);

// Offset time zones or UTC have no transitions
if (ES.ParseOffsetString(id) !== null || id === 'UTC') {
if (ES.TestTimeZoneOffsetString(id) || id === 'UTC') {
return null;
}

Expand All @@ -128,7 +129,7 @@ export class TimeZone {
const id = GetSlot(this, TIMEZONE_ID);

// Offset time zones or UTC have no transitions
if (ES.ParseOffsetString(id) !== null || id === 'UTC') {
if (ES.TestTimeZoneOffsetString(id) || id === 'UTC') {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export class ZonedDateTime {
fields = ES.PrepareTemporalFields(fields, entries);
let { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } =
ES.InterpretTemporalDateTimeFields(calendar, fields, options);
const offsetNs = ES.ParseOffsetString(fields.offset);
const offsetNs = ES.ParseTimeZoneOffsetString(fields.offset);
const epochNanoseconds = ES.InterpretISODateTimeOffset(
year,
month,
Expand Down
47 changes: 47 additions & 0 deletions polyfill/test/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,15 @@ describe('Duration', () => {
throws(() => oneDay.add(hours24, { relativeTo: { year: 2019, month: 11 } }), TypeError);
throws(() => oneDay.add(hours24, { relativeTo: { year: 2019, day: 3 } }), TypeError);
});
it('throws with invalid offset in relativeTo', () => {
throws(
() =>
Temporal.Duration.from('P2D').add('P1M', {
relativeTo: { year: 2021, month: 11, day: 26, offset: '+088:00', timeZone: 'Europe/London' }
}),
RangeError
);
});
});
describe('Duration.subtract()', () => {
const duration = Duration.from({ days: 3, hours: 1, minutes: 10 });
Expand Down Expand Up @@ -928,6 +937,15 @@ describe('Duration', () => {
equal(zero2.microseconds, 0);
equal(zero2.nanoseconds, 0);
});
it('throws with invalid offset in relativeTo', () => {
throws(
() =>
Temporal.Duration.from('P2D').subtract('P1M', {
relativeTo: { year: 2021, month: 11, day: 26, offset: '+088:00', timeZone: 'Europe/London' }
}),
RangeError
);
});
});
describe('Duration.abs()', () => {
it('makes a copy of a positive duration', () => {
Expand Down Expand Up @@ -1512,6 +1530,16 @@ describe('Duration', () => {
equal(`${yearAndHalf.round({ relativeTo: '2020-01-01', smallestUnit: 'years' })}`, 'P1Y');
equal(`${yearAndHalf.round({ relativeTo: '2020-07-01', smallestUnit: 'years' })}`, 'P2Y');
});
it('throws with invalid offset in relativeTo', () => {
throws(
() =>
Temporal.Duration.from('P1M280D').round({
smallestUnit: 'month',
relativeTo: { year: 2021, month: 11, day: 26, offset: '+088:00', timeZone: 'Europe/London' }
}),
RangeError
);
});
});
describe('Duration.total()', () => {
const d = new Duration(5, 5, 5, 5, 5, 5, 5, 5, 5, 5);
Expand Down Expand Up @@ -1851,6 +1879,16 @@ describe('Duration', () => {
equal(d.total({ unit: 'microsecond', relativeTo }), d.total({ unit: 'microseconds', relativeTo }));
equal(d.total({ unit: 'nanosecond', relativeTo }), d.total({ unit: 'nanoseconds', relativeTo }));
});
it('throws with invalid offset in relativeTo', () => {
throws(
() =>
Temporal.Duration.from('P1M280D').total({
unit: 'month',
relativeTo: { year: 2021, month: 11, day: 26, offset: '+088:00', timeZone: 'Europe/London' }
}),
RangeError
);
});
});
describe('Duration.compare', () => {
describe('time units only', () => {
Expand Down Expand Up @@ -1947,6 +1985,15 @@ describe('Duration', () => {
it('does not lose precision when totaling everything down to nanoseconds', () => {
notEqual(Duration.compare({ days: 200 }, { days: 200, nanoseconds: 1 }), 0);
});
it('throws with invalid offset in relativeTo', () => {
throws(() => {
const d1 = Temporal.Duration.from('P1M280D');
const d2 = Temporal.Duration.from('P1M281D');
Temporal.Duration.compare(d1, d2, {
relativeTo: { year: 2021, month: 11, day: 26, offset: '+088:00', timeZone: 'Europe/London' }
});
}, RangeError);
});
});
});

Expand Down
71 changes: 71 additions & 0 deletions polyfill/test/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,23 @@ describe('ZonedDateTime', () => {
});
equal(`${zdt}`, '1976-11-18T00:00:00-10:30[-10:30]');
});
it('throws with invalid offset', () => {
const offsets = ['use', 'prefer', 'ignore', 'reject'];
offsets.forEach((offset) => {
throws(() => {
Temporal.ZonedDateTime.from(
{
year: 2021,
month: 11,
day: 26,
offset: '+099:00',
timeZone: 'Europe/London'
},
{ offset }
);
}, RangeError);
});
});
describe('Overflow option', () => {
const bad = { year: 2019, month: 1, day: 32, timeZone: lagos };
it('reject', () => throws(() => ZonedDateTime.from(bad, { overflow: 'reject' }), RangeError));
Expand Down Expand Up @@ -1025,6 +1042,14 @@ describe('ZonedDateTime', () => {
throws(() => zdt.with('12:00'), TypeError);
throws(() => zdt.with('invalid'), TypeError);
});
it('throws with invalid offset', () => {
const offsets = ['use', 'prefer', 'ignore', 'reject'];
offsets.forEach((offset) => {
throws(() => {
Temporal.ZonedDateTime.from('2022-11-26[Europe/London]').with({ offset: '+088:00' }, { offset });
}, RangeError);
});
});
});

describe('.withPlainTime manipulation', () => {
Expand Down Expand Up @@ -1617,6 +1642,18 @@ describe('ZonedDateTime', () => {
equal(`${dt1.until(dt2, { smallestUnit: 'years', roundingMode: 'halfExpand' })}`, 'P2Y');
equal(`${dt2.until(dt1, { smallestUnit: 'years', roundingMode: 'halfExpand' })}`, '-P1Y');
});
it('throws with invalid offset', () => {
throws(() => {
const zdt = ZonedDateTime.from('2019-01-01T00:00+00:00[UTC]');
zdt.until({
year: 2021,
month: 11,
day: 26,
offset: '+099:00',
timeZone: 'Europe/London'
});
}, RangeError);
});
});
describe('ZonedDateTime.since()', () => {
const zdt = ZonedDateTime.from('1976-11-18T15:23:30.123456789+01:00[Europe/Vienna]');
Expand Down Expand Up @@ -1948,6 +1985,18 @@ describe('ZonedDateTime', () => {
equal(`${dt2.since(dt1, { smallestUnit: 'years', roundingMode: 'halfExpand' })}`, 'P1Y');
equal(`${dt1.since(dt2, { smallestUnit: 'years', roundingMode: 'halfExpand' })}`, '-P2Y');
});
it('throws with invalid offset', () => {
throws(() => {
const zdt = ZonedDateTime.from('2019-01-01T00:00+00:00[UTC]');
zdt.since({
year: 2021,
month: 11,
day: 26,
offset: '+099:00',
timeZone: 'Europe/London'
});
}, RangeError);
});
});

describe('ZonedDateTime.round()', () => {
Expand Down Expand Up @@ -2188,6 +2237,17 @@ describe('ZonedDateTime', () => {
TypeError
);
});
it('throws with invalid offset', () => {
throws(() => {
zdt.equals({
year: 2021,
month: 11,
day: 26,
offset: '+099:00',
timeZone: 'Europe/London'
});
}, RangeError);
});
});
describe('ZonedDateTime.toString()', () => {
const zdt1 = ZonedDateTime.from('1976-11-18T15:23+01:00[Europe/Vienna]');
Expand Down Expand Up @@ -2894,6 +2954,17 @@ describe('ZonedDateTime', () => {
equal(ZonedDateTime.compare(clockBefore, clockAfter), 1);
equal(Temporal.PlainDateTime.compare(clockBefore.toPlainDateTime(), clockAfter.toPlainDateTime()), -1);
});
it('throws with invalid offset', () => {
throws(() => {
Temporal.ZonedDateTime.compare(zdt1, {
year: 2021,
month: 11,
day: 26,
offset: '+099:00',
timeZone: 'Europe/London'
});
}, RangeError);
});
});
});

Expand Down