Temporal Cookbook

Overview

Running the cookbook files

Running cookbook files: see instructions in ../polyfill/README.md

Frequently Asked Questions

These are some of the most common tasks that people ask questions about on StackOverflow with legacy Date. Here's how they would look using Temporal.

Current date and time

How to get the current date and time in the local time zone?

/**
 * Get the current date in JavaScript
 * This is a popular question on Stack Overflow for dates in JS
 * https://stackoverflow.com/questions/1531093/how-do-i-get-the-current-date-in-javascript
 *
 */

const date = Temporal.now.dateISO(); // Gets the current date
date.toString(); // returns the date in ISO 8601 date format

// If you additionally want the time:
Temporal.now.dateTimeISO().toString(); // date and time in ISO 8601 format

Note that if you just want the date and not the time, you should use Temporal.Date. If you want both, use Temporal.DateTime.

Unix timestamp

How to get a Unix timestamp?

/**
 * Get a (Unix) timestamp in JavaScript
 * This is the No.1 voted question on Stack Overflow for dates in JS
 * https://stackoverflow.com/questions/221294/how-do-you-get-a-timestamp-in-javascript
 *
 */

const timeStamp = Temporal.now.instant();

// Timestamp in Milliseconds
timeStamp.epochMilliseconds;
// Timestamp in Seconds
timeStamp.epochSeconds;

Converting between Temporal types and legacy Date

Instant from legacy Date

Map a legacy ECMAScript Date instance into a Temporal.Instant instance corresponding to the same instant in exact time.

const legacyDate = new Date('1970-01-01T00:00:01Z');
const instant = legacyDate.toTemporalInstant();

assert.equal(instant.toString(), '1970-01-01T00:00:01Z');

Construction

Time zone object from name

Temporal.TimeZone.from() can convert an IANA time zone name into a Temporal.TimeZone object.

// Construct a Temporal.TimeZone from an IANA name:
const tz = Temporal.TimeZone.from('Europe/London');

// Cast the timezone back to an IANA name, two ways:
tz.toString(); // Europe/London
tz.id; // Europe/London

Calendar input element

You can use Temporal objects to set properties on a calendar control. Here is an example using an HTML <input type="date"> element with any day beyond “today” disabled and not selectable.

const datePicker = document.getElementById('calendar-input');
const browserCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
const today = Temporal.now.date(browserCalendar);
datePicker.max = today;
datePicker.value = today;

Converting between types

Noon on a particular date

An example of combining a calendar date (Temporal.Date) and a wall-clock time (Temporal.Time) into a Temporal.DateTime.

const date = Temporal.Date.from('2020-05-14');

const noonOnDate = date.toDateTime(Temporal.Time.from({ hour: 12 }));

assert(noonOnDate instanceof Temporal.DateTime);
assert.equal(noonOnDate.toString(), '2020-05-14T12:00:00');

Birthday in 2030

An example of combining a day on the calendar (Temporal.MonthDay) and a year into a Temporal.Date.

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

const birthdayIn2030 = birthday.toDateInYear(2030);
birthdayIn2030.dayOfWeek; // => 7

assert(birthdayIn2030 instanceof Temporal.Date);
assert.equal(birthdayIn2030.toString(), '2030-12-15');

Serialization

Zoned instant from instant and time zone

Use the optional parameter of Temporal.Instant.prototype.toString() to map an exact-time Temporal.Instant instance and a time zone name, into a string serialization of the wall-clock time in that time zone corresponding to the exact time.

Without the parameter, Temporal.Instant.prototype.toString() gives a serialization in UTC time. Using the parameter is useful if you need your serialized strings to be in a specific time zone.

const instant = Temporal.Instant.from('2020-01-03T10:41:51Z');

const result = instant.toString('Europe/Paris');

assert.equal(result, '2020-01-03T11:41:51+01:00[Europe/Paris]');
assert(instant.equals(Temporal.Instant.from(result)));

// With an offset:

const result2 = instant.toString('-07:00');

assert.equal(result2, '2020-01-03T03:41:51-07:00');

// With a Temporal.TimeZone object:

const timeZone = Temporal.TimeZone.from('Asia/Seoul');
const result3 = instant.toString(timeZone);

assert.equal(result3, '2020-01-03T19:41:51+09:00[Asia/Seoul]');

Sorting

Each Temporal type has a compare() static method, which can be passed to Array.prototype.sort() as the compare function in order to sort an array of Temporal types.

Sort DateTimes

Sort a list of Temporal.DateTimes, for example in order to get a conference schedule in the correct order. Sorting other Temporal types would work exactly the same way as this.

/**
 * getSortedLocalDateTimes will sort an array of zoneless Temporal.DateTime instances by the
 * corresponding local date and time of day (e.g., for building a conference schedule).
 *
 *
 * @param {Temporal.DateTime[]} dateTimes - This is a DateTime instance
 * @param {boolean} [reverse=false] - Return in reversed order
 * @returns {Temporal.DateTime[]} the array from dateTimes, sorted
 */
function getSortedLocalDateTimes(dateTimes, reverse = false) {
  let newDateTimes = Array.from(dateTimes).sort(Temporal.DateTime.compare);

  return reverse ? newDateTimes.reverse() : newDateTimes;
}

// Sorting some conferences without timezones for example vue.js Amsterdam 2020
let a = Temporal.DateTime.from({
  year: 2020,
  day: 20,
  month: 2,
  hour: 8,
  minute: 45
}); // Introduction
let b = Temporal.DateTime.from({
  year: 2020,
  day: 21,
  month: 2,
  hour: 13,
  minute: 10
}); // Lunch Break
let c = Temporal.DateTime.from({
  year: 2020,
  day: 20,
  month: 2,
  hour: 15,
  minute: 30
}); // Coffee Break
const results = getSortedLocalDateTimes([a, b, c]);
assert.deepEqual(
  results.map((x) => x.toString()),
  ['2020-02-20T08:45:00', '2020-02-20T15:30:00', '2020-02-21T13:10:00']
);

Sort ISO date/time strings

