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

@dynamicMemberLookup does this unless I’m missing something. That wouldn’t solve the issue as you need both a calendar and a date to make calculations so simply forwarding some APIs to one of the props wouldn’t make use of the second. You need propers methods on the wrapper type.

Yes, @dynamicMemberLookup does not work with methods.

It would make maybe things simpler if Date would be a protocol.

Part of the (relatively frequent and not just isolated to Swift) problem is that, fundamentally speaking, "Date" is a human "every day concept" that we think is simple, when it is actually not. That much has become clear from this thread, I believe.

The problem arises that "our" Date is a point in time, and not what we call a date in every day speak. This is not the same.

I think there have been discussions in the past about how unfortunate a name this is, so I think discussing a renaming of the type is understandable (though personally I am of the opinion the huge effort for this is not worth the benefit), but simply adding on top of Date as it is won't help (IMO).

I advise trainees in my company and one of the early lectures I often have to hammer into them is that "a date is not a day, month, and year, it's not easy, and it's not plainly 'math'-able in any way". With the latter I mean that due to it being a timestamp, you cannot "add a day" or the like without further context (i.e. a calendar). This is even true for "everyday dates", btw.

TL;DR: I am against such a proposal

1 Like

I prefer Date being independent of time zones or calendars, making it work more correctly. But I do understand the need for a more intuitive API when working with the Gregorian calendar, which I think is very common.

I personally also miss date-only or time-only types since Date always represents both together. So I created the types GregorianDay and GregorianTimeOfDay in my HandySwift open-source package. I use them in the model layer whenever I need a day-only or time-only type. And they also are easy to convert to the Date type like this:

GregorianTimeOfDay(hour: 8, minute: 15, second: 30)
    .date(day: GregorianDay(year: 1960, month: 11, day: 01), timeZone: .current)

Maybe something like this could be an approach for Foundation, too.

As pointed-out, Date in Swift means point-in-time, not to be confused with the common use of the word "date". It is true that the relationship between the two meanings is too complex, and it is also true that point-in-time calculations are simple and precise. But it is not true that date (as in "date in the calendar") calculations are either complex or inaccurate. I'll try to bring some theory to what is considered to be very specific in a few answers above, and some accuracy to what is considered to be inaccurate.

Since the term Date is already (mis)used as a synonym of point-in-time, we need another term for the common meaning of date, like YMD, or CalendarDate, or the super-confusing Calendar.Date. I use the second alternative in the following.

Consider the ordered list of all possible Date, reduced (projected) to the format YYYY-MM-DD, and remove all duplicates. This is the ordered set of CalendarDate. By definition CalendarDate is an item in this list. No relationship to point-in-time is necessary once this list is known. (This set is infinite, but this is a minor problem. As in many other cases, assume that the available range is more than enough for all practical purposes).

Now forget about (ignore) the "precise" equation [1 day = 24*60*60 sec]. Instead think of the equation [1 day = jump to the next item in the list].

Several CalendarDate calculations become surprisingly simple if the association to point-in-time is ignored. A CalendarDate has a unique year, month, day (immediately visible in the YYYY-MM-DD format), and also a unique weekday (not immediately visible, but can be calculated in one or few lines of math).

It is pretty simple to define and to calculate the next CalendarDate (as the next item in the list).

A CalendarDate has a tri-state DST flag (depending on the locale). In the ordered list of CalendarDate, continuous blocks with DST=off and DST=on alternate, separated by a CalendarDate with undefined/dual/partial DST. All the complexity about the exact point-in-time when DST changes, or the gap/overlap in time expressed as hh:mm:ss, is gone. The CalendarDate's when DST is switching can also be easily calculated (a complex process in terms of point-in-time becomes easy to describe in terms of date-in-calendar, as a result of information reduction).

The distance between two CalendarDate in days is pretty simple to calculate (as number or jumps, not as difference of two points-in-time).

