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

A quick search of the tzinfo mailing list didn’t turn up the incident you referenced, but I did find a case in 2012 where a country changed the definition of the weekend from Saturday/Sunday to Friday/Saturday. So depending on politics, 1 week after a weekend day may not itself be a weekend!

2 Likes

It was a historical case in a particular city in France iirc. I've been out of the date-time domain for quite a few years now though, Dave Delong would likely remember better than me.

1 Like

After I fix the calendar (e.g., Gregorian), and knowing that for many years before and a few years after, nothing irregular happens, why do I need to do through point-in-time (and waste a few more bytes and CPU cycles), instead of a direct calculation? What you are saying is that because there are irregularities we have to go the hard way also for the common cases where there is an easy way.

You cannot know if “the easy way” is correct without reference to a concrete point in time.

1 Like

I don't understand this argument. For common cases you can pre-calculate a safe range in which there are no surprises. Going to an absolute point-in-time and back does not help you much, the irregularity is in the calendar and you have to know it, either way. How a conversion to absolute time makes the problem easier, and why is it necessary if you are in a safe range?

Sure, let's allow that the exact time doesn't matter.

But you still haven't answered the objection. How does your app know when the due date arrives? I'm asking in the most mundane sense, nothing philosophical.

• If your app is relying on the OS to tell you, it's going to tell you at a point-in-time that the date has arrived. Probably at midnight, in any rational OS.
• If your app is watching the clock, it's going to be checking whether now (a point-in-time is within the due date.

I don't see any situation where your app is genuinely going to be handling only plain dates, and not having to deal with points-in-time (or, equivalently, events).

2 Likes

Fundamentally you cannot do it, because calendars are political creations and are subject to human forces. They are not regular and change on short notice.

This part needs an association to point-in-time, although only an approximate one. Adding a day, month, etc., does not need any association to point-in-time. Entry and display of a date does not any association either.

I didn't say that association to point-in-time is not needed, I said that several date calculation can be done in an easier way. When it comes to time instead of a date, of course you need a point-in-time.

And how conversion to point-in-time resolves the calendar irregularities?

In principle you know the whole history of a calendar until today, and someone has already rectified all past irregularities (if not, point-in-time cannot do this magic). So in principle you can write down a long list with the names of each day, one after the other, with additional disambiguation info if needed. You can simply go through this list and answer questions about dates at the granularity of a day. For big ranges in this list you can do math and avoid the listing, for irregularities you need some kind of dictionary.

What is missing?

Because political bodies make decisions about timekeeping relative to a specific point in time. The most common by far is choosing whether, when, and how to observe Daylight Saving Time. The United States has changed its rule for this at least once in my lifetime, and some polities relitigate this decision up to twice a year. But the decision is of the form “we will transition to Daylight Saving Time at the instant known as such-and-such time on the day known as Somthingary Nth.”

Perhaps you’re unaware that this is already how it works. IANA maintains a database known as tzinfo, which ships as part of Apple and Linux OSes and is frequently updated. (I believe Microsoft maintains its own separate timezone database.)

5 Likes

For reasons, it is illegal to serve alcohol in Norway after a certain time at night, and this time might be different in different cities. When I first moved to Trondheim as a 19-year old, the cut-off time there was 02.30 at night.

I remember some time that fall on a Saturday night, that they stopped servering after 02.30, kept the taps closed for half an hour until 03.00, which due to DST immediately became 02.00, in which the taps re-opened for another half hour.

That memory has forever altered my perception of time-as-a-cultural-human-construct vs. time-as-a-natural-phenomenon.

12 Likes

Perhaps you are talking about a different problem. I am talking about date calculations, in which the input is a date (the name of a day without time information) and the output is also a date. DST does not affect the sequence of days. You need it for time calculations, you don't need access to it for date calculations. For other irregularities you may need to store some information at the granularity of a day, or you may ignore it because it has no impact to the sequence of days.

So after you have the list of all irregularities at the granularity of a day, my question is what is missing. Why do you need to convert to point-in-time and back at every date calculation?