Sort a list of ISO 8601 date/time strings, for example to place log entries in order.

/**
 * sortInstantStrings will sort an array of strings (each of which is
 * parseable as a Temporal.Instant and may or may not include an IANA time
 * zone name) by the corresponding exact time (e.g., for presenting global
 * log events sequentially).
 *
 * @param {string[]} strings - an array of ISO strings
 * @param {boolean} [reverse=false] - ascending or descending order
 * @returns {string[]} the array from strings, sorted
 */
function sortInstantStrings(strings, reverse = false) {
  const sortedInstants = strings
    .map((v) => [v, Temporal.Instant.from(v)])
    .sort(([, i1], [, i2]) => Temporal.Instant.compare(i1, i2))
    .map(([str]) => str);

  return reverse ? sortedInstants.reverse() : sortedInstants;
}

// simple string comparison order would not be correct here:
const a = '2020-01-23T17:04:36.491865121-08:00';
const b = '2020-02-10T17:04:36.491865121-08:00';
const c = '2020-04-01T05:01:00-05:00[America/New_York]';
const d = '2020-04-01T10:00:00+01:00[Europe/London]';
const e = '2020-04-01T11:02:00+02:00[Europe/Berlin]';

const results = sortInstantStrings([a, b, c, d, e]);

// results will have correct order
assert.deepEqual(results, [
  '2020-01-23T17:04:36.491865121-08:00',
  '2020-02-10T17:04:36.491865121-08:00',
  '2020-04-01T10:00:00+01:00[Europe/London]',
  '2020-04-01T05:01:00-05:00[America/New_York]',
  '2020-04-01T11:02:00+02:00[Europe/Berlin]'
]);

Rounding

Round a time down to whole hours

Use the round() method of each Temporal type if you want to round the time fields. Here's an example of rounding a time down to the previously occurring whole hour:

const time = Temporal.Time.from('12:38:28.138818731');

const wholeHour = time.round({ smallestUnit: 'hour', roundingMode: 'floor' });

assert.equal(wholeHour.toString(), '12:00:00');

Round a date to the nearest start of the month

Rounding is only defined for time fields. Rounding a date field can be ambiguous, so date-only types such as Temporal.Date don't have a round() method. If you need to round a date to the nearest month, for example, then you must explicitly pick what kind of rounding you want. Here is an example of rounding to the nearest start of a month, rounding up in case of a tie:

const date = Temporal.Date.from('2018-09-16');

let { year, month } = date;
if ((date.day - 1) / date.daysInMonth >= 0.5) month++;
if (month > 12) {
  month %= 12;
  year++;
}
const nearestMonth = Temporal.Date.from({ year, month, day: 1 });

assert.equal(nearestMonth.toString(), '2018-10-01');

See also Push back a launch date for an easier way to round up unconditionally to the next start of a month.

Time zone conversion

Preserving local time

Map a zoneless date and time of day into a Temporal.Instant instance at which the local date and time of day in a specified time zone matches it. This is easily done with dateTime.toInstant(), but here is an example of implementing different disambiguation behaviors than the 'compatible', 'earlier', 'later', and 'reject' ones built in to Temporal.

/**
 * Get an exact time corresponding with a calendar date / wall-clock time in a
 * particular time zone, the same as Temporal.TimeZone.getInstantFor() or
 * Temporal.DateTime.toInstant(), but with more disambiguation options.
 *
 * As well as the default Temporal disambiguation options 'compatible',
 * 'earlier', 'later', and 'reject', there are additional options possible:
 *
 * - 'clipEarlier': Equivalent to 'earlier' when turning the clock back, and
 *   when setting the clock forward returns the time just before the clock
 *   changes.
 * - 'clipLater': Equivalent to 'later' when turning the clock back, and when
 *   setting the clock forward returns the exact time of the clock change.
 *
 * @param {Temporal.DateTime} dateTime - Calendar date and wall-clock time to
 *   convert
 * @param {Temporal.TimeZone} timeZone - Time zone in which to consider the
 *   wall-clock time
 * @param {string} [disambiguation='earlier'] - Disambiguation mode, see description.
 * @returns {Temporal.Instant} Absolute time in timeZone at the time of the
 *   calendar date and wall-clock time from dateTime
 */
function getInstantWithLocalTimeInZone(dateTime, timeZone, disambiguation = 'earlier') {
  // Handle the built-in modes first
  if (['compatible', 'earlier', 'later', 'reject'].includes(disambiguation)) {
    return timeZone.getInstantFor(dateTime, { disambiguation });
  }

  const possible = timeZone.getPossibleInstantsFor(dateTime);

  // Return only possibility if no disambiguation needed
  if (possible.length === 1) return possible[0];

  switch (disambiguation) {
    case 'clipEarlier':
      if (possible.length === 0) {
        const before = dateTime.toInstant(timeZone, { disambiguation: 'earlier' });
        return timeZone.getNextTransition(before).subtract({ nanoseconds: 1 });
      }
      return possible[0];
    case 'clipLater':
      if (possible.length === 0) {
        const before = dateTime.toInstant(timeZone, { disambiguation: 'earlier' });
        return timeZone.getNextTransition(before);
      }
      return possible[possible.length - 1];
  }
  throw new RangeError(`invalid disambiguation ${disambiguation}`);
}

const germany = Temporal.TimeZone.from('Europe/Berlin');
const nonexistentGermanWallTime = Temporal.DateTime.from('2019-03-31T02:45');

const germanResults = {
  earlier: /*     */ '2019-03-31T01:45:00+01:00[Europe/Berlin]',
  later: /*       */ '2019-03-31T03:45:00+02:00[Europe/Berlin]',
  compatible: /*  */ '2019-03-31T03:45:00+02:00[Europe/Berlin]',
  clipEarlier: /* */ '2019-03-31T01:59:59.999999999+01:00[Europe/Berlin]',
  clipLater: /*   */ '2019-03-31T03:00:00+02:00[Europe/Berlin]'
};
for (const [disambiguation, result] of Object.entries(germanResults)) {
  assert.equal(
    getInstantWithLocalTimeInZone(nonexistentGermanWallTime, germany, disambiguation).toString(germany),
    result
  );
}