The distance between two CalendarDate in months is also pretty simple to calculate (just remove/ignore -DD and count the number of jumps between YYYY-MM in the reduced list).

All the above properties and calculations are perfectly accurate. One may think of a date as point or period in time, and claim that the calculations above do not give the "correct" (wanted) result. But thinking of a date with the common meaning, they make sense.

The question "what CalendarDate do we have now?" is pretty complex to answer, because it re-establishes the association to point-in-time, but this question is irrelevant in this context.

To rephrase (and distort) the main point in this Pitch: does it make sense to have an API for date calculations, with the common meaning of "date", and without resorting to point-in-time calculations (which make the concept of date ill-defined)?

What is still specific is that the concept of CalendarDate belongs to a calendar. Each calendar has its own set of dates and its own logic. The point is that CalendarDate as described above, captures this logic in a better way because it counts days and not seconds. When people think about dates and invent rules about them, they mostly think of a day as flipping a page in the desk calendar, rather than counting elapsed time in seconds. Doing date calculations as byproduct of elapsed time calculations, does not remove the specifics of calendars, it only makes the calculations harder because they are moved out of their natural domain.

Although out of the scope of this Pitch, it worths mentioning a related concept. ClockTime could capture how people think of the clock time (and not more). It belongs to a CalendarDate (which belongs to a calendar), and it depends on some locale information about DST (but not on the time shift, which is the most important locale information). Under some conditions, an hh:mm:ss may not be valid or may need to be augmented with phase information (when clock goes back).

ClockTime is still not convertible to/from point-in-time. It could make sense to define caclculations at this abstraction level, which is how people think when they look at their watch.

However, ClockTime calculations are too complex and the extra mile to reach the "scientific" point-in-time (the so-called Date) is too small (just add the locale time shift). It also makes sense to skip this abstraction level, and to present clock time as a derived property of point-in-time. This also avoids the confusion in storing time, which in most cases has to be a universal point-in-time and not a local clock time.

1 Like