I work in the same space, on the same type of application, with an identical use-case for this type of date information, so I know where you're coming from. I suspect that some of the response you've seen in this thread are because this is a very niche (but valid!) use-case for date information, so it's worth describing in a bit more detail.

  1. In this scenario, "date"s are really labels that customers apply to their own information, for all that entails. The date that a transaction occurred on is supplied by the customer for themselves, so the info has exactly as much relevance as they perceive and expect

    • "Did I make this purchase on 2024-12-02 or 2024-12-03?" is up for the customer to decide/figure out/look up/care about. Whether a particular transaction occurred on a specific date only has relevance to their own calculus
    • Because of this, the association between these labels and real-world dates is actually so loose that pretty much all date-time pitfalls can be hand-waved away:
      • "Time skips" (DST, day skips due to time zone changes in a locale) are irrelevant because customers record the "wall-calendar" version of the date (it's either the date before the jump or the date after, and usually those are the same date anyway)
        • For those familiar, this is the "ignore leap seconds and rely on NTP" type approach that Darwin platforms use: the missing time never existed, and the current wall clock is either before or after the leap second
      • "The user's current calendar" is rarely relevant because the calendar is hardcoded: the business world effectively operates on the Gregorian calendar, and for interoperability, banks and financial institutions rely on either Gregorian or ISO-8601 exclusively
  2. There is a very brief window where "current time" could be perceived as relevant, because the majority of the information is historical with very low granularity (and whatever information isn't, is about to be)

    Things like scheduled transactions are typically easy to handle, depending on the use-case specifics: one simple scheme is to just check the "current wall-calendar date" (current on-device Gregorian date w/ time zone info) against the "expected wall-calendar date" (customer-supplied date for the future transaction) when the customer opens the app. Typically, this type of information is only relevant when the customer is actually looking at it, so their current frame-of-reference is what matters; there doesn't necessarily need to be any scheduling involved.

    (For when you do need to schedule, the specifics of what needs to happen dictates how it happens; between local push notifications and server-based actions, there's typically a simple solution.)

  3. In the vast majority of cases, any ambiguity is typically easy to resolve because almost all purchases go through a bank in some form, and bank information is often used as the source-of-truth anyway (whether in an automated form for applications which import banking information directly, or manually when user-verified against statements)

    • Think of your bank or credit card statements: you typically get a ledger of transactions, each with an associated date. At the end of the day, it doesn't matter what the date is, or how that information was derived; the transaction "happened" on that date, and it's a reasonable point of reference to use

There's more detail I can give here, but at the end of the day, I would consider this a very niche scenario, and in almost all other cases, the type of simplification done here would be utterly incorrect for handling user data.

But, when you're dealing with:

  1. Wall-calendar dates with no associated time information,
  2. In a globally-consistent, non-localized calendar,
  3. For very low-granularity date information where customer intent can fill in the gaps

then, yes, point-in-time calculations are typically unnecessary, and you can get by with much simpler handling.

(In our app, we have GregorianMonth [yyyy-MM] and GregorianDate [yyyy-MM-dd] to handle these scenarios, with bi-directional conversion to Date — but I would actively argue against making types like this generally-available, as they're an enormous pitfall otherwise.)

8 Likes

Whether to support or not the concept of date without time, is up to the standard library developers to decide. If decided to not support it, to claim afterwards that simple date calculations are inherently too complex, or that the only way to do them is to think at the level of time, is against common logic.

The "concept of date without time" is not super sharply defined either. I understand your use-case, but that's not the only way you could understand it. Or in other words, it is not just the "point in time" aspect that makes dates complicated, even "date without time" can become complex.

Your approach of simply deigning it as a list of y-MM-dd objects can become more complex in terms of date calculations too, even though it lacks "point in time" information.

The illustration of how to add one (or more days) onto it you described as counting an offset in the list works, but consider the following use-case:
"I want to add one month onto it". What's the offset? You described how you can do a similar list-style interpretation by cutting off the day information, but that's not helping: Adding one month to a date usually still yields in a y-MM-dd object, so how do you figure out the day?

You can't, that's the fundamental problem. Regardless of whether we include finer granularity than days in a date, the issue lies in the coarser elements. We usually represent them as tuples of (bounded) integers, but there is no inherent "unit conversion" between them. A month does not inherently have 28, 30, or 31 days, and this goes beyond simply figuring out which month it is. Yes, there's math behind leap days and such, but the needed calculations take the year into account, too. Not to mention potential political decisions.

To properly "do math" on dates (including "dates without time"), you need more than simple lists of countable objects, you need calendar information. That's what DateComponents and Calendar are for.

The original pitch here was to basically include convenience calculation functions of the above form to Date, which was opposed by many people (including me). I understand the frustration with this, but such functionality would simply require too many assumptions and hidden implementation details. It could become misleading and imply (specifically to relatively new programmers) the wrong mental models.

Of course adding a new "date without time" type could circumvent this, but even that type would either require similar API and relations to Calendar to be generally useful or be very limited in functionality.
The name-clash between Date and the common usage of the word date is unfortunate and may need addressing, but the underlying functionality is sound and indeed as complex as the type makes it look.

11 Likes

Thank you for your answer, it clarifies many points.

You are correct that this Pitch is about Date and "date without time" (which I call date with small d, sorry for the confusion) does not belong to it, although it is relevant and in the spirit of this Pitch.

You are also correct that date calculations are not always simple, although in common cases they are, and they include much of the calendar logic, which is not simple.

Regarding the underlying concepts: Date captures the concept of time, in a way that covers also dates, with and without time. There are two different definitions of "day", approximately equal: one is the alternation of light and dark (a countable object as you said) and the other is a unit of time (24 h). Some of calendar logic is based on days as objects and some is based on time. The fine details are always expressed in time, but much of the motivation and reasoning is about days as objects. My view (maybe I am wrong) is that these two aspects, which are both blended into a calendar, are mostly orthogonal. Patching historical gaps, adding new gaps, or giving new names to old years, is mostly at the level of day as object and above. DST, leap seconds, and other timing details are at the level of day as time unit and below. To my view, software types and concepts work best if they capture the underlying concepts and motivations that they are meant to represent. In the case of calendars, this is a mixture of day/month/year (as objects) and time.

Regarding the implementation: you are correct, dates without time are part of the same calendar as Date (point-in-time) is, when converted to a readable string. They capture one aspect of the calendar logic. Operations on them duplicate already existing operations on Date, ideally they give the same result but in a different way. Apart from more efficient calculations, their main advantage is that they simplify the specification of a date without time. Their operations implement what people think when they do date calculations at the level of day and above, and they avoid any surprises in conversions between dates and time. Questions on dates cannot be expressed directly in terms of Date, they need additional time information, mostly dummy, which is unwanted detail in some applications.

I didn't try to do date calculations with DateComponents, because it seems to be an interface to define points-in-time. If it can perform date calculations as described above (with all the complexity of calendars and without conversion to point-in-time, which means by skipping or simplifying time details), then its documentation needs an improvement.

I generally agree that a type that represents a "day" without any time is useful. My birthday is August 4th, and even if I were to move to a different time zone, I'm sure I would till celebrate Aug 4 as my birthday.

Over the years I've moved my address book from on-device to Google to iCloud, and I'm not sure exactly how or when — but hundreds of people in my address book, now have the incorrect birthday.

I think it is because some of the software involved, was glossing over some of the details in this thread, by storing is as a timestamp, but presenting it in the UI as a "day", probably storing the time component as 00:00:00. One of the migrations at some point probably did time zone conversion, and converted it to 23:00:00 the previous day. Since the UI still present these as simple days-without-time, they are now off by one day.

I often send "Happy birthday" messages — or see my phone generate a little photo reel, a day early.

However, I still think we shouldn't contaminate Date with false expectations of simplicity.

A new type — now, that's another story!

Thanks for all the feedback on this pitch!

I will look into a new type, which represents a day without time and timezones, maybe called FixedDate or GregorianDate? I cannot implement other calendars as I do not have any insights in these systems.

You would be able convert from and to a Date but would loose any information associated with it like time or timezone which would enable easy calculation as adding a day or a month to the date.
Converting back would create a Date with filling in the day, month, year components but leave anything else as default. You would need to pass in a Calendar to this conversion.

In my opinion this type is needed for many usecases where no exact information is necessary, like the mentioned personal finance manager (where I originated my pitch from as well btw) or other Apps where timezones would introduce non-expected inconsistencies.

If you are happy with this idea, it will be made into a proposal when I have time, most likely beginning of 2025.

Since this serves a specific purpose, it sounds like a great type for a package, and Swift's package ecosystem gets stronger by the day. But I agree with @itaiferber here:

3 Likes

This type exists. It is called DateComponents.

3 Likes