const brazilEast = Temporal.TimeZone.from('America/Sao_Paulo');
const doubleEasternBrazilianWallTime = Temporal.DateTime.from('2019-02-16T23:45');

const brazilianResults = {
  earlier: /*     */ '2019-02-16T23:45:00-02:00[America/Sao_Paulo]',
  later: /*       */ '2019-02-16T23:45:00-03:00[America/Sao_Paulo]',
  compatible: /*  */ '2019-02-16T23:45:00-02:00[America/Sao_Paulo]',
  clipEarlier: /* */ '2019-02-16T23:45:00-02:00[America/Sao_Paulo]',
  clipLater: /*   */ '2019-02-16T23:45:00-03:00[America/Sao_Paulo]'
};
for (const [disambiguation, result] of Object.entries(brazilianResults)) {
  assert.equal(
    getInstantWithLocalTimeInZone(doubleEasternBrazilianWallTime, brazilEast, disambiguation).toString(brazilEast),
    result
  );
}

Preserving exact time

Map a zoned date and time of day into a string serialization of the local time in a target time zone at the corresponding exact time. This could be used when converting user-input date-time values between time zones.

/**
 * Takes a local date and time in one time zone, and serializes it to a string
 * expressing the local date and time in another time zone at the same exact
 * time.
 *
 * If `sourceDateTime` doesn't exist in `sourceTimeZone`, or exists twice, then
 * an error will be thrown by default.
 * Usually this is what you want. (FIXME: but is it?)
 * Use `sourceDisambiguationPolicy` to change this behaviour.
 *
 * @param {Temporal.DateTime} sourceDateTime - The local date and time
 * @param {Temporal.TimeZone} sourceTimeZone - The time zone for
 *  `sourceDateTime`
 * @param {Temporal.TimeZone} targetTimeZone - The time zone for the
 *  return value
 * @param {string} [sourceDisambiguationPolicy=reject] - what to do when
 *  `sourceDateTime` is ambiguous
 * @returns {string} String indicating the time with time zone designation
 */
function getParseableZonedStringWithLocalTimeInOtherZone(
  sourceDateTime,
  sourceTimeZone,
  targetTimeZone,
  sourceDisambiguationPolicy = 'reject'
) {
  let instant = sourceDateTime.toInstant(sourceTimeZone, { disambiguation: sourceDisambiguationPolicy });
  return instant.toString(targetTimeZone);
}

const result = getParseableZonedStringWithLocalTimeInOtherZone(
  Temporal.DateTime.from('2020-01-09T00:00'),
  Temporal.TimeZone.from('America/Chicago'),
  Temporal.TimeZone.from('America/Los_Angeles')
);
// On this date, when it's midnight in Chicago, it's 10 PM the previous night in LA
assert.equal(result, '2020-01-08T22:00:00-08:00[America/Los_Angeles]');

Here is another example similar to the previous one, using the time zone for future events. The times and locations of a series of future meetings are stored as a pair of strings: one for the calendar date and wall-clock time, and one for the time zone. They cannot be stored as an exact time because between now and the time when the event happens, the time zone rules for daylight saving time could change — for example, Brazil abolished daylight saving time in 2019 — but the meeting would still be held at the same wall-clock time on that date. So if the time zone rules changed, the event's exact time would change.

This example calculates the starting times of all the Ecma TC39 meetings in 2019, in local time in Tokyo.

// Dates of the 2019 TC39 meetings:
const tc39meetings = [
  {
    dateTime: '2019-01-28T10:00',
    timeZone: 'America/Phoenix'
  },
  {
    dateTime: '2019-03-26T10:00',
    timeZone: 'America/New_York'
  },
  {
    dateTime: '2019-06-04T10:00',
    timeZone: 'Europe/Berlin'
  },
  {
    dateTime: '2019-07-23T10:00',
    timeZone: 'America/Los_Angeles'
  },
  {
    dateTime: '2019-10-01T10:00',
    timeZone: 'America/New_York'
  },
  {
    dateTime: '2019-12-03T10:00',
    timeZone: 'America/Los_Angeles'
  }
];

// To follow the meetings remotely from Tokyo, calculate the times you would
// need to join:
const localTimeZone = Temporal.TimeZone.from('Asia/Tokyo');
const localTimes = tc39meetings.map(({ dateTime, timeZone }) => {
  return Temporal.DateTime.from(dateTime)
    .toInstant(timeZone, { disambiguation: 'reject' })
    .toDateTimeISO(localTimeZone);
});

assert.deepEqual(
  localTimes.map((dt) => dt.toString()),
  [
    '2019-01-29T02:00:00',
    '2019-03-26T23:00:00',
    '2019-06-04T17:00:00',
    '2019-07-24T02:00:00',
    '2019-10-01T23:00:00',
    '2019-12-04T03:00:00'
  ]
);

Daily occurrence in local time

Similar to the previous recipe, calculate the exact times of a daily occurrence that happens at a particular local time in a particular time zone.

/**
 * Returns an iterator of the exact times corresponding to a daily occurrence
 * starting on a particular date, and happening at a particular local time in a
 * particular time zone.
 *
 * @param {Temporal.Date} startDate - Starting date
 * @param {Temporal.Time} time - Local time that event occurs at
 * @param {Temporal.TimeZone} timeZone - Time zone in which event is defined
 */
function* calculateDailyOccurrence(startDate, time, timeZone) {
  for (let date = startDate; ; date = date.add({ days: 1 })) {
    yield date.toDateTime(time).toInstant(timeZone);
  }
}

// Daily meeting at 8 AM California time
const startDate = Temporal.Date.from('2017-03-10');
const time = Temporal.Time.from('08:00');
const timeZone = Temporal.TimeZone.from('America/Los_Angeles');
const iter = calculateDailyOccurrence(startDate, time, timeZone);

