[Pitch] Extending Date with methods to get specific new dates

@itaiferber
Let us agree that:

gregorian.nextDateOf(2011-12-29) = 2011-12-30
gregorianInSamoa.nextDateOf(2011-12-29) = 2011-12-31

which means gregorian is the Gregorian calendar as it is known in the world, ignoring any local exceptions (which we specify explicitly if they are applicable).

I claim that:

gregorian.nextDateOf(2024-12-02) = 2024-12-03
gregorian.nextDateOf(2011-12-29) = 2011-12-30

In your point (3) above you say that the answer depends on DST, timezone, locate, other. Is there such a combination which gives another answer for the first? For the second you said in Samoa, but if gregorian is defined as the globally known Gregorian, is there any other combination which gives another answer? Same for other dates.

Ah, but the surprising date behavior that @itaiferber called out wasn't due to Samoa using a non-Gregorian calendar and deciding to align with the Gregorian calendar, it was due to a decision to change the time zone offset used by their geo-political region from UTC–11 to UTC+13.

Of course, if you go so far as to say, well, given a fixed calendar, fixed time zone, fixed location, for a fixed range of the calendar, can't we perform determinate calculations on such 'timeless' dates, then, perhaps (though I'm prepared for Dave to show me the exception :slightly_smiling_face:). But that's defining away a whole lot of complexity and I'd probably need substantial convincing that such specific utilities are of sufficient use to deserve a place as general API surface! IMO better to provide the general calendrical facilities and leave it to users to specify for their narrow cases "I am okay with 'next day' assuming a fixed calendar, time zone, location, and date range for my use".

4 Likes

To be clear, we're not talking about "dates divorced from a concept of time", we're talking about "dates without specifying time of day". i.e., the same concept of loose equatability you're talking about.

I understand. I don't disagree that this is how the Gregorian calendar currently defines these operations. The point is that this information is not useful or applicable outside of a very narrow and limited use-case, and therefore, does not warrant being exposed uniquely as general-purpose API because it is not general-purpose. Whereas, the operation that you define as

for all of its complexity, is the real-world behavior that most people need.

The Foundation Calendar APIs expose the complexity required to get correct answers; it is possible to get the results you want out of that same API, even if it's more verbose.

1 Like

@QuinceyMorris
What you don't want to have in system library is already there. When I feed-in "2024-12-02" in one of the standard functions, it is accepted and it returns a Date. I don't get nil or an error saying "the standard library works only with points-in-time". Moreover, it silently fills-in time and place (timezone), as if "2024-12-02" is the same thing as a single point-in-time at the default timezone, which by the way is one fraction of a second apart from another date.

Similarly, when I convert a Date to "YYYY-MM-DD" format, I don't get an error saying "the standard library stores and processes only points-in-time, which cannot be converted to a wall calendar date without time".

No, you're misrepresenting my words here. Time of day isn't the same thing as a point in time. Times of day also need a concept of equatability related to points in time, just likes dates. Points in time are measurements of elapsed time since a reference date, as is fundamental to the Date type.

Apologies, I guess I must've misunderstood your post, then. I'm not sure in what way a "date without a time of day" differs from

In any case, I do agree that the correct system library approach is to provide localized, point-in-time APIs upon which you can build more specialized tooling if needed.

A hypothetical example: suppose a user schedules an event in the app to, say, transfer money between two bank accounts on a future date and then he travels to another country which is 12 timezones later, wouldn't the app fail to show the event on proper date? In contrast, saving the date in point-in-time format (e.g. start of the date) should work.

We can only assume you're referring to DateFormatter. There is most definitely the justification that calendar, locale, and timezone are implicitly unwrapped optionals, as explained throughout this thread. Also, is Foundation and not stlib.

Or, if you're manually setting DateComponents, well then that still requires Calendar and has been explained throughout this thread.

1 Like

Apologies for being a bit abrupt. Let's take 3 parallel examples to show the difference:

  1. I ask you to schedule a payment for me on 2024-12-2. You can't do that with any absolutely precise certainty without knowing what that date means to me — what locale I'm in, for example, along with the other sorts of contextual information that's been mentioned in this thread. "2024-12-2" is a date without a specified time of day.

  2. I ask you to schedule a payment for me on 2024-12-2 at 6pm. Same deal, and you need pretty much the same contextual information as case #1. "2024-12-2 at 6pm" is a date with a specified time of day.

  3. I ask you to schedule a payment for me on Date(timeIntervalSinceReferenceDate: <some elapsed time in seconds>). You can do this with absolute precision, because it's the same point in time for you as it is for me.

