Skip to content

Commit

Permalink
Adapt cookbook examples to ZonedDateTime
Browse files Browse the repository at this point in the history
This adapts all the relevant cookbook examples (except the meeting planner
which is blocked on a bug in ZonedDateTime.toLocaleString) to use
ZonedDateTime. Most of these were identified in Justin's original pull
request.

Co-authored-by: Justin Grant <[email protected]>

See: #569
  • Loading branch information
ptomato authored and Ms2ger committed Nov 6, 2020
1 parent a41cca1 commit d25c725
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 152 deletions.
9 changes: 5 additions & 4 deletions docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ This is easily done with `dateTime.toInstant()`, but here is an example of imple

### 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.
Map a zoned date and time of day into another zoned date and time of day in a target time zone at the corresponding exact time.
This could be used when converting user-input date-time values between time zones.

```javascript
{{cookbook/getParseableZonedStringWithLocalTimeInOtherZone.mjs}}
{{cookbook/zonedDateTimeInOtherZone.mjs}}
```

Here is another example similar to the previous one, using the time zone for future events.
Expand All @@ -189,7 +189,7 @@ Similar to the previous recipe, calculate the exact times of a daily occurrence

### 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.
Use `Temporal.TimeZone.getOffsetStringFor()` or `Temporal.ZonedDateTime.offset` 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.

```javascript
{{cookbook/getUtcOffsetStringAtInstant.mjs}}
Expand Down Expand Up @@ -368,6 +368,7 @@ This example takes a roster of wall-clock opening and closing times for a busine
### Flight arrival/departure/duration

Map localized trip departure and arrival times into trip duration in units no larger than hours.
(By default, differences between ZonedDateTime instances are exact differences in time units.)

```javascript
{{cookbook/getTripDurationInHrMinSec.mjs}}
Expand Down Expand Up @@ -439,7 +440,7 @@ Depending on the behaviour you want, you will need to pick the right `overflow`

### Next weekly occurrence

From a `Temporal.Instant` instance and a local `Temporal.TimeZone`, get a `Temporal.PlainDateTime` 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").
From a `Temporal.ZonedDateTime` instance, get a `Temporal.ZonedDateTime` 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").