assert(iter.next().value.toString(), '2017-03-10T16:00:00.000000000Z');
assert(iter.next().value.toString(), '2017-03-11T16:00:00.000000000Z');
// DST change:
assert(iter.next().value.toString(), '2017-03-12T15:00:00.000000000Z');
assert(iter.next().value.toString(), '2017-03-13T15:00:00.000000000Z');

UTC offset for a zoned event, as a string

Use Temporal.TimeZone.getOffsetStringFor() to map a Temporal.Instant instance and a time zone into the UTC offset at that exact time in that time zone, as a string.

const instant = Temporal.Instant.from('2020-01-09T00:00Z');
const nyc = Temporal.TimeZone.from('America/New_York');

nyc.getOffsetStringFor(instant); // => -05:00

UTC offset for a zoned event, as a number of seconds

Similarly, use Temporal.TimeZone.getOffsetNanosecondsFor() to do the same thing for the offset as a number of seconds. (Remember to divide by 109 to convert nanoseconds to seconds.)

const instant = Temporal.Instant.from('2020-01-09T00:00Z');
const nyc = Temporal.TimeZone.from('America/New_York');

nyc.getOffsetNanosecondsFor(instant) / 1e9; // => -18000

Offset between two time zones at an exact time

Also using Temporal.TimeZone.getOffsetNanosecondsFor(), we can map a Temporal.Instant instance and two time zones into the signed difference of UTC offsets between those time zones at that exact time, as a number of seconds.

/**
 * Returns the number of seconds' difference between the UTC offsets of two
 * time zones, at an exact time
 *
 * @param {Temporal.Instant} instant - An exact time
 * @param {Temporal.TimeZone} sourceTimeZone - A time zone to examine
 * @param {Temporal.TimeZone} targetTimeZone - A second time zone to examine
 * @returns {number} The number of seconds difference between the time zones'
 *   UTC offsets
 */
function getUtcOffsetDifferenceSecondsAtInstant(instant, sourceTimeZone, targetTimeZone) {
  const sourceOffsetNs = sourceTimeZone.getOffsetNanosecondsFor(instant);
  const targetOffsetNs = targetTimeZone.getOffsetNanosecondsFor(instant);
  return (targetOffsetNs - sourceOffsetNs) / 1e9;
}

const instant = Temporal.Instant.from('2020-01-09T00:00Z');
const nyc = Temporal.TimeZone.from('America/New_York');
const chicago = Temporal.TimeZone.from('America/Chicago');

// At this exact time, Chicago is 3600 seconds earlier than New York
assert.equal(getUtcOffsetDifferenceSecondsAtInstant(instant, nyc, chicago), -3600);

Dealing with dates and times in a fixed location

Here is an example of Temporal used in a graph, showing fictitious activity for a storage tank in a fixed location (Stockholm, Sweden). The graph always starts at midnight in the tank's location, but the graph labels are in the viewer's time zone.

// tankDataX and tankDataY are X and Y-axis data for the last 24 hours,
// obtained from elsewhere, e.g. const [tankDataX, tankDataY] = fetchData();
// tankDataX is an array of Temporal.Instant, and tankDataY is an array of numbers.

// Show data starting from the most recent midnight in the tank's location (Stockholm)
const tankTimeZone = Temporal.TimeZone.from('Europe/Stockholm');
const labelFormatter = new Intl.DateTimeFormat(undefined, {
  weekday: 'short',
  hour: 'numeric',
  minute: 'numeric',
  timeZone: Temporal.now.timeZone()
});
const browserCalendar = labelFormatter.resolvedOptions().calendar;
const tankMidnight = Temporal.now.date(browserCalendar, tankTimeZone).toDateTime().toInstant(tankTimeZone);
const atOrAfterMidnight = (x) => Temporal.Instant.compare(x, tankMidnight) >= 0;
const dataStartIndex = tankDataX.findIndex(atOrAfterMidnight);
const graphLabels = tankDataX.slice(dataStartIndex).map((x) => labelFormatter.format(x));
const graphPoints = tankDataY.slice(dataStartIndex);

const ctx = document.getElementById('storage-tank').getContext('2d');
// The graph is made with Chart.js (https://www.chartjs.org/)
new Chart(ctx, {
  type: 'line',
  data: {
    labels: graphLabels,
    datasets: [
      {
        label: 'Fill level',
        data: graphPoints
      }
    ]
  },
  options: {
    title: {
      display: true,
      text: 'Stockholm storage tank'
    },
    scales: {
      yAxes: [
        {
          ticks: {
            beginAtZero: true
          }
        }
      ]
    }
  }
});

Book a meeting across time zones

Across the web there are several tools for finding meeting times that are appropriate for all the participants' time zones, such as World Time Buddy, World Clock Meeting Planner, and built into various calendar software.

// Display local time zone and three others
const here = Temporal.now.timeZone();
const now = Temporal.now.instant();
const timeZones = [
  { name: 'Here', tz: here },
  { name: 'New York', tz: Temporal.TimeZone.from('America/New_York') },
  { name: 'London', tz: Temporal.TimeZone.from('Europe/London') },
  { name: 'Tokyo', tz: Temporal.TimeZone.from('Asia/Tokyo') }
];

// Start the table at midnight local time
const browserCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
const calendarNow = now.toDateTime(here, browserCalendar);
const startTime = calendarNow
  .with(Temporal.Time.from('00:00')) // midnight
  .toInstant(here);

// Build the table
const table = document.getElementById('meeting-planner');
timeZones.forEach(({ name, tz }) => {
  const row = document.createElement('tr');

  const title = document.createElement('td');
  title.textContent = `${name} (UTC${tz.getOffsetStringFor(now)})`;
  row.appendChild(title);

  for (let hours = 0; hours < 24; hours++) {
    const cell = document.createElement('td');

    const dt = startTime.add({ hours }).toDateTime(tz);
    cell.className = `time-${dt.hour}`;

    // Highlight the current hour in each row
    if (hours === calendarNow.hour) cell.className += ' time-current';

    // Show the date in midnight cells
    let formatOptions;
    if (dt.hour === 0) {
      formatOptions = { month: 'short', day: 'numeric' };
    } else {
      formatOptions = { hour: 'numeric' };
    }
    cell.textContent = dt.toLocaleString(undefined, formatOptions);
    row.appendChild(cell);
  }

  table.appendChild(row);
});

