Skip to content

Commit

Permalink
Normative?: Use normalized time duration in operations
Browse files Browse the repository at this point in the history
This introduces Normalized Time Duration Records, which we use to
encapsulate 96-bit integer operations on duration times. (In the reference
polyfill, the TimeDuration class fulfills the same purpose.) These
operations are specified naively in the mathematical value domain, but can
be changed in a later editorial commit to correspond to how
implementations would write 64+32 bit operations, if we so desire. (The
results must be exactly the same, so that can be decided later, outside of
a TC39 plenary.)

This commit also replaces TotalDurationNanoseconds with
NormalizeTimeDuration, and NanosecondsToDays with
NormalizedTimeDurationToDays. Several operations are changed to return a
Normalized Duration Record, which is a Normalized Time Duration record
combined with a Date Duration Record.

Having already limited time units of durations in the previous commit,
this does not affect any results, nor any existing tests in test262. But I
can't prove conclusively that there isn't some edge case somewhere that
makes this change observable.
  • Loading branch information
ptomato committed Jun 20, 2023
1 parent fdbb907 commit 14b39fa
Show file tree
Hide file tree
Showing 17 changed files with 1,258 additions and 964 deletions.
7 changes: 4 additions & 3 deletions polyfill/lib/calendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
HasSlot,
SetSlot
} from './slots.mjs';
import { TimeDuration } from './timeduration.mjs';