Nevertheless, for cases #1 and #2, if you wanted to do something reasonable without additional information, then you could schedule the payment sometime on your 2024-12-2 (for #1) or at your 2024-12-2 at 6pm (for #2). Wherever you are located in the world, you'll handle it within about 24 hours of my expectation, so … thank you!

Your action — converting from my date components to a point-in-time, then from point-in-time to your date components, then looking at your watch — is what constitutes the "equatability" of dates, with or without time of day specified. There's no equatability of that kind without a shared comparison of points-in-time.

Conversely, if there's no equatability — for example, unknown to you I meant 2024 spins, 12 twists and 2 tumbles on the Venusian calendar — there's a high probably the payment isn't going to happen anywhere near the time I hoped for.

The easy parts of the Date APIs are the functions that do some variant of adding a time interval to a point in time, no context needed. The hard parts are the functions that convert to or from component-wise years, months, days, hours, minutes and seconds, using context derived from elsewhere. That's all good stuff to have as standard functions, and I want them in the Date APIs.

There are no parts of the Date APIs that add component-wise years, months, days, hours, minutes and seconds to points in time. I don't want those functions on Date because they're too unpredictable semantically.

Because Apple has done quite a bit of work in the area of dates over the years, there are APIs to do date and time component calculations, using DateComponent and Calendar values. However, these aren't quite the functions you were asking for, because they — verbosely — need additional options to explain how you want the calculation to behave for your particular combination of components. Get rid of the verbosity, and you'd again end up with unpredictable semantics.

1 Like

The moment of execution of a transaction inherently refers to a point-in-time. There are many other cases in which only the date part is documented, and it is sufficient.

  • My salary comes in the first day of each month (no need to say at what time, no need to say at which TZ I am each month).
  • Last January there was a lot of snow (no need to say at what time it started and when it finished).
  • Something is expected to happen in year 2050 (no need to say at what time, not even at which month). Even 2050 is approximate in this case.
  • The next report will be released two months later (no need to say at which day, at what time, at what second, at what nano-second).

For the use cases in which a precise time is needed (mostly timestamps), the representation of time as Date, or any other Int/Double representation, is a convenient approximation of the physical time. I happen to be an expert in hardware clock generation and distribution. The clock timing, as a physical quantity, is modelled with jitter and phase noise. It is a statistical event. What is equitable in a statistical distribution? The discretized representation of time is only an illusion of precision, which is good enough for the applications (if not you need a better clock). The frequency accuracy of a typical crystal clock is a few ppm (parts per million). Assuming that one moment you have a perfect sync with some absolute clock, after 1 ms you will have an error of more than 1 ns. Good enough for most applications, not enough for physics experiments and for some engineering applications. In other words, many of the bits stored in a Date are pure noise from physical point of view.

Nevertheless Date is sufficient for the representation of physical time, as used in almost all computer applications. Similarly, the date part is good enough for many applications. You try to convert everything to a Date because this is what you have, this does not make it a physics-grade standard.

I'm afraid you misunderstood my quesiton. I'm also working on a financial planning app, so I completely understand why you brougt up the topic. Let's supposes user schedules a transfer from account A to account B on a specific date so that account B has engouh balance to pay for credit card bill. If the app shows the tranfer on a later date when user travels to a different time zone, I don't think that's an expected behaivor. I think @QuinceyMorris raised a similar point in his post above.

