Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write cookbook of practical scenarios using Temporal #240

Closed
littledan opened this issue Oct 30, 2019 · 39 comments · Fixed by #305
Closed

Write cookbook of practical scenarios using Temporal #240

littledan opened this issue Oct 30, 2019 · 39 comments · Fixed by #305
Assignees
Labels
documentation Additions to documentation

Comments

@littledan
Copy link
Member

@gibson042 suggested doing this, and I think it's a great idea that shouldn't be lost. A couple purposes:

  • Validate the design, to know that these things can be done ergonomically
  • Serve as documentation for JS developers who want to use Temporal
  • Good material to copy/paste into slide presentations about Temporal!

Resources for Moment and date-fns, including documentation and stack overflow questions, could be good seed material

@ryzokuken ryzokuken added the documentation Additions to documentation label Nov 1, 2019
@gibson042
Copy link
Collaborator

gibson042 commented Nov 13, 2019

As promised earlier today, this is finally ready. It remains my hope that filling in the bodies of the below functions is used to validate the Temporal API, and in particular that any discomfort identified is used to change that API before it is finalized. This should have happened months ago, but I still believe that it is better late than never.

Construction

Instant from legacy Date

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

function getInstantAtEs2019DateInstance( es2019Date ) {
    // ???
}

Zoned instant from instant and time zone

Map a Temporal.Absolute instance and a time zone name or object into a string serialization of the local time in that zone corresponding to the instant in absolute time.

function getParseableIanaZonedStringAtInstant( absolute, ianaTimeZoneName ) {
    let timeZoneObject = getTimeZoneObjectFromIanaName( ianaTimeZoneName );
    return getParseableZonedStringAtInstant(absolute, timeZoneObject);
}
function getParseableZonedStringAtInstant( absolute, timeZoneObject ) {
    // ???
}

getParseableIanaZonedStringAtInstant(
    Temporal.Absolute.from("2020-01-09T00:00Z"),
    "Europe/Paris"
).replace(/(T\d+:\d+).*?([+-])/, "$1$2") ===
"2020-01-09T01:00+01:00[Europe/Paris]";

Fixed-offset instant from instant and UTC offset

Map a Temporal.Absolute instance and a UTC offset into a string serialization of the local time in that offset corresponding to the instant in absolute time.

function getParseableFixedOffsetStringAtInstant( absolute, utcOffset ) {
    // ???
}

getParseableFixedOffsetStringAtInstant(
    Temporal.Absolute.from("2020-01-09T00:00Z"),
    "+09:00"
).replace(/(T\d+:\d+).*?([+-])/, "$1$2") ===
"2020-01-09T09:00+09:00";

TimeZone instance

Map an IANA time zone name into an object encapsulating all corresponding offsets and transition rules.

function getTimeZoneObjectFromIanaName( ianaTimeZoneName ) {
    // ???
}

Construct such an object directly from tzdata-compatible rules of arbitrary complexity (e.g., for use in testing).

function getTimeZoneObjectFromRules( rules ) {
    // ???
}

Sorting

Zoneless datetimes

Sort an array of zoneless Temporal.DateTime instances by the corresponding local date and time of day (e.g., for building a conference schedule).

function getSortedLocalDateTimes( datetimes, direction ) {
    // ???
}

Absolute instants

Sort an array of strings (each of which is parseable as a Temporal.Absolute and may or may not include an IANA time zone name) by the corresponding absolute time (e.g., for presenting global log events sequentially).

function getSortedInstants( parseableAbsoluteStrings, direction ) {
    // ???
}

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, with user-specifiable results for inputs that correspond with either zero or multiple local values in the target time zone (fail vs. clip-to-earlier [e.g., 01:30 to 00:59:59.999999999 when skipped or to the first repeated 01:30] vs. approximate-to-first [e.g., 01:30 to 02:30 when skipped or to the first repeated 01:30] vs. approximate-to-last [e.g., 01:30 to 02:30 when skipped or to the last repeated 01:30]). This could be used when updating the time zone for an appointment or scheduled reminder, or a local-time schedule.

function getInstantWithLocalTimeInZone(
        dateTime,
        timeZoneObject,
        disambiguationPolicy = "constrain" ) {
    // ???
}

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. This could be used when converting user-input date-time values between time zones.