Could you please expand of how is it exactly different from the traditional inheritance? (let's suppose we did have it in Swift for structs).

I'm not sure CalendarDate would even be an ordered set

1 Like

The ordering of days is in principle quite simple: a day is the alternation of light and dark, and each day has one before and one after it. Their ordering is defined by this sequence, not necessarily by sorting their names. No matter how complex and localized it is to specify when one day is finished and the next starts, this sequence remains the same (earth does not forget to rotate).

Ideally a calendar has one page for each day that happened or is going to happen, with a different name in each page. Of course you can mess up with the calendar and have days without name or days with double name. Then you need additional indicators to disambiguate (similar for clock time when the clock goes back).

No doubt that calendars are complex. Thinking of a day in the scientific way (number of seconds) adds even more complexity, and it also distorts the very simple concept that a day is the alternation of light and dark. So better to keep date calculations at the granularity of a day, and switch to point-in-time when needed.

This intuitive definition falls apart towards the poles, where during the summer the sun does not set for months at a time.

More broadly, I’m not sure what point you’re trying to make. People have thought very hard about calendaring for thousands of years. The complexity we have now is a result of the edge cases that have been discovered or the requirement for ever greater precision.

5 Likes

Correct, the concept of day is not universal. What happens at poles does not invalidate the sequence of days as observed out of poles. Those who invented calendars did not live at the poles.

My point is that for thousands of years, as you said, people calculate dates using one day as a unit. This could be represented internally as an integer number which counts days. Excluding edge cases, date calculations are much simpler than going back and forth to points-in-time.

It sounds like you’re arguing for doing date math with DateComponents instead of Date, which I agree produces better results for most use cases.

1 Like

Well, yes, for thousands of years people have been calculating dates incorrectly, which is one reason for calendar reforms such as the introduction of the Gregorian calendar in 1582, which affected only parts of the world at that time.

However, I don't think that's the point. Depending on where we live, and where we travel, my list of dates might or might not have the same entries as yours. Even if our lists are the same, we have no information that allows us to equate dates between our lists.

For example, if you invite me to your birthday party, I would have no way to be certain to arrive at the party on the correct day. (You'd need some edge cases, such as crossing the international date line, or jogging around the poles, to make such a simple example work.)

Without the ability to equate dates, people would be unable to communicate date information reliably with each other, nor would there be any independent meaning to intervals of days.

2 Likes

it worths mentioning a related concept. ClockTime could capture how people think of the clock time (and not more).

You've just invented DateComponents.

2 Likes

You are talking about a different problem, you want to define a point-in-time. Think of the problem: what is the next date after 2024-12-02? You need little more than a range check and an addition to answer it directly. Now try to answer the same question using points-in-time. First you have to convert this date to a Date, which makes the question ill-defined. Then you need to perform more calculations, and to convert back to a date (with small d). Still the stated question is very common, it has a very definite answer no matter where you live and what time it is now, and it needs very little calculations to answer. (You may claim that the answer is wrong, but this is the answer most people expect).

Date solves a much more complex problem, and yes, you can use it to solve also questions like the above. This doesn't mean that this question is complex in itself. I reacted to claims that date calculations are too complex. This is mostly because you solve them in a complex way.

Yes, almost. Some details are different.

I'm talking about equatability, aside from the question of how that's defined. If you don't have equatability of dates between people, you can't communicate any useful information (about dates) between them.

Of course, in your scheme it's 2024-12-03. That just isn't useful for anything, by itself. You can use this sort of calculation to draw an empty calendar, perhaps, but you couldn't put any events on the calendar.

That isn't the problem, though. You're writing an app which has no direct experience of each individual's position on that individual's calendar, and you are — let's say — trying to do "stuff" that requires navigating between dates on that calendar.

The vast majority of the stuff is dependent on the point-in-time location of events, for which you need to display a user-localized representation of the dates involved. For all such stuff, you are stuck with a point-in-time perspective that can be hard to represent, and the representation can be very transient.

Even for things that semantically don't seem to involve a point-in-time, such as "remind me to do X on the day after 2024-12-02", will still involve a point-in-time:

  1. At what time on 2024-12-03 do you display the reminder?
  2. Even more fundamentally, how does your app know when the reminder is due? That is, how does it know at what point-in-time this user's 2024-12-03 day starts?
1 Like

Different apps have different needs. I work on an app which needs a very loose association to point-in-time. It is a personal finance app in which users enter transactions with a date. The exact time is not important, even if an interface is offered it is too tedious to enter a time to each transaction (and the time of entry is not always the time of transaction). Is the date entered scientifically correct and universal? Maybe not, but it doesn't matter, it is what the user wants to name it. The same moment may be another date in a different place, but the app is for personal use.

Say that you transfer this info to a friend in another place in the world. Do they really care if it was +/-1 day for them? Most probably not, maybe they care if it was weekday or weekend at the place where the transaction happened, not at their place.

The app also maintains a list of scheduled transactions, with a due date. It gives a reminder or it executes them automatically when the due date arrives. At what exact time? It doesn't really matter. Any time within the date is fine. Also it needs to calculate the next execution date for repeated transactions (after a month, half-year, etc.). Again only the date matters, not the exact time, not the exact place.

Searching for the recommended way to calculate the next day, month, etc., I mostly find option 1 in ankinsoid's answer above, which needs a Date. The other two options also need a Date. I have only a date (small d).

Not really. The answer to "what day is after 2024-12-02" depends on the calendar and the timezone, and cannot always be assume to be a strictly linear incrementation of values. You must consult the calendar in order to answer it, and our calendar APIs (and all good calendar APIs) are based on the ICU libraries, which work on points-in-time.

I use all of those in my Time library, which calls this thing a Fixed<Day>.

2 Likes

My favorite truly bizarre example, which sadly I don't have the reference handy for anymore, is a situation where due to a DST change, the date briefly went backwards, which also caused it to briefly stop being the weekend and then start again.