Arithmetic

How many days until a future date

An example HTML form inspired by Days Calculator on timeanddate.com:

// Form parameters
const params = new URL(document.location).searchParams;
const futuredateParam = params.get('futuredate');

// If you have Intl.DurationFormat, then you can do this with
// until.toLocaleString() and untilMonths.toLocaleString(). This
// example will be updated when that becomes possible. For now, we can
// generate the string only in English.
function englishPlural(n, singular, plural) {
  return `${n} ${n === 1 ? singular : plural}`;
}

// When form data posted:
if (futuredateParam !== null) {
  const futureDate = Temporal.Date.from(futuredateParam);
  const browserCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
  const today = Temporal.now.date(browserCalendar);
  const until = today.until(futureDate, { largestUnit: 'days' });
  const untilMonths = until.round({ largestUnit: 'months', relativeTo: today });

  const dayString = englishPlural(until.days, 'day', 'days');
  const monthString =
    `${englishPlural(untilMonths.months, 'month', 'months')}` +
    (untilMonths.days !== 0 ? `, ${englishPlural(untilMonths.days, 'day', 'days')}` : '');

  const results = document.getElementById('futuredate-results');
  results.innerHTML = `
    <p>From and including: <strong>${today.toLocaleString()}</strong></p>
    <p>To but not including: <strong>${futureDate.toLocaleString()}</strong></p>
    <h4>Result: ${dayString}</h4>
    <p>It is ${dayString} from the start date to the end date, but not
    including the end date.</p>
    <p>Or ${monthString} excluding the end date.</p>
  `;
}

Unit-constrained duration between now and a past/future zoned event

Take the difference between two Temporal.Instant instances as a Temporal.Duration instance (positive or negative), representing the duration between the two instants without using units coarser than specified (e.g., for presenting a meaningful countdown with vs. without using months or days).

const result = Temporal.Instant.from('2020-01-09T04:00Z').since(Temporal.Instant.from('2020-01-09T00:00Z'), {
  largestUnit: 'hours'
});
assert.equal(`${result}`, 'PT4H');

const result2 = Temporal.Instant.from('2020-01-09T00:00Z').until(Temporal.Instant.from('2020-01-09T04:00Z'), {
  largestUnit: 'minutes'
});
assert.equal(`${result2}`, 'PT240M');

// Example of using it in a countdown:

const duration = Temporal.now.instant().until(Temporal.Instant.from('2020-04-01T13:00-07:00[America/Los_Angeles]'));
// Note that this does not work unless you have Intl.DurationFormat, which is
// still an early-stage proposal.
`It's ${duration.toLocaleString()} ${duration.sign < 0 ? 'until' : 'since'} the TC39 Temporal presentation`;

Nearest offset transition in a time zone

Map a Temporal.Instant instance and a Temporal.TimeZone object into a Temporal.Instant instance representing the nearest following exact time at which there is an offset transition in the time zone (e.g., for setting reminders).

/**
 * Get the nearest following exact time that the given time zone transitions
 * to another UTC offset, inclusive or exclusive.
 *
 * @param {Temporal.Instant} instant - Starting exact time to consider
 * @param {Temporal.TimeZone} timeZone - Time zone to consider
 * @param {boolean} inclusive - Include the start time, or not
 * @returns {(Temporal.Instant|null)} - Next UTC offset transition, or null if
 *   none known at this time
 */
function getInstantOfNearestOffsetTransitionToInstant(instant, timeZone, inclusive) {
  let nearest;
  if (inclusive) {
    // In case instant itself is the exact time of a transition:
    nearest = timeZone.getNextTransition(instant.subtract({ nanoseconds: 1 }));
  } else {
    nearest = timeZone.getNextTransition(instant);
  }
  return nearest;
}

const instant = Temporal.Instant.from('2019-04-16T21:01Z');

const nyc = Temporal.TimeZone.from('America/New_York');
const nextTransition = getInstantOfNearestOffsetTransitionToInstant(instant, nyc, false);
assert.equal(nextTransition.toString(), '2019-11-03T06:00:00Z');

// Inclusive
const sameTransition = getInstantOfNearestOffsetTransitionToInstant(nextTransition, nyc, true);
assert.equal(sameTransition.toString(), nextTransition.toString());

// No known future DST transitions in a time zone
const regina = Temporal.TimeZone.from('America/Regina');
assert.equal(getInstantOfNearestOffsetTransitionToInstant(instant, regina), null);

Comparison of an exact time to business hours

This example takes a roster of wall-clock opening and closing times for a business, and maps an exact time into a time-sensitive state indicator ("opening soon" vs. "open" vs. "closing soon" vs. "closed").

/**
 * Compare the given exact time to the business hours of a business located in
 * a particular time zone, and return a string indicating whether the business
 * is open, closed, opening soon, or closing soon. The length of "soon" can be
 * controlled using the `soonWindow` parameter.
 *
 * @param {Temporal.Instant} now - Exact time at which to consider whether the
 *  business is open
 * @param {Temporal.TimeZone} timeZone - Time zone in which the business is
 *  located
 * @param {(Object|null)[]} businessHours - Array of length 7 indicating
 *  business hours during the week
 * @param {Temporal.Time} businessHours[].open - Time at which the business
 *  opens
 * @param {Temporal.Time} businessHours[].close - Time at which the business
 *  closes
 * @param {Temporal.Duration} soonWindow - Length of time before the opening
 *  or closing time during which the business should be considered "opening
 *  soon" or "closing soon"
 * @returns {string} "open", "closed", "opening soon", or "closing soon"
 */