function getParseableZonedStringAtInstantWithLocalTimeInOtherZone(
        sourceDateTime,
        sourceTimeZoneObject,
        targetTimeZoneObject,
        sourceDisambiguationPolicy = "constrain" ) {
    let instant = getInstantWithLocalTimeInZone(
        sourceDateTime,
        sourceTimeZoneObject,
        sourceDisambiguationPolicy
    );
    return getParseableZonedStringAtInstant(instant, targetTimeZoneObject);
}

getParseableZonedStringAtInstantWithLocalTimeInOtherZone(
    Temporal.DateTime.from("2020-01-09T00:00"),
    getTimeZoneObjectFromIanaName("America/Chicago"),
    getTimeZoneObjectFromIanaName("America/Los_Angeles")
).replace(/(T\d+:\d+).*?([+-])/, "$1$2") ===
"2020-01-08T22:00-08:00[America/Los_Angeles]";

UTC offset for a zoned event, as a string

Map a Temporal.Absolute instance and a time zone into the UTC offset at that instant in that time zone, as a string.

function getUtcOffsetStringAtInstant( absolute, timeZoneObject ) {
    // ???
}

getUtcOffsetStringAtInstant(
    Temporal.Absolute.from("2020-01-09T00:00Z"),
    getTimeZoneObjectFromIanaName("America/New_York")
) === "-05:00";

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

Map a Temporal.Absolute instance and a time zone into the UTC offset at that instant in that time zone, as a number of seconds.

function getUtcOffsetSecondsAtInstant( absolute, timeZoneObject ) {
    // ???
}

getUtcOffsetSecondsAtInstant(
    Temporal.Absolute.from("2020-01-09T00:00Z"),
    getTimeZoneObjectFromIanaName("America/New_York")
) === -18000;

Offset between two time zones at an instant

Map a Temporal.Absolute instance and two time zones into the signed difference of UTC offsets between those time zones at that instant, as a number of seconds.

function getUtcOffsetDifferenceSecondsAtInstant(
        absolute,
        sourceTimeZoneObject,
        targetTimeZoneObject ) {
    // ???
}

getUtcOffsetDifferenceSecondsAtInstant(
    Temporal.Absolute.from("2020-01-09T00:00Z"),
    getTimeZoneObjectFromIanaName("Etc/UTC"),
    getTimeZoneObjectFromIanaName("America/Chicago")
) === -21600;

Arithmetic

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

Map two Temporal.Absolute instances into an ascending/descending order indicator and a Temporal.Duration instance 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).

function getElapsedDurationSinceInstant(
        absoluteThen,
        absoluteNow,
        largestTimeElementInResult = "year" ) {
    // ???
}

(result => `${result.sign}${result.duration}`)(
    getElapsedDurationSinceInstant(
        Temporal.Absolute.from("2020-01-09T00:00Z"),
        Temporal.Absolute.from("2020-01-09T04:00Z")
    )
) === "+PT4H";

Nearest offset transition in a time zone

Map a Temporal.Absolute instance and a time zone object into a Temporal.Absolute instance representing the nearest instant (at-or-after vs. after vs. at-or-before vs. before) at which there is an offset transition in the time zone (e.g., for setting reminders).

function getInstantOfNearestOffsetTransitionToInstant(
        absolute,
        timeZoneObject,
        directionAndClusivity ) {
    // ???
}

Comparison of an instant to business hours

Courtesy @gilmoreorless at https://github.com/tc39/proposal-temporal/issues/26#issuecomment-513208398 , map a localized date and time of day into a time-sensitive state indicator ("opening soon" vs. "open" vs. "closing soon" vs. "closed").

function getBusinessOpenStateText(
        absoluteNow,
        timeZoneObject,
        businessHours,
        soonWindowDuration ) {
    // ???
}

Flight arrival/departure/duration

Courtesy @kaizhu256 at https://github.com/tc39/proposal-temporal/issues/139#issuecomment-510925843 , map localized trip departure and arrival times into trip duration in units no larger than hours.

function getTripDurationInHrMinSec( parseableDeparture, parseableArrival ) {
    // ???
}

Map localized departure time and duration into localized arrival time.

function getLocalizedArrival( departureAbsolute, duration, destinationTimeZoneObject ) {
    // ???
}

"A month from now"

Map a Temporal.Date instance into a new Temporal.Date instance that follows it by a possibly negative integer number of months, with user-specifiable policy of how to deal with results that don't exist because of variable month duration (fail vs. clip-to-fit vs. spill-into-following-month vs. any other identified strategies). This could be used for billing date calculation or anniversary reminders.