const ArrayIncludes = Array.prototype.includes;
const ArrayPrototypePush = Array.prototype.push;
Expand Down Expand Up @@ -121,16 +122,16 @@ export class Calendar {
duration = ES.ToTemporalDuration(duration);
options = ES.GetOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
const { days } = ES.BalanceTimeDuration(
const norm = TimeDuration.normalize(
GetSlot(duration, DAYS),
GetSlot(duration, HOURS),
GetSlot(duration, MINUTES),
GetSlot(duration, SECONDS),
GetSlot(duration, MILLISECONDS),
GetSlot(duration, MICROSECONDS),
GetSlot(duration, NANOSECONDS),
'day'
GetSlot(duration, NANOSECONDS)
);
const { days } = ES.BalanceTimeDuration(norm, 'day');
const id = GetSlot(this, CALENDAR_ID);
return impl[id].dateAdd(
date,
Expand Down
187 changes: 78 additions & 109 deletions polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import {
NANOSECONDS,
CALENDAR,
INSTANT,
EPOCHNANOSECONDS,
CreateSlots,
GetSlot,
SetSlot
} from './slots.mjs';
import { TimeDuration } from './timeduration.mjs';

const ObjectCreate = Object.create;

Expand Down Expand Up @@ -62,6 +64,7 @@ export class Duration {
SetSlot(this, NANOSECONDS, nanoseconds);

if (typeof __debug__ !== 'undefined' && __debug__) {
const normSeconds = TimeDuration.normalize(0, 0, 0, seconds, milliseconds, microseconds, nanoseconds);
Object.defineProperty(this, '_repr_', {
value: `${this[Symbol.toStringTag]} <${ES.TemporalDurationToString(
years,
Expand All @@ -70,10 +73,7 @@ export class Duration {
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds
normSeconds
)}>`,
writable: false,
enumerable: false,
Expand Down Expand Up @@ -360,67 +360,46 @@ export class Duration {
plainRelativeTo,
calendarRec
));
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
ES.RoundDuration(
let norm = TimeDuration.normalize(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
({ years, months, weeks, days, norm } = ES.RoundDuration(
years,
months,
weeks,
days,
norm,
roundingIncrement,
smallestUnit,
roundingMode,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
timeZoneRec
));
if (zonedRelativeTo) {
({ years, months, weeks, days, norm } = ES.AdjustRoundedDurationDays(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
norm,
roundingIncrement,
smallestUnit,
roundingMode,
plainRelativeTo,
calendarRec,
zonedRelativeTo,
calendarRec,
timeZoneRec
));
if (zonedRelativeTo) {
({ 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,
zonedRelativeTo,
calendarRec,
timeZoneRec
));
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
norm,
largestUnit,
zonedRelativeTo,
timeZoneRec
));
} else {
const daysDuration = TimeDuration.normalize(days, 0, 0, 0, 0, 0, 0);
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
norm.add(daysDuration),
largestUnit
));
}
Expand Down Expand Up @@ -501,6 +480,7 @@ export class Duration {
plainRelativeTo,
calendarRec
));
let norm;
// If the unit we're totalling is smaller than `days`, convert days down to that unit.
if (zonedRelativeTo) {
const intermediate = ES.MoveRelativeZonedDateTime(
Expand All @@ -512,42 +492,35 @@ export class Duration {
weeks,
0
);
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative(
norm = TimeDuration.normalize(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
const endNs = ES.AddZonedDateTime(
GetSlot(intermediate, INSTANT),
timeZoneRec,
GetSlot(intermediate, CALENDAR),
0,
0,
0,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
unit,
intermediate,
timeZoneRec
));
norm
);
const startNs = GetSlot(intermediate, EPOCHNANOSECONDS);
norm = TimeDuration.fromEpochNsDiff(endNs, startNs);
if (unit === 'year' || unit === 'month' || unit === 'week' || unit === 'day') {
({ days, norm } = ES.NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec));
} else {
days = 0;
}
} else {
({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration(
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
unit
));
norm = TimeDuration.normalize(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
days = 0;
}
// Finally, truncate to the correct unit and calculate remainder
const { total } = ES.RoundDuration(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
norm,
1,
unit,
'trunc',
Expand All @@ -569,48 +542,46 @@ export class Duration {
}
const { precision, unit, increment } = ES.ToSecondsStringPrecisionRecord(smallestUnit, digits);

const { seconds, milliseconds, microseconds, nanoseconds } = ES.RoundDuration(
0,
0,
0,
let norm = TimeDuration.normalize(
0,
0,
0,
GetSlot(this, SECONDS),
GetSlot(this, MILLISECONDS),
GetSlot(this, MICROSECONDS),
GetSlot(this, NANOSECONDS),
increment,
unit,
roundingMode
GetSlot(this, NANOSECONDS)
);
({ norm } = ES.RoundDuration(0, 0, 0, 0, norm, increment, unit, roundingMode));
return ES.TemporalDurationToString(
GetSlot(this, YEARS),
GetSlot(this, MONTHS),
GetSlot(this, WEEKS),
GetSlot(this, DAYS),
GetSlot(this, HOURS),
GetSlot(this, MINUTES),
seconds,
milliseconds,
microseconds,
nanoseconds,
norm,
precision
);
}
toJSON() {
if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver');
const norm = TimeDuration.normalize(
0,
0,
0,
GetSlot(this, SECONDS),
GetSlot(this, MILLISECONDS),
GetSlot(this, MICROSECONDS),
GetSlot(this, NANOSECONDS)
);
return ES.TemporalDurationToString(
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)
norm
);
}
toLocaleString(locales = undefined, options = undefined) {
Expand All @@ -619,17 +590,23 @@ export class Duration {
return new Intl.DurationFormat(locales, options).format(this);
}
console.warn('Temporal.Duration.prototype.toLocaleString() requires Intl.DurationFormat.');
const norm = TimeDuration.normalize(
0,
0,
0,
GetSlot(this, SECONDS),
GetSlot(this, MILLISECONDS),
GetSlot(this, MICROSECONDS),
GetSlot(this, NANOSECONDS)
);
return ES.TemporalDurationToString(
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)
norm
);
}
valueOf() {
Expand Down Expand Up @@ -705,6 +682,8 @@ export class Duration {
const instant = GetSlot(relativeTo, INSTANT);
const precalculatedDateTime = ES.GetPlainDateTimeFor(timeZoneRec, instant, calendarRec.receiver);

const norm1 = TimeDuration.normalize(0, h1, min1, s1, ms1, µs1, ns1);
const norm2 = TimeDuration.normalize(0, h2, min2, s2, ms2, µs2, ns2);
const after1 = ES.AddZonedDateTime(
instant,
timeZoneRec,
Expand All @@ -713,12 +692,7 @@ export class Duration {
mon1,
w1,
d1,
h1,
min1,
s1,
ms1,
µs1,
ns1,
norm1,
precalculatedDateTime
);
const after2 = ES.AddZonedDateTime(
Expand All @@ -729,12 +703,7 @@ export class Duration {
mon2,
w2,
d2,
h2,
min2,
s2,
ms2,
µs2,
ns2,
norm2,
precalculatedDateTime
);
return ES.ComparisonResult(after1.minus(after2).toJSNumber());
Expand All @@ -745,9 +714,9 @@ export class Duration {
({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', relativeTo, calendarRec));
({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', relativeTo, calendarRec));
}
ns1 = ES.TotalDurationNanoseconds(d1, h1, min1, s1, ms1, µs1, ns1);
ns2 = ES.TotalDurationNanoseconds(d2, h2, min2, s2, ms2, µs2, ns2);
return ES.ComparisonResult(ns1.minus(ns2).toJSNumber());
const norm1 = TimeDuration.normalize(d1, h1, min1, s1, ms1, µs1, ns1);
const norm2 = TimeDuration.normalize(d2, h2, min2, s2, ms2, µs2, ns2);
return norm1.cmp(norm2);
}
}

Expand Down
Loading

0 comments on commit 14b39fa

Please sign in to comment.