function getBusinessOpenStateText(now, timeZone, businessHours, soonWindow) {
  function inRange(i, start, end) {
    return Temporal.Instant.compare(i, start) >= 0 && Temporal.Instant.compare(i, end) < 0;
  }

  const dateTime = now.toDateTimeISO(timeZone);
  const weekday = dateTime.dayOfWeek % 7; // convert to 0-based, for array indexing

  // Because of times wrapping around at midnight, we may need to consider
  // yesterday's and tomorrow's hours as well
  const today = dateTime.toDate();
  const yesterday = today.subtract({ days: 1 });
  const tomorrow = today.add({ days: 1 });

  // Push any of the businessHours that overlap today's date into an array,
  // that we will subsequently check. Convert the businessHours Times into
  // DateTimes so that they no longer wrap around.
  const businessHoursOverlappingToday = [];
  const yesterdayHours = businessHours[(weekday + 6) % 7];
  if (yesterdayHours) {
    const { open, close } = yesterdayHours;
    if (Temporal.Time.compare(close, open) < 0) {
      businessHoursOverlappingToday.push({
        open: yesterday.toDateTime(open).toInstant(timeZone),
        close: today.toDateTime(close).toInstant(timeZone)
      });
    }
  }
  const todayHours = businessHours[weekday];
  if (todayHours) {
    const { open, close } = todayHours;
    const todayOrTomorrow = Temporal.Time.compare(close, open) >= 0 ? today : tomorrow;
    businessHoursOverlappingToday.push({
      open: today.toDateTime(open).toInstant(timeZone),
      close: todayOrTomorrow.toDateTime(close).toInstant(timeZone)
    });
  }

  // Check if any of the candidate business hours include the given time
  const soon = now.add(soonWindow);
  let openNow = false;
  let openSoon = false;
  for (const { open, close } of businessHoursOverlappingToday) {
    openNow = openNow || inRange(now, open, close);
    openSoon = openSoon || inRange(soon, open, close);
  }

  if (openNow) {
    if (!openSoon) return 'closing soon';
    return 'open';
  }
  if (openSoon) return 'opening soon';
  return 'closed';
}

// For example, a restaurant or bar might have opening hours that go past
// midnight; make sure this is handled correctly
const businessHours = [
  /* Sun */ { open: Temporal.Time.from('13:00'), close: Temporal.Time.from('20:30') },
  /* Mon */ null, // closed Mondays
  /* Tue */ { open: Temporal.Time.from('11:00'), close: Temporal.Time.from('20:30') },
  /* Wed */ { open: Temporal.Time.from('11:00'), close: Temporal.Time.from('20:30') },
  /* Thu */ { open: Temporal.Time.from('11:00'), close: Temporal.Time.from('22:00') },
  /* Fri */ { open: Temporal.Time.from('11:00'), close: Temporal.Time.from('00:00') },
  /* Sat */ { open: Temporal.Time.from('11:00'), close: Temporal.Time.from('02:00') }
];

const now = Temporal.Instant.from('2019-04-07T00:00+01:00[Europe/Berlin]');
const tz = Temporal.TimeZone.from('Europe/Berlin');
const soonWindow = Temporal.Duration.from({ minutes: 30 });
const saturdayNightState = getBusinessOpenStateText(now, tz, businessHours, soonWindow);
assert.equal(saturdayNightState, 'open');

Flight arrival/departure/duration

Map localized trip departure and arrival times into trip duration in units no larger than hours.

/**
 * Given localized departure and arrival times, get a trip duration suitable
 * for display in an airline ticket website, for example.
 *
 * @param {string} parseableDeparture - Departure time with time zone
 * @param {string} parseableArrival - Arrival time with time zone
 * @returns {Temporal.Duration} A duration with units no larger than hours
 */
function getTripDurationInHrMinSec(parseableDeparture, parseableArrival) {
  const departure = Temporal.Instant.from(parseableDeparture);
  const arrival = Temporal.Instant.from(parseableArrival);
  return departure.until(arrival, { largestUnit: 'hours' });
}

const flightTime = getTripDurationInHrMinSec(
  '2020-03-08T11:55:00+08:00[Asia/Hong_Kong]',
  '2020-03-08T09:50:00-07:00[America/Los_Angeles]'
);
assert.equal(flightTime.toString(), 'PT12H55M');

Map localized departure time and duration into localized arrival time.

/**
 * Given a localized departure time and a flight duration, get a local arrival
 * time in the destination time zone.
 *
 * @param {string} parseableDeparture - Departure time with time zone
 * @param {Temporal.Duration} flightTime - Duration of the flight
 * @param {Temporal.TimeZone} destinationTimeZone - Time zone in which the
 *  flight's destination is located
 * @param {Temporal.Calendar|string} calendar - Calendar system used for output
 * @returns {Temporal.DateTime} Local arrival time
 */
function getLocalizedArrival(parseableDeparture, flightTime, destinationTimeZone, calendar) {
  const departure = Temporal.Instant.from(parseableDeparture);
  const arrival = departure.add(flightTime);
  return arrival.toDateTime(destinationTimeZone, calendar);
}

const arrival = getLocalizedArrival(
  '2020-03-08T11:55:00+08:00[Asia/Hong_Kong]',
  Temporal.Duration.from({ minutes: 775 }),
  'America/Los_Angeles',
  'iso8601'
);
assert.equal(arrival.toString(), '2020-03-08T09:50:00');

Push back a launch date

Add the number of days it took to get an approval, and advance to the start of the following month.

/**
 * Take a date, add a number of days' delay, and round to the start of the next
 * month.
 *
 * @param {Temporal.Date} date - Original date
 * @param {number} delayDays - Number of days' delay
 * @returns {Temporal.Date} - Beginning of the next month after the delay
 */
function plusAndRoundToMonthStart(date, delayDays) {
  return date
    .add({ days: delayDays })
    .add({ months: 1 }) // constrains to end of month if needed, e.g. Jan 31 -> Feb 28
    .with({ day: 1 });
}

const oldLaunchDate = Temporal.Date.from('2019-06-01');

const fifteenDaysDelay = plusAndRoundToMonthStart(oldLaunchDate, 15);
assert.equal(fifteenDaysDelay.toString(), '2019-07-01');

