Skip to content

Commit

Permalink
Add cookbook example of creating your own disambiguation behaviour
Browse files Browse the repository at this point in the history
This was requested in the original cookbook issue and is now possible
using TimeZone.getPossibleAbsolutesFor().

Closes: #317
Closes: #318
  • Loading branch information
ptomato committed May 21, 2020
1 parent 0ed6991 commit d8b1867
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ Here's an example of rounding a time _down_ to the previously occurring whole ho

## Time zone conversion

### Preserving local time

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

```javascript
{{cookbook/getInstantWithLocalTimeInZone.mjs}}
```

### Preserving absolute instant

Map a zoned date and time of day into a string serialization of the local time in a target zone at the corresponding instant in absolute time.
Expand Down
1 change: 1 addition & 0 deletions docs/cookbook/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import './getElapsedDurationSinceInstant.mjs';
import './getFirstTuesdayOfMonth.mjs';
import './getInstantBeforeOldRecord.mjs';
import './getInstantOfNearestOffsetTransitionToInstant.mjs';
import './getInstantWithLocalTimeInZone.mjs';
import './getLocalizedArrival.mjs';
import './getParseableZonedStringAtInstant.mjs';
import './getParseableZonedStringWithLocalTimeInOtherZone.mjs';
Expand Down
92 changes: 92 additions & 0 deletions docs/cookbook/getInstantWithLocalTimeInZone.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Get an absolute time corresponding with a calendar date / wall-clock time in
* a particular time zone, the same as Temporal.TimeZone.getAbsoluteFor() or
* Temporal.DateTime.inTimeZone(), but with more disambiguation options.
*
* As well as the default Temporal disambiguation options 'earlier', 'later',
* and 'reject', there are additional options possible:
*
* - 'earlierLater': Same as what the Moment Timezone and Luxon libraries do;
* equivalent to 'earlier' when turning the clock back, and 'later' when
* setting the clock forward.
* - '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 - Disambiguation mode, see description.
* @returns {Temporal.Absolute} 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 (['earlier', 'later', 'reject'].includes(disambiguation)) {
return timeZone.getAbsoluteFor(dateTime, { disambiguation });
}

const possible = timeZone.getPossibleAbsolutesFor(dateTime);

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

switch (disambiguation) {
case 'earlierLater':
if (possible.length === 0) return timeZone.getAbsoluteFor(dateTime, { disambiguation: 'later' });
return possible[0];
case 'clipEarlier':
if (possible.length === 0) {
const before = dateTime.inTimeZone(timeZone, { disambiguation: 'earlier' });
return timeZone
.getTransitions(before)
.next()
.value.minus({ nanoseconds: 1 });
}
return possible[0];
case 'clipLater':
if (possible.length === 0) {
const before = dateTime.inTimeZone(timeZone, { disambiguation: 'earlier' });
return timeZone.getTransitions(before).next().value;
}
return possible[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+01:00[Europe/Berlin]',
later: /* */ '2019-03-31T03:45+02:00[Europe/Berlin]',
earlierLater: /**/ '2019-03-31T03:45+02:00[Europe/Berlin]',
clipEarlier: /* */ '2019-03-31T01:59:59.999999999+01:00[Europe/Berlin]',
clipLater: /* */ '2019-03-31T03: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-02:00[America/Sao_Paulo]',
later: /* */ '2019-02-16T23:45-03:00[America/Sao_Paulo]',
earlierLater: /**/ '2019-02-16T23:45-02:00[America/Sao_Paulo]',
clipEarlier: /* */ '2019-02-16T23:45-02:00[America/Sao_Paulo]',
clipLater: /* */ '2019-02-16T23:45-03:00[America/Sao_Paulo]'
};
for (const [disambiguation, result] of Object.entries(brazilianResults)) {
assert.equal(
getInstantWithLocalTimeInZone(doubleEasternBrazilianWallTime, brazilEast, disambiguation).toString(brazilEast),
result
);
}

0 comments on commit d8b1867

Please sign in to comment.