PS: I do save the date in point-in-time format in my app, but it's by accident. I haven't realized this scenario until I read this thread today. I think the above example might help to show why point-in-time is necessary (though one may argue that user shouldn't depend on app like this for accurate schedule).

I agree with this entirely! For this type of operation, both (1) and (2) absolutely require additional information, if the intent is to schedule an event for you in your local time. A good example of this is a calendar event, which necessarily requires a date, time, and time zone in order to get right (and if I wanted to be extra careful, I would even take note of the version of the time zone rules in use at the time that you scheduled the event, so that if they change for your time zone, I can try to recreate the event as appropriate, or prompt you to make a decision about the change!).


Let's riff off of (1) here, though, to show off the type of date-without-time that I was trying to describe. Let's say you're trying to schedule an automated payment for your rent/mortgage to make your housing payment; my payment, for example, is scheduled for the first of the month, every month. The time here is unspecified: the payment processor will presumably draw the funds at some point during that day (or maybe even the day after!), but that's when I've requested it to be scheduled.

Now, I want to use my personal finance app to make sure I have enough money set aside in my bank account to cover this payment, and one way to do this is to set up a scheduled recurring transaction for the first of the month for the expected amount — note that the app does not make the transaction, but is making a record of the transaction. Just like the payment, this record has no set time.

If I open the app any day of the preceding month, I can see that transaction as "Pending": it's still in the future, and not yet valid to approve as "yep, this has happened". If I open up the app on the first of the month, and look at the list of transactions, though, that scheduled transaction will now be ready for me to adjust, and approve for entry.

Practically speaking, how do I make this work?

  • I could go with (3): at the time of creating the scheduled transaction, I can calculate the start of the day in my local time zone, get that Date as a timestamp, and store it. Any time before that timestamp, the transaction won't be ready; any time after, it will. Trivial to store, load, and check
    • However: if between when I scheduled the transaction and when the day arrives, time zone rules change, or I actually physically change time zones, that time stamp will no longer refer to the start of the day where I'm physically located. If I open up the app, it might not be ready to approve and enter even though for me, the day has already started! (Or, it might be available to approve and enter early, and if I do that, the recorded date might be wrong)
  • I could go with (2): at the time of creating the scheduled transaction, I can prompt for a time of day (and presumably time zone) for entry, just like a calendar event. Storing both the timestamp and a time zone makes it easier to adjust for changes (and translating that time for the local time zone if that changes too)
    • But: this is a hassle — as a user, I don't care what time the transaction is scheduled for, so entering it is a pain. And, if I pick, say, "noon on 2025-01-01" but open up the app at 9:00 AM local time, the transaction still won't be ready even though the date itself has arrived
  • I could go with (1): at the time of creating the scheduled transaction, I can store just the yyyy-MM-dd data of the date (e.g., as a string), without a time or a time zone; when I later launch the app, I can read that representation, get the yyyy-MM-dd representation of the current date in the Gregorian calendar in the local time zone, and compare them (or, if I do want to rely on point-in-time calculations, parse the representation as the start of the day for yyyy-MM-dd in the Gregorian calendar in the local time zone, and compare that timestamp to Date.now)
    • This has the benefit of not requiring recording a time, or having to deal with time zones: if the user opens up the app at any time on yyyy-MM-dd in their local time zone, they'll get a match

Dropping the time-of-day information altogether has advantages here that specific-point-in-time representations might not. (If you squint, you could see this as isomorphic to storing a noon-on-a-specific-date-with-time-zone timestamp with approximate date equality to check "is that timestamp today", but you might still need to account for time zone changes, which is a hassle.)

But, again, this is a very specific application of logic for a very specific purpose. This fundamentally does not work if:

  1. You need something to happen at a specific time
  2. You need something to happen regardless if the app is open

e.g., you could not use this scheme if the app were actually responsible for making the transaction, instead of storing a record of it.


This sounds to me like a different scenario than what I'm describing above (though if I've misunderstood, do let me know!): if you need to show something that either has or has not already happened from an external data source, then presumably, you would store the time stamp of when that did occur when you know about it.

For example:

  • The scheme I describe above is how we (roughly) handle user-created pending transactions, but for our customers who also import their transactions from their credit card or bank institutions, the user-created transaction can, at a later point, be linked to the "real" transaction as reported by the institution
  • For customers who set up credit card data imports, transactions which are "pending" from the institution side (e.g., the transaction has been made but not yet cleared) are shown as such until the next data import comes in which shows that the transaction has actually cleared

The specifics of what you store and how depend very much on the exact thing you're trying to do. (With more details on what you're looking to achieve, I can say more.)

2 Likes

In any case, all I've been hoping to express here is that:

  1. There is a valid use-case for storing dates without times or time zone information
  2. That use-case is very narrow, and should not inform general-purpose calendar APIs

(For this reason, we have our own GregorianCalendar/GregorianMonth/GregorianDate infrastructure internally on top of Calendar, and I don't think it would make sense or be safe to expose as API at the system level anywhere)

2 Likes

I later realized saving point-in-time has its own issues. One is as you described in option 3 above. While it's possible to modify code to support representing a day using any time in that day, instead of start of the day, it's not ideal. I think the root cause is inherent in a date only (no accurate time) system - it's vague by its nature. The example of different timezones just amplifies it. Event if there is no different timezone involved, it's still not suitable for accurate schedule. For example, an app which represents transactions using days has no idea about their order even though it can pretend to know it by showing inbound transations before oubound transactions in UI. Another example: adding timezone information doesn't help because there isn't accurate 1:1 mapping between days in different time zones. I guess that's probably one of the reasons why a date only libarary doesn't exist unless we accept those limitations.

1 Like