const sixtyDaysDelay = plusAndRoundToMonthStart(oldLaunchDate, 60);
assert.equal(sixtyDaysDelay.toString(), '2019-08-01');

Schedule a reminder ahead of matching a record-setting duration

When considering a record (for example, a personal-best time in a sport), you might want to receive an alert just before the record is about to be broken. This example takes a record as a Temporal.Duration, the starting exact time of the current attempt as a Temporal.Instant, and another Temporal.Duration indicating how long before the potentially record-setting exact time you would like to receive an alert. It returns the exact time at which a notification could be sent, for example "Keep going! 5 more minutes and it will be your personal best!"

This could be used for workout tracking, racing (including long and potentially time-zone-crossing races like the Bullrun Rally, Iditarod, Self-Transcendence 3100, and Clipper Round The World), or even open-ended analogs like event-every-day "streaks".

/**
 * Retrieve an exact time at which to give advance notice of a record that
 * is potentially about to be broken.
 *
 * @param {Temporal.Instant} start - Starting exact time of the event
 * @param {Temporal.Duration} previousRecord - Existing record to be broken
 * @param {Temporal.Duration} noticeWindow - Advance notice time
 * @returns {Temporal.Instant} Exact time at which to give advance notice of
 *  breaking the record
 */
function getInstantBeforeOldRecord(start, previousRecord, noticeWindow) {
  return start.add(previousRecord).subtract(noticeWindow);
}

// Start of the men's 10000 meters at the Rio de Janeiro 2016 Olympic Games
const raceStart = Temporal.Instant.from('2016-08-13T21:27-03:00[America/Sao_Paulo]');
// Kenenisa Bekele's world record set in 2005
const record = Temporal.Duration.from({ minutes: 26, seconds: 17, milliseconds: 530 });
const noticeWindow = Temporal.Duration.from({ minutes: 1 });
// Time to send a "hurry up, can you finish the race in 1 minute?" push
// notification to all the runners
const reminderAt = getInstantBeforeOldRecord(raceStart, record, noticeWindow);

assert.equal(reminderAt.toString(), '2016-08-14T00:52:17.53Z');

Nth weekday of the month

Example of getting a Temporal.Date representing the first Tuesday of the given Temporal.YearMonth, adaptable to other weekdays.

/**
 * Gets the first Tuesday of the month and returns its date
 *
 * @param {Temporal.YearMonth} queriedMonth - YearMonth instance to query
 * @returns {Temporal.Date} Temporal.Date Instance which gives first tuesday
 */
function getFirstTuesday(queriedMonth) {
  // We first need to convert to a date
  const firstOfMonth = queriedMonth.toDateOnDay(1);

  // We have Monday = 1, Sunday = 7, and we want to add a positive number
  // smaller than 7 to get to the first Tuesday.
  // If we're already on a Tuesday (2) then we want to add 0.
  // So for the first of the month being a Monday through Sunday the additions are:
  //    1, 0, 6, 5, 4, 3, 2 which is given by that formula.
  // This lookup table makes this example easier to read, but you can also
  // calculate the answer: (7 + desiredWeekday - firstOfMonth.dayOfWeek) % 7
  return firstOfMonth.add({ days: [1, 0, 6, 5, 4, 3, 2][firstOfMonth.dayOfWeek - 1] });
}

const myMonth = Temporal.YearMonth.from('2020-02');
const firstTuesdayOfMonth = getFirstTuesday(myMonth);
assert.equal(firstTuesdayOfMonth.toString(), '2020-02-04');
assert.equal(firstTuesdayOfMonth.dayOfWeek, 2);

// Similarly, to get the second Tuesday:
const secondWeek = myMonth.toDateOnDay(8);
const secondTuesday = secondWeek.add({ days: [1, 0, 6, 5, 4, 3, 2][secondWeek.dayOfWeek - 1] });
assert.equal(secondTuesday.day, firstTuesdayOfMonth.day + 7);

// And the last Tuesday:
const lastOfMonth = myMonth.toDateOnDay(myMonth.daysInMonth);
const lastTuesday = lastOfMonth.subtract({ days: [6, 0, 1, 2, 3, 4, 5][lastOfMonth.dayOfWeek - 1] });
assert.equal(lastTuesday.toString(), '2020-02-25');
assert.equal(lastTuesday.dayOfWeek, 2);
// or:
const lastTuesday2 = lastOfMonth.subtract({ days: (7 + lastOfMonth.dayOfWeek - 2) % 7 });
assert.equal(Temporal.Date.compare(lastTuesday, lastTuesday2), 0);

Given a Temporal.YearMonth instance and an ISO 8601 ordinal calendar day of the week ranging from 1 (Monday) to 7 (Sunday), return a chronologically ordered array of Temporal.Date instances corresponding with every day in the month that is the specified day of the week (of which there will always be either four or five).

/**
 * Gets an array of Temporal.Dates of every day in the given month, that falls
 * on the given day of the week.
 *
 * @param {Temporal.YearMonth} yearMonth - Year and month to query
 * @param {number} dayNumberOfTheWeek - Day of the week, Monday=1, Sunday=7
 * @returns {Temporal.Date[]} Array of dates
 */
function getWeeklyDaysInMonth(yearMonth, dayNumberOfTheWeek) {
  const firstOfMonth = yearMonth.toDateOnDay(1);
  let nextWeekday = firstOfMonth.add({ days: (7 + dayNumberOfTheWeek - firstOfMonth.dayOfWeek) % 7 });
  const result = [];
  while (nextWeekday.month === yearMonth.month) {
    result.push(nextWeekday);
    nextWeekday = nextWeekday.add({ days: 7 });
  }
  return result;
}

assert.equal(
  getWeeklyDaysInMonth(Temporal.YearMonth.from('2020-02'), 1).join(' '),
  '2020-02-03 2020-02-10 2020-02-17 2020-02-24'
);
assert.equal(
  getWeeklyDaysInMonth(Temporal.YearMonth.from('2020-02'), 6).join(' '),
  '2020-02-01 2020-02-08 2020-02-15 2020-02-22 2020-02-29'
);