```javascript
{{cookbook/nextWeeklyOccurrence.mjs}}
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import './getInstantOfNearestOffsetTransitionToInstant.mjs';
import './getInstantWithLocalTimeInZone.mjs';
import './getLocalizedArrival.mjs';
import './getParseableZonedStringAtInstant.mjs';
import './getParseableZonedStringWithLocalTimeInOtherZone.mjs';
import './getSortedLocalDateTimes.mjs';
import './getTimeStamp.mjs';
import './getTimeZoneObjectFromIanaName.mjs';
Expand All @@ -31,3 +30,4 @@ import './noonOnDate.mjs';
import './plusAndRoundToMonthStart.mjs';
import './roundDownToWholeHours.mjs';
import './sortExactTimeStrings.mjs';
import './zonedDateTimeInOtherZone.mjs';
10 changes: 5 additions & 5 deletions docs/cookbook/calculateDailyOccurrence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
function* calculateDailyOccurrence(startDate, time, timeZone) {
for (let date = startDate; ; date = date.add({ days: 1 })) {
yield date.toPlainDateTime(time).toZonedDateTime(timeZone);
yield date.toZonedDateTime({ time, timeZone }).toInstant();
}
}

Expand All @@ -19,8 +19,8 @@ const time = Temporal.PlainTime.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');
assert.equal(iter.next().value.toString(), '2017-03-10T16:00:00Z');
assert.equal(iter.next().value.toString(), '2017-03-11T16:00:00Z');
// DST change:
assert(iter.next().value.toString(), '2017-03-12T15:00:00.000000000Z');
assert(iter.next().value.toString(), '2017-03-13T15:00:00.000000000Z');
assert.equal(iter.next().value.toString(), '2017-03-12T15:00:00Z');
assert.equal(iter.next().value.toString(), '2017-03-13T15:00:00Z');
74 changes: 26 additions & 48 deletions docs/cookbook/getBusinessOpenStateText.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
* is open, closed, opening soon, or closing soon. The length of "soon" can be
* controlled using the `soonWindow` parameter.
*
* FIXME: This example should stop using TimeZone.getInstantFor as soon as the
* ZonedDateTime.with(), add(), and subtract() methods get implemented.
*
* @param {Temporal.ZonedDateTime} now - Date and Time at which to consider
* whether the business is open
* @param {(Object|null)[]} businessHours - Array of length 7 indicating
Expand All @@ -21,58 +18,30 @@
* @returns {string} "open", "closed", "opening soon", or "closing soon"
*/
function getBusinessOpenStateText(now, businessHours, soonWindow) {
function inRange(i, start, end) {
return Temporal.Instant.compare(i, start) >= 0 && Temporal.Instant.compare(i, end) < 0;
const compare = Temporal.ZonedDateTime.compare;
function inRange(zdt, start, end) {
return compare(zdt, start) >= 0 && compare(zdt, end) < 0;
}

const dateTime = now.toPlainDateTime();
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.toPlainDate();
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.PlainTime.compare(close, open) < 0) {
businessHoursOverlappingToday.push({
open: now.timeZone.getInstantFor(yesterday.toPlainDateTime(open)),
close: now.timeZone.getInstantFor(today.toPlainDateTime(close))
});
}
}
const todayHours = businessHours[weekday];
if (todayHours) {
const { open, close } = todayHours;
const todayOrTomorrow = Temporal.PlainTime.compare(close, open) >= 0 ? today : tomorrow;
businessHoursOverlappingToday.push({
open: now.timeZone.getInstantFor(today.toPlainDateTime(open)),
close: now.timeZone.getInstantFor(todayOrTomorrow.toPlainDateTime(close))
});
}
for (const delta of [-1, 0]) {
const openDate = now.toPlainDate().add({ days: delta });
// convert weekday (1..7) to 0-based index, for array:
const index = (openDate.dayOfWeek + 7) % 7;
if (!businessHours[index]) continue;

// Check if any of the candidate business hours include the given time
const nowInstant = now.toInstant();
const soon = nowInstant.add(soonWindow);
let openNow = false;
let openSoon = false;
for (const { open, close } of businessHoursOverlappingToday) {
openNow = openNow || inRange(nowInstant, open, close);
openSoon = openSoon || inRange(soon, open, close);
}
const { open: openTime, close: closeTime } = businessHours[index];
const open = openDate.toZonedDateTime({ time: openTime, timeZone: now.timeZone });
const isWrap = Temporal.PlainTime.compare(closeTime, openTime) < 0;
const closeDate = isWrap ? openDate.add({ days: 1 }) : openDate;
const close = closeDate.toZonedDateTime({ time: closeTime, timeZone: now.timeZone });

if (openNow) {
if (!openSoon) return 'closing soon';
return 'open';
if (inRange(now, open, close)) {
return compare(now, close.subtract(soonWindow)) >= 0 ? 'closing soon' : 'open';
}
if (inRange(now.add(soonWindow), open, close)) return 'opening soon';
}
if (openSoon) return 'opening soon';
return 'closed';
}

Expand All @@ -92,3 +61,12 @@ const now = Temporal.ZonedDateTime.from('2019-04-07T00:00+02:00[Europe/Berlin]')
const soonWindow = Temporal.Duration.from({ minutes: 30 });
const saturdayNightState = getBusinessOpenStateText(now, businessHours, soonWindow);
assert.equal(saturdayNightState, 'open');

const lastCall = now.add({ hours: 1, minutes: 50 });
assert.equal(lastCall.toString(), '2019-04-07T01:50:00+02:00[Europe/Berlin]');
const lastCallState = getBusinessOpenStateText(lastCall, businessHours, soonWindow);
assert.equal(lastCallState, 'closing soon');

const tuesdayEarly = now.add({ days: 2, hours: 6 });
const tuesdayEarlyState = getBusinessOpenStateText(tuesdayEarly, businessHours, soonWindow);
assert.equal(tuesdayEarlyState, 'closed');
31 changes: 5 additions & 26 deletions docs/cookbook/getLocalizedArrival.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,6 @@
/**
* Given a localized departure time and a flight duration, get a local arrival
* time in the destination time zone.
*
* FIXME: This becomes a one-liner when Temporal.ZonedDateTime.add() is
* implemented.
*
* @param {string} departure - 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.PlainDateTime} Local arrival time
*/
function getLocalizedArrival(departure, flightTime, destinationTimeZone, calendar) {
const instant = departure.toInstant();
const arrival = instant.add(flightTime);
return destinationTimeZone.getDateTimeFor(arrival, calendar);
}
const departure = Temporal.ZonedDateTime.from('2020-03-08T11:55:00+08:00[Asia/Hong_Kong]');
const flightTime = Temporal.Duration.from({ minutes: 775 });

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

assert.equal(arrival.toString(), '2020-03-08T09:50:00-07:00[America/Los_Angeles]');
38 changes: 0 additions & 38 deletions docs/cookbook/getParseableZonedStringWithLocalTimeInOtherZone.mjs

This file was deleted.

21 changes: 4 additions & 17 deletions docs/cookbook/getTripDurationInHrMinSec.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
/**
* 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 departure = Temporal.ZonedDateTime.from('2020-03-08T11:55:00+08:00[Asia/Hong_Kong]');
const arrival = Temporal.ZonedDateTime.from('2020-03-08T09:50:00-07:00[America/Los_Angeles]');

const flightTime = departure.until(arrival);

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');
4 changes: 4 additions & 0 deletions docs/cookbook/getUtcOffsetStringAtInstant.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ const instant = Temporal.Instant.from('2020-01-09T00:00Z');
const nyc = Temporal.TimeZone.from('America/New_York');

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

// Can also be done with ZonedDateTime.offset:
const source = instant.toZonedDateTimeISO(nyc);
source.offset; // => -05:00
21 changes: 9 additions & 12 deletions docs/cookbook/nextWeeklyOccurrence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,23 @@
* Returns the local date and time for the next occurrence of a weekly occurring
* event.
*
* FIXME: This should use ZonedDateTime arithmetic once ZonedDateTime.add() and
* subtract() are implemented.
*
* @param {Temporal.ZonedDateTime} now - Starting point
* @param {number} weekday - Weekday event occurs on (Monday=1, Sunday=7)
* @param {Temporal.PlainTime} eventTime - Time event occurs at
* @param {Temporal.TimeZone} eventTimeZone - Time zone where event is planned
* @returns {Temporal.PlainDateTime} Local date and time of next occurrence
* @returns {Temporal.ZonedDateTime} Local date and time of next occurrence
*/
function nextWeeklyOccurrence(now, weekday, eventTime, eventTimeZone) {
const dateTime = now.withTimeZone(eventTimeZone).toPlainDateTime();
const nextDate = dateTime.toPlainDate().add({ days: (weekday + 7 - dateTime.dayOfWeek) % 7 });
let nextOccurrence = nextDate.toPlainDateTime(eventTime);
const nowInEventTimeZone = now.withTimeZone(eventTimeZone);
const nextDate = nowInEventTimeZone.toPlainDate().add({ days: (weekday + 7 - nowInEventTimeZone.dayOfWeek) % 7 });
let nextOccurrence = nextDate.toZonedDateTime({ time: eventTime, timeZone: eventTimeZone });

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

return eventTimeZone.getInstantFor(nextOccurrence).toZonedDateTime(now).toPlainDateTime();
return nextOccurrence.withTimeZone(now.timeZone);
}

// "Weekly on Thursdays at 08:45 California time":
Expand All @@ -31,8 +28,8 @@ const eventTimeZone = Temporal.TimeZone.from('America/Los_Angeles');

const rightBefore = Temporal.ZonedDateTime.from('2020-03-26T15:30+00:00[Europe/London]');
let next = nextWeeklyOccurrence(rightBefore, weekday, eventTime, eventTimeZone);
assert.equal(next.toString(), '2020-03-26T15:45:00');
assert.equal(next.toString(), '2020-03-26T15:45:00+00:00[Europe/London]');

const rightAfter = Temporal.ZonedDateTime.from('2020-03-26T16:00+00:00[Europe/London]');
next = nextWeeklyOccurrence(rightAfter, weekday, eventTime, eventTimeZone);
assert.equal(next.toString(), '2020-04-02T16:45:00');
assert.equal(next.toString(), '2020-04-02T16:45:00+01:00[Europe/London]');
2 changes: 1 addition & 1 deletion docs/cookbook/storageTank.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const labelFormatter = new Intl.DateTimeFormat(undefined, {
timeZone: Temporal.now.timeZone()
});
const browserCalendar = labelFormatter.resolvedOptions().calendar;
const tankMidnight = Temporal.now.plainDate(browserCalendar, tankTimeZone).toPlainDateTime().toInstant(tankTimeZone);
const tankMidnight = Temporal.now.zonedDateTime(browserCalendar).withTimeZone(tankTimeZone).startOfDay().toInstant();
const atOrAfterMidnight = (x) => Temporal.Instant.compare(x, tankMidnight) >= 0;
const dataStartIndex = tankDataX.findIndex(atOrAfterMidnight);
const graphLabels = tankDataX.slice(dataStartIndex).map((x) => labelFormatter.format(x));
Expand Down
6 changes: 6 additions & 0 deletions docs/cookbook/zonedDateTimeInOtherZone.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const source = Temporal.ZonedDateTime.from('2020-01-09T00:00[America/Chicago]');

const result = source.withTimeZone('America/Los_Angeles');

// On this date, when it's midnight in Chicago, it's 10 PM the previous night in LA
assert.equal(result.toString(), '2020-01-08T22:00:00-08:00[America/Los_Angeles]');

0 comments on commit d25c725

Please sign in to comment.