javascriptdatetimecalendardurationecmascript-temporal

Why does arithmetic on `Temporal.Duration` instances require a `relativeTo` reference calendar date?


The proposed JavaScript Temporal date/time API casually mentions “Interpreting years, months, or weeks requires a reference point” (source), but I fail to understand what is meant by this.

More specifically, the following is not possible:

let oneWeek = Temporal.Duration.from({ weeks: 1 });
console.log(`one week is ${oneWeek.total('day')} days`);

It will result in the following error:

RangeError: a starting point is required for balancing calendar units

This can be resolved by bloating my code with a relativeTo parameter to the total() call, as follows:

console.log(`one week is ${oneWeek.total({ unit: 'day', relativeTo: Temporal.Now.plainDateISO() })} days`);

But could anyone elaborate on why this is necessary? As far as I can tell a week has always been and will always be seven days…?


Solution

  • As far as I can tell a week has always been and will always be seven days

    Even around a daylight saving time change? You know, those Sundays where you change the clock back or forward by one hour? If you measure a duration from, say, Wednesday to the next Wednesday, then this “one week” could be exactly 7 days long, or 6 days and 23 hours, or 7 days and 1 hour.

    See the documentation on Balancing Relative to a Reference Point:

    Balancing that includes days, weeks, months, and years is more complicated because those units can be different lengths. In the default ISO calendar, a year can be 365 or 366 days, and a month can be 28, 29, 30, or 31 days. In other calendars, years aren’t always 12 months long and weeks aren’t always 7 days. Finally, in time zones that use Daylight Saving Time (DST) days are not always 24 hours long.

    This is also referring to leap years.

    There’s also discussion in issue #857:

    Specifically, the following time scale unit conversions are contextual (the “conversion boundaries”):

    • year ↔ day (365, 366 days)
    • month ↔ day (28, 29, 30, 31 days)
    • minute ↔ second (59, 60, 61 seconds)

    That last one is referring to leap seconds.

    And of course, the Temporal API has to account for what happened in September, 1752. Related: Get number of days in a specific month that are in a date range.

    The point is that the answer to the question of how long some unit of time is, will change depending on context. This isn’t just about time zones; different calendars have all sorts of anomalies.

    Sure, the proleptic Gregorian calendar used in browsers might not be affected by the year 1752, but that requires knowledge about which calendar to use. You supply this information with relativeTo; it’s contained within Temporal.Now.plainDateISO(). Now it’s clear that an ISO week can be used which will always have 7 days. But without this information, it’s impossible to tell exactly, and a general date and time API cannot be sure that an ISO week is what you actually meant.