function plusMonths( date, months, disambiguationPolicy = "constrain" ) {
    // ???
}

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.

function plusAndRoundToMonthStart( date, delayDays ) {
    // ???
}

Schedule a reminder ahead of matching a record-setting duration

Map a Temporal.Absolute instance, a previous-record Temporal.Duration, and an advance-notice Temporal.Duration into a Temporal.Absolute instance corresponding with an absolute instant ahead of the instant at which the previous record will be matched by the specified window. This could be used for workout tracking, racing (including long and potentially time-zone-crossing races like the Bullrun Rally, Idatarod, Self-Transcendence 3100, and Clipper Round The World), or even open-ended analogs like event-every-day "streaks".

function getInstantBeforeOldRecord(
        startAbsolute,
        previousRecordDuration,
        noticeWindowDuration ) {
    // ???
}

Nth weekday of the month

#240 (comment)
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).

function getWeeklyDaysInMonth( yearMonth, dayNumberOfTheWeek ) {
    // ???
}

getWeeklyDaysInMonth(new Temporal.YearMonth(2020, 2), 1).join(" ") ===
"2020-02-03 2020-02-10 2020-02-17 2020-02-24";
getWeeklyDaysInMonth(new Temporal.YearMonth(2020, 2), 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.

function countPrecedingWeeklyDaysInMonth( date ) {
    // ???
}

countPrecedingWeeklyDaysInMonth(Temporal.Date.from("2020-02-28")) === 3;
countPrecedingWeeklyDaysInMonth(Temporal.Date.from("2020-02-29")) === 4;

Isolation

Attenuation

Create an object that supports exactly the same interface as Temporal and is indistinguishable from it (even though side-door means such as Function.prototype.toString) except that it provides no mechanism by which code could use it to determine that the host environment is not executing within a date, time, time zone, and tzdata edition under the control of the creator, without costing the creator any capabilities provided by the native Temporal. This has use for secure environments like SES, but also for purely functional environments like Elm (cf. #103) and for testing.
NOTE: If Temporal were 100% pure and deterministic (like e.g. Array), then the unmodified (except for perhaps being deeply frozen) Temporal object itself would serve this purpose.
cc @erights

function getAttenuatedTemporal( Temporal, attenuations ) {
    // ???
}

Extension

Extra-expanded years

Create a Temporal derivative that supports arbitrarily-large years (e.g., +635427810-02-02) for astronomical purposes, ideally without requiring modifications to year-agnostic interfaces such as Time (but still supporting e.g. UnlimitedTemporal.Time.from("10:23").withDate("+635427810-02-02")).

function makeExpandedTemporal( Temporal ) {
    // ???
}

@sffc
Copy link
Collaborator

sffc commented Nov 22, 2019

Thanks for the cookbook! FYI, of the 18 examples here, I believe only 3 require calendar information:

  • Unit-constrained duration between now and a past/future zoned event
  • "A month from now"
  • Push back a launch date

This matches with my instinct that we should support calendar systems but we don't need to reshape the whole API around them. Rather, we can introduce a calendar field in places it is needed, but without adding any radically new types.

@sffc
Copy link
Collaborator

sffc commented Jan 9, 2020

Just posting here as FYI: we have an internal client who wants to do something that is difficult with the current JavaScript Date class. The client wants to display midnight in an arbitrary IANA timezone in the user's local timezone. For example, initialize a datetime as midnight in America/Chicago, and then display it in America/Los_Angeles. This is difficult in JavaScript Date because it doesn't support IANA timezones. This could be another cookbook example.

@gibson042
Copy link
Collaborator

Good news, this is composable from the existing recipes! I've added it as an explicit scenario with implementation: Preserving absolute instant

pipobscure pushed a commit that referenced this issue Jan 24, 2020
* Addition of cookbook

* Adding parsableZonedString

* * Cookbook link in readme
* Prettier did some tidying up

* Update cookbook/absoluteFromLegacyDate.mjs

Co-Authored-By: Richard Gibson <[email protected]>

* Update cookbook/absoluteFromLegacyDate.mjs

Co-Authored-By: Richard Gibson <[email protected]>

* Update cookbook/absoluteFromLegacyDate.mjs

Co-Authored-By: Richard Gibson <[email protected]>

* Update cookbook/absoluteFromLegacyDate.mjs

Co-Authored-By: Richard Gibson <[email protected]>

* Update cookbook/getParseableZonedStringAtInstant.mjs

Co-Authored-By: Richard Gibson <[email protected]>

* * update the cookbook examples to have no import
* fix index.mjs to add Temporal to the global objectr
* update readme

* adding note about how to run cookbook files

Co-authored-by: Richard Gibson <[email protected]>
@ptomato
Copy link
Collaborator

ptomato commented Jan 24, 2020

I think this was accidentally closed by the "Addition of cookbook fixes #240" commit message, in any case there are still more cookbook examples to add, so let's keep this open!

@sffc
Copy link
Collaborator

sffc commented Jan 31, 2020

Another use case from an internal client. They want to:

  1. Get the IANA timezone for the current OS
    • Yes: Temporal.now.timeZone()
  2. Get the GMT offset of the timezone
    • Yes: timeZone.getOffsetFor(Temporal.now.absolute())
  3. Know whether the timezone is daylight time or not
    • We have methods for finding the next summer time transitions, but do we have a way to determine whether or not the timezone is currently in summer time?

I will continue posting use cases here as we get them. They come organically from internal email lists and question boards.

@ptomato
Copy link
Collaborator

ptomato commented Feb 1, 2020

How would they define whether a time zone is "in summer time"? I suppose it depends on the location of the time zone (northern/southern hemisphere) as well as whether that time zone does a daylight saving time transition at all? My understanding is also that time zones can have offset transitions for other reasons than daylight saving.

@pipobscure
Copy link
Collaborator

DST is not a universal concept. There are places that don’t have anything like it; there are those that have multiples. Also it’s not even the case that one offset correlates to astronomical time (noon = highest sun) in any way. So whether a time is DST or not is actually pretty meaningless.
The IANA database has that info in it in a rather arbitrary way. So we could just go with that. But I don’t think it’s actually used anywhere. So I question the utility.

TL;DR “Is this summertime” is a meaningless question

@gilmoreorless
Copy link
Contributor

Know whether the timezone is daylight time or not

I agree with @pipobscure that the question becomes meaningless when it's dug into.

1. Why do they need to know if it's in "daylight time"?

  • Is it to do calculations with offsets? In that case, use other methods to get the offsets without bothering about DST.
  • Is it to display the correct time zone abbreviation? Then that's a formatting issue better handled by Intl.DateTimeFormat.
  • Is it to know the "base offset" of a time zone? That could be a good use case, until you dig into the history of time zones.
    • What exactly defines a "base offset" anyway? Many places have equal parts of the year with DST and without it (i.e. 6 months between transitions).
    • What happens when the "base offset" itself is changed? There have been several instances in the past few years of regions moving to what they call "permanent DST". For example, moving from +00 in winter and +01 in summer, to +01 all year round with no DST.

2. "Daylight saving time" is NOT the same as "summer time".

This discussion has been circling on the tzdb mailing list for a few years now (where there's a suggestion to redefine "DST" as "daylight shifted time").

The real catch comes from countries that define their timekeeping laws to say that "standard time" is in summer, with "alternative time" (or some other name) in winter. That is, they have negative DST. The main examples of this are Ireland (+01 "standard" in summer, +00 in winter), Nigeria, and Morocco. Morocco is a particularly interesting case because they recently changed from +00/+01 to a "permanent" +01... except they still need to switch back to +00 during the month of Ramadan.

See also these threads in tzdb list archives from the past year or so:

The most relevant quote comes from the last thread listed above (specifically from this reply):

I preferred whichever reference used the terms "standard time" and "alternative time"

That was POSIX, before it was changed (not yet published, but applied,
"alternative time" will no longer appear when the next edition is pubished)
based upon some input from this direction.

But, while I agree it was better, it's not good enough either - first
because there's not just two, and second, because there's no point giving
these things a name - whatever time it is (commonly agreed, whether
legislatively backed or not) is "standard time" - that's what it means
to be "standard". The only other thing we can (at least currently) rely
upon is that there is a current offset from UTC (ie: that UTC is stable,
and the time anywhere is, for a short while anyway, a constant offset
from UTC - positive or negative (or 0)).

Some juristictions provide names for the various times, mostly for convenience
around the transitions, but none of them are very useful, and here we really
cannot rely upon any such thing existing.

@sffc
Copy link
Collaborator

sffc commented Feb 4, 2020

It appears the client is trying to interop with the win32 function SetDynamicTimeZoneInformation, which requires a struct containing the following fields, which all appear to be required:

typedef struct _TIME_DYNAMIC_ZONE_INFORMATION {
  LONG       Bias;
  WCHAR      StandardName[32];
  SYSTEMTIME StandardDate;
  LONG       StandardBias;
  WCHAR      DaylightName[32];
  SYSTEMTIME DaylightDate;
  LONG       DaylightBias;
  WCHAR      TimeZoneKeyName[128];
  BOOLEAN    DynamicDaylightTimeDisabled;
} DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION;

So in order to interop with this API, you need to know which offset ("bias") is for daylight time and which offset is for standard time.

Probably not super important for ECMAScript, but worth pointing out. The existence of this win32 API suggests that this distinction between which offset is daylight or not may also appear in other places in the tech ecosystem.

@jasonwilliams
Copy link
Member

Taken from @gilmoreorless

I’ve followed this proposal from the start, but I had to ignore it for a while (for reasons unimportant to the issue at hand). I’ve now caught up on everything that’s happened in the past few months, and I have some concerns.

It’s great that the proposal is gaining traction and going through the TC39 process. However, there are still barely any actual use cases listed. This proposal was designed to replace the shortcomings of Date, because Date doesn’t easily handle many real-world scenarios without a fair amount of hackery (such as mis-using the UTC timeline). But if we don’t explicitly list out these scenarios and use cases, how can we be sure that the proposed API is actually covering them?

I notice that this particular issue is still unresolved after 2 years. It’s listed in the “finalize documentation” project, but I’m not sure if that comes before or after “finalize spec text”. I very strongly feel that locking in the API before documenting the use cases is the wrong way around. The README currently (commit 4a0f079 at time of writing) links to examples.md, but that only contains a single scenario, with the rest being API documentation.

Rather than just complaining, I’ll briefly list some scenarios I’ve personally had to deal with in production systems. I can provide more details/clarification if needed, but I want to make sure the expanded information would be useful first. Some of these cases are well-known problems with many library solutions available, but it’s still worth listing them for completeness.

1. Abstract date logic which is not tied to any specific location

(I’m avoiding the “Local”/“Civil” prefix naming debate here)

  1. Showing a generic calendar control, with any day beyond “today” disabled and not selectable.

  2. Simple calculations of “how many days until {future date}”.

  3. Integration with wearable devices.

2. Dealing with dates and times in a time zone that differs from the user’s browser

  1. A graph showing activity for a storage tank in a fixed location. The requirement was to always start the graph at midnight for the tank’s location, regardless of the viewer’s time zone.

  2. Calculation of recurring schedules for a specific location.

    • “Email this generated report at 4pm daily, Brisbane time.”
  3. Calculation of how recurring schedules will be affected in other locations.

    • “This meeting is always at 2pm in Sydney. What time will that be for our remote team in Ho Chi Minh City before and after Sydney’s daylight saving shifts?”
  4. Trying to book a meeting or event that works for 4 different time zones, with a display of the relative times in each zone (à la World Time Buddy).

3. Combinations of both

  1. Show opening hours for a chain of stores spread across multiple time zones.

    • e.g. The default is for all stores to be open from 8am to 6pm in their respective locations.
    • Viewing the website for a particular store needs to show if it’s currently open/closed, or closing soon. This means taking the abstract shared closing time and applying it to the time zone of the specific store.

I don’t want to be the only person providing these, though, as it’s just one person’s perspective. There have been some other examples scattered among various issues that I’ve seen so far:

  • @kaizhu256 has provided a use-case in #139 (comment) regarding flight durations and take-off/landing times.
  • @ljharb recounted a bug with scheduling a future event in #78 (comment)
  • I’m also hopeful that @ljharb could provide some more scenarios based on the things Airbnb have had to deal with.

We could specify a list of chosen scenarios, with examples of how to do them with Temporal vs currently-existing methods (some of which might only be possible with third-party libraries). But first we have to decide on what that list contains.

ryzokuken pushed a commit that referenced this issue May 5, 2020
This one is a bit different, since it runs in the cookbook page directly
in the browser.

See: #240
ryzokuken pushed a commit that referenced this issue May 5, 2020
Another in-browser example, this one is inspired by an HTML form on
timeanddate.com.

See: #240
ptomato added a commit that referenced this issue May 7, 2020
ryzokuken pushed a commit that referenced this issue May 8, 2020
ptomato added a commit that referenced this issue May 8, 2020
@kaizhu256

This comment has been minimized.

@kaizhu256

This comment has been minimized.

@pipobscure

This comment has been minimized.

ryzokuken pushed a commit that referenced this issue May 14, 2020
ptomato added a commit that referenced this issue May 14, 2020
Use cases from #561 suggested by @justingrant

Closes: #561
See also: #240
ptomato added a commit that referenced this issue May 15, 2020
Use cases from #561 suggested by @justingrant

Closes: #561
See also: #240
ptomato added a commit that referenced this issue May 18, 2020
Use cases from #561 suggested by @justingrant

Closes: #561
See also: #240
@ptomato
Copy link
Collaborator

ptomato commented May 20, 2020

Issues have now been opened for all the discussions raised about changing the API based on writing the cookbook examples.

Here are the cookbook examples that haven't been written yet, that would be good to have. In particular for exercising the custom time zone and custom calendar APIs when they are merged into main.

Once those are written I think we can close this issue, or else close it now and open separate issues for those three.

Isolation

Attenuation

Create an object that supports exactly the same interface as Temporal and is indistinguishable from it (even though side-door means such as Function.prototype.toString) except that it provides no mechanism by which code could use it to determine that the host environment is not executing within a date, time, time zone, and tzdata edition under the control of the creator, without costing the creator any capabilities provided by the native Temporal. This has use for secure environments like SES, but also for purely functional environments like Elm (cf. #103) and for testing.
NOTE: If Temporal were 100% pure and deterministic (like e.g. Array), then the unmodified (except for perhaps being deeply frozen) Temporal object itself would serve this purpose.
cc @erights

function getAttenuatedTemporal( Temporal, attenuations ) {
    // ???
}

Extension

Extra-expanded years

Create a Temporal derivative that supports arbitrarily-large years (e.g., +635427810-02-02) for astronomical purposes, ideally without requiring modifications to year-agnostic interfaces such as Time (but still supporting e.g. UnlimitedTemporal.Time.from("10:23").withDate("+635427810-02-02")).

function makeExpandedTemporal( Temporal ) {
    // ???
}

TimeZone instance

Construct such an object directly from tzdata-compatible rules of arbitrary complexity (e.g., for use in testing).

function getTimeZoneObjectFromRules( rules ) {
    // ???
}

@sffc
Copy link
Collaborator

sffc commented May 20, 2020

Hooray! +1 for opening a new issue for the remaining cases, and close this issue as fixed. We (you) deserve it!

@gibson042
Copy link
Collaborator

I apologize for not having time for Temporal lately, but I wanted to address the big comment here in advance of reviewing the full cookbook.

  • getInstantWithLocalTimeInZone - Absolute.prototype.inTimeZone() covers this, but the text mentions several more disambiguation policies than we support. @gibson042, did you intend for all of these to be included ?

Yes, the cookbook getInstantWithLocalTimeInZone needs sufficient configurability for skipped wall-clock times to reject vs. clip to latest preceding instant vs. replace with equivalent post-transition instant and for repeated wall-clock times to reject vs. use first vs. use last. And if that's too difficult with the existing API surface area, then we have a signal that the existing surface area is insufficient.

I don't think polyfill implementation issues should block cookbook recipes coded against the documented API, but presumably this was fixed by #513 anyway.

  • plusMonths — This is identical to Temporal.Date.plus or Temporal.Date.minus. We recently removed disambiguation: 'balance' from arithmetic methods. @gibson042, are "spill-into-following-month" semantics still considered useful enough that we should bring them back in this cookbook example? Otherwise I think it's obvious enough that you should use plus() or minus() here.

Yes, the interesting aspects of this recipe are the disambiguation. plusMonths needs sufficient configurability for addition to reject vs. clip vs. overflow-into-following and for subtraction to reject vs. clip. It should be possible to select among at least the following behaviors by changing only the configured policy:

  • 2021-03-31 plus one month yields 2021-04-30, and 2021-03-31 plus −1 months yields 2021-02-28
  • 2021-03-31 plus one month yields 2021-05-01, and 2021-03-31 plus −1 months yields 2021-02-28
  • 2021-03-31 plus one month yields an error, and 2021-03-31 plus −1 months yields 2021-02-28
  • 2021-03-31 plus one month yields an error, and 2021-03-31 plus −1 months yields an error
  • There's a lot of Temporal.Something.from() involved, making for long lines. For example, to take a DateTime and get midnight on that day, it's either dateTime.getDate().withTime(Temporal.Time.from('00:00')) (or, even longer if you want to avoid the from(), dateTime.with({ hour: 0, minute: 0, second: 0, microsecond: 0, millisecond: 0, nanosecond: 0 })). It might be more readable to reverse our decision not to take strings in methods like withTime(), plus(), etc.

I agree, .withTime("00:00") seems both friendly and unambiguous. That should be the case for every with* method, which can thus follow a common algorithm pattern of initial input type-casting (no-op when a brand-check against valid types passes, otherwise lookup and calling of <TemporalType>.from).

  • ZonedDateTime was removed because it can easily be replaced by a { dateTime, timeZone } box. I'm not necessarily suggesting we reconsider that, but I did note that it's tempting to use the string returned by absolute.toString(timeZone) instead of the box. This is not necessarily correct, because even though Temporal will still deserialize the string correctly if the time zone changes its offset rules, other programs might not.
  • Likewise, another box that seems to be commonly needed is { sign, duration }. I don't know if it was ever considered to have signed Durations but sign < 0 ? foo.minus(duration) : foo.plus(duration) and sign = Temporal.Foo.compare(one, two) < 0 ? -1 : 1 are pretty cumbersome.

Excellent; this is precisely the kind of insight that we were hoping would be provided by having the cookbook. I'm currently inclined towards both Temporal.ZonedDateTime and signed Temporal.Duration, although the latter comes with serialization/deserialization concerns because ISO 8601 durations are unsigned (resulting in some libraries supporting/preferring negative-valued time elements such as a) PT1H-210M while others support/preferring only a single leading negation such as b) -PT2H30M or conceivably even c) P-T2H30—I prefer the latter model, c specifically if there's a gun to my head, but all of the syntax changes make me uncomfortable because 8601 is very particular about designators).

@ptomato
Copy link
Collaborator

ptomato commented Jun 1, 2020

Yes, the cookbook getInstantWithLocalTimeInZone needs sufficient configurability for skipped wall-clock times to reject vs. clip to latest preceding instant vs. replace with equivalent post-transition instant and for repeated wall-clock times to reject vs. use first vs. use last. And if that's too difficult with the existing API surface area, then we have a signal that the existing surface area is insufficient.

In the meantime I did write a cookbook example that does this: https://github.com/tc39/proposal-temporal/blob/main/docs/cookbook/getInstantWithLocalTimeInZone.mjs (It became possible after adding Temporal.TimeZone.prototype.getPossibleAbsolutesFor().)

I don't think polyfill implementation issues should block cookbook recipes coded against the documented API, but presumably this was fixed by #513 anyway.

Yes, this was fixed.

Yes, the interesting aspects of this recipe are the disambiguation. plusMonths needs sufficient configurability for addition to reject vs. clip vs. overflow-into-following and for subtraction to reject vs. clip. It should be possible to select among at least the following behaviors by changing only the configured policy:

  • 2021-03-31 plus one month yields 2021-04-30, and 2021-03-31 plus −1 months yields 2021-02-28

  • 2021-03-31 plus one month yields 2021-05-01, and 2021-03-31 plus −1 months yields 2021-02-28

  • 2021-03-31 plus one month yields an error, and 2021-03-31 plus −1 months yields 2021-02-28

  • 2021-03-31 plus one month yields an error, and 2021-03-31 plus −1 months yields an error

These are covered by, respectively:

  • plus({months: 1}), minus({months: 1}) (i.e. constrain, the default)
  • plus({months: 1}, {disambiguation: 'balance'}) (which we removed, discussion in Balance disambiguation when subtracting months #344), minus({months: 1})
  • plus({months: 1}, {disambiguation: 'reject'}), minus({months: 1})
  • plus({months: 1}, {disambiguation: 'reject'}), minus({months: 1}, {disambiguation: 'reject'})

I'm not sure I agree that it's critical to have a cookbook recipe for something we couldn't figure out a use case for. But let's continue that discussion in a new issue?

I agree, .withTime("00:00") seems both friendly and unambiguous. That should be the case for every with* method, which can thus follow a common algorithm pattern of initial input type-casting (no-op when a brand-check against valid types passes, otherwise lookup and calling of <TemporalType>.from).

Prior discussion in #237, current feedback thread in #592.

Temporal.ZonedDateTime and signed Temporal.Duration

Current feedback threads in #569 and #558.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.