Given a Temporal.Date instance, return the count of preceding days in its month that share its day of the week.

/**
 * Gets the number of preceding days in the same month as `date` that fall on
 * the same day of the week as `date`.
 *
 * @param {Temporal.Date} date - Starting date
 * @returns {number} Number of days
 */
function countPrecedingWeeklyDaysInMonth(date) {
  // This doesn't actually require Temporal
  return Math.floor((date.day - 1) / 7);
}

assert.equal(countPrecedingWeeklyDaysInMonth(Temporal.Date.from('2020-02-28')), 3);
assert.equal(countPrecedingWeeklyDaysInMonth(Temporal.Date.from('2020-02-29')), 4);

Manipulating the day of the month

Here are some examples of taking an existing date, and adjusting the day of the month.

const date = Temporal.Date.from('2020-04-14');

// Third day of next month:

const thirdOfNextMonth = date.add({ months: 1 }).with({ day: 3 });

assert.equal(thirdOfNextMonth.toString(), '2020-05-03');

// Last day of this month:

const lastOfThisMonth = date.with({ day: date.daysInMonth });

assert.equal(lastOfThisMonth.toString(), '2020-04-30');

// On the 18th of this month at 8 PM:

const thisMonth18thAt8PM = date.with({ day: 18 }).toDateTime(Temporal.Time.from('20:00'));

assert.equal(thisMonth18thAt8PM.toString(), '2020-04-18T20:00:00');

Same date in another month

Likewise, here are some examples of taking an existing date and adjusting the month, but keeping the day and year the same.

Depending on the behaviour you want, you will need to pick the right overflow option, but the default of "constrain" should be correct for most cases.

const date = Temporal.Date.from('2020-05-31');

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

const feb = date.with({ month: 2 });

assert.equal(feb.toString(), '2020-02-29');

// Same date and time, but in April
// (and throw an exception if the date doesn't exist in April):

assert.throws(() => {
  date.with({ month: 4 }, { overflow: 'reject' });
});

Next weekly occurrence

From a Temporal.Instant instance and a local Temporal.TimeZone, get a Temporal.DateTime representing the next occurrence of a weekly event that is scheduled on a particular weekday and time in a particular time zone. (For example, "weekly on Thursdays at 08:45 California time").

/**
 * Returns the local date and time for the next occurrence of a weekly occurring
 * event.
 *
 * @param {Temporal.Instant} now - Starting point
 * @param {Temporal.TimeZone} localTimeZone - Time zone for return value
 * @param {number} weekday - Weekday event occurs on (Monday=1, Sunday=7)
 * @param {Temporal.Time} eventTime - Time event occurs at
 * @param {Temporal.TimeZone} eventTimeZone - Time zone where event is planned
 * @returns {Temporal.DateTime} Local date and time of next occurrence
 */
function nextWeeklyOccurrence(now, localTimeZone, weekday, eventTime, eventTimeZone) {
  const dateTime = now.toDateTimeISO(eventTimeZone);
  const nextDate = dateTime.toDate().add({ days: (weekday + 7 - dateTime.dayOfWeek) % 7 });
  let nextOccurrence = nextDate.toDateTime(eventTime);

  // Handle the case where the event is today but already happened
  if (Temporal.DateTime.compare(dateTime, nextOccurrence) > 0) {
    nextOccurrence = nextOccurrence.add({ days: 7 });
  }

  return nextOccurrence.toInstant(eventTimeZone).toDateTimeISO(localTimeZone);
}

// "Weekly on Thursdays at 08:45 California time":
const weekday = 4;
const eventTime = Temporal.Time.from('08:45');
const eventTimeZone = Temporal.TimeZone.from('America/Los_Angeles');

const rightBefore = Temporal.Instant.from('2020-03-26T08:30-07:00[America/Los_Angeles]');
const localTimeZone = Temporal.TimeZone.from('Europe/London');
let next = nextWeeklyOccurrence(rightBefore, localTimeZone, weekday, eventTime, eventTimeZone);
assert.equal(next.toString(), '2020-03-26T15:45:00');

const rightAfter = Temporal.Instant.from('2020-03-26T09:00-07:00[America/Los_Angeles]');
next = nextWeeklyOccurrence(rightAfter, localTimeZone, weekday, eventTime, eventTimeZone);
assert.equal(next.toString(), '2020-04-02T16:45:00');

Weekday of yearly occurrence

In some countries, when a public holiday falls on a Tuesday or Thursday, an extra "bridge" public holiday is observed on Monday or Friday in order to give workers a long weekend off. The following example calculates this.

/**
 * Calculates the days that need to be taken off work in order to have a long
 * weekend around a public holiday, "bridging" the holiday if it falls on a
 * Tuesday or Thursday.
 *
 * @param {Temporal.MonthDay} holiday - Yearly date on the calendar
 * @param {number} year - Year in which to calculate the bridge days
 * @returns {Temporal.Date[]} List of dates to be taken off work
 */
function bridgePublicHolidays(holiday, year) {
  const date = holiday.toDateInYear(year);
  switch (date.dayOfWeek) {
    case 1: // Mon
    case 3: // Wed
    case 5: // Fri
      return [date];
    case 2: // Tue; take Monday off
      return [date.subtract({ days: 1 }), date];
    case 4: // Thu; take Friday off
      return [date, date.add({ days: 1 })];
    case 6: // Sat
    case 7: // Sun
      return [];
  }
}

const labourDay = Temporal.MonthDay.from('05-01');

// No bridge day
assert.deepEqual(
  bridgePublicHolidays(labourDay, 2020).map((d) => d.toString()),
  ['2020-05-01']
);

// Bridge day
assert.deepEqual(
  bridgePublicHolidays(labourDay, 2018).map((d) => d.toString()),
  ['2018-04-30', '2018-05-01']
);

// Bad luck, the holiday is already on a weekend
assert.deepEqual(
  bridgePublicHolidays(labourDay, 2021).map((d) => d.toString()),
  []
);