Proposal-temporal: How to do "partial cloning" of some units but not others?

Created on 13 May 2020  路  6Comments  路  Source: tc39/proposal-temporal

A common date/time use-case is to change some units in a Date but not others. For example:

  • Remove minutes/seconds from a time
  • Noon on a particular day
  • 3rd day of next month
  • My birthday in 2030
  • Same date and time, but in February (and use the last day if date doesn't exist in Feb)
  • Same date and time, but April (and throw an error if date === 31)
  • Last day of the month
  • On the 18th of that month at 8:00PM

How would you do these cases with Temporal?

Doing this with Date today has a few problems:

  • It's extremely verbose-- it's like 200 characters to clone a full Date if you only want to change one field because you have to call 7 different Date methods to change one unit value.
  • Day-of-month values can overflow, yielding unexpected behavior. For example, if I have March 31 and I want the same day of the month in February by calling Date.setMonth(3, 31) then I'll end up with May 1 not April 30 as expected.
  • (our old friend DST) If you're changing the date (but not the time) and if the current Date is the instant that DST ends, then you'll end up with a one-hour-earlier local time in the new Date, because of this behavior (from https://tc39.es/ecma262/#sec-local-time-zone-adjustment):

When tlocal represents local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change) or skipped local time at a positive time zone transitions (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), tlocal must be interpreted using the time zone offset before the transition.

IMHO the ideal API (on DateTime, Absolute, Time, etc) would be similar to constructing a Duration: you'd pass a parameter containing units e.g. {day: 20, month: 7} or {minutes: 0, seconds: 0}) and optionally indicate how you'd want to handle day-of-month overflow.

Does such an API exist already? I didn't see one in the docs but I know docs might be out of date.

documentation

Most helpful comment

Spread doesn't work, no, but have you seen with()?

const dt = new Temporal.Date(2020, 3, 14)
const dt2 = dt1.with({ day: 1 }); // 2020-03-01

All 6 comments

Here's another possible zero-work API. Is object spread supposed to work?

const dt = new Temporal.Date(2020, 3, 14)
const dt2 = new Temporal.Date({...someDateTime, day: 1});

Spread doesn't work, no, but have you seen with()?

const dt = new Temporal.Date(2020, 3, 14)
const dt2 = dt1.with({ day: 1 }); // 2020-03-01

@justingrant @Ms2ger also, getFields should work with object spread and any similar operations.

const dt = new Temporal.Date(2020, 3, 14)
const dt2 = Temporal.Date.from({...dt.getFields(), day: 1});

I think we should add these use cases to the cookbook but I'll take an initial stab at them here:

Remove minutes/seconds from a time

time.with({minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0})
// or
Temporal.Time.from({ hour: time.hour })

Noon on a particular day

date.withTime(Temporal.Time.from({ hour: 12 })
// or
date.withTime(Temporal.Time.from('12:00'))

3rd day of next month

const date = Temporal.now.date();
date.with({ month: date.month + 1, day: 3 }, { disambiguation: 'balance' })
// or
date.plus({ months: 1 }).with({ day: 3 })

My birthday in 2030

const birthday = Temporal.MonthDay.from('12-15');
birthday.withYear(2030)

Same date and time, but in February (and use the last day if date doesn't exist in Feb)

dateTime.with({ month: 2 })
// could add { disambiguation: 'constrain' } for clarity, but it's the default

Same date and time, but April (and throw an error if date === 31)

dateTime.with({ month: 4 }, { disambiguation: 'reject' })
// I did have to double-check this one because I wasn't sure if it'd work! but it does

Last day of the month

Temporal.now.date().with({ day: 31 })
// here again { disambiguation: 'constrain' } is implied

On the 18th of that month at 8:00PM

Temporal.now.date().with({ day: 18 }).withTime(Temporal.Time.from('20:00'))

Ahhh, with was what I was looking for. That API looks great-- nice DX.

I'll get these examples added to the cookbook, then.

Was this page helpful?
0 / 5 - 0 ratings