Search code examples
javascripttemporal

Uncaught RangeError: Duration field days not supported by Temporal.Instant (adding 1 day to a temporal instant without using magic numbers)


Background about Temporal

I'm trying to add 1 day to a Temporal.Instant, to use as the exp value for a JWT. The most obvious-seeming way to do this:

Temporal.Now.instant().add({ days: 1 }).epochSeconds

Throws Uncaught RangeError: Duration field days not supported by Temporal.Instant. Try Temporal.ZonedDateTime instead. This is presumably because Temporal.Instant doesn't contain any calendar info, and the concept of "1 day" could vary depending on things like DST and leap seconds.

The obvious alternative is to use 24 hours:

Temporal.Now.instant().add({ hours: 24 }).epochSeconds

Not bad, but not 100% satisfactory, as now we're left with 24 as a magic number.

To get around this, we can use:

Temporal.Now.instant().add({ hours: Temporal.Duration.from({ days: 1 }).total({ unit: 'hours' }) }).epochSeconds

But that's pretty verbose and hard to visually parse, when all we really wanted is the concept of "one day from now in seconds since the epoch".

We could also use the option suggested by the error message and use a ZonedDateTime, but time zone info isn't relevant to the use case, plus it's better for the result to be a consistent amount of time in the future, regardless of DST etc.

Is there a more concise/idiomatic way of doing this without using any magic numbers? Or do I just have to suck it up and use hours: 24?


Solution

  • In the end, you have to choose a policy for determining the expiration timestamp, and the policy that it seems you want is "expire after 24 hours" — this is factually the same as "expire after 1 day except on days that last shorter or longer than 24 hours". "1 day" is just as magic of a number as "24 hours." 😄

    To make your code more readable and have the expiration policy only in one place, you might store a Temporal.Duration in a constant:

    const TOKEN_VALIDITY_DURATION = Temporal.Duration.from({ hours: 24 });
    Temporal.Now.instant().add(TOKEN_VALIDITY_DURATION).epochSeconds;
    

    If you really don't want the number 24 in your code, here is a slightly shorter version of the conversion ignoring DST, where you could use round() instead of total() to avoid constructing a third duration:

    Temporal.Duration.from({ days: 1 }).round({ largestUnit: 'hours' })