I think the following ingredients are minimally necessary for normal day-to-day stuff:
1. For events in the past, a timestamp
2. For events in the future, a date/time pair with precision at least milliseconds and a timezone.
3. An ISO 8601 serialization/deserialization method
4. A duration type, which denotes a number of 'hours/minutes/seconds/days/weeks'.
5. A partial representation of date, to denote repeating events or events which last a whole day. The partial representation should optionally include a timezone. This allows me to say: '5th of May', or '22nd of November 1980, in CEST'.
For calculations of dates and times, it is very useful to have a notion of time intervals, durations of time and weekdays.
> For events in the future, a date/time pair with precision at least milliseconds and a timezone.
Hmmm...Can you give an example where that would be used? I'm thinking of a user who wants to be woken up at 7am even if they change their timezone. But that wouldn't require millisecond precision.
> A duration type, which denotes a number of 'hours/minutes/seconds/days/weeks'.. For calculations of dates and times, it is very useful to have a notion of time intervals, durations of time and weekdays.
Couldn't you do that with the first two types I mentioned? With two instances of Type 1 it's trivial to count the number of milliseconds between them. With two instances of Type 2 you can have d1.daysUntil(d2) or dq.monthsUntil(d2) etc.
> This allows me to say: '5th of May'...
Is that common enough and complicated enough to warrant an extra type? When I write code to process credit card payments, I just record the card expiry as two integers: expMonth and expYear. When I want to check if the card is expired I just create a Type 2 instance representing the last day of the month. Seems to me I could do something similar for 'repeat on 5 May'.
For the first example, a skype meeting with participants on both sides of the equator, or even just in countries where one has ended daylight savings time and the other hasn't yet.
That may seem like a special case, but it is relatively common, it might also matter for normal business meetings in a foreign country - UK time is an hour behind France, so if you are a UK business man trying to have a meeting in Paris, you need to take time zones into account.
It is never trivial to calculate the duration between two times, because you may have to take into account leap years, leap seconds, months that does not have the same number of days, etc, not to mention if they are in two different timezones. That doesn't mean it isn't possible, but it is not trivial.
What is the last day of the month? That depends on the month, and if that is February it also depends on the current year. If you need to process credit cards, you might need to know the last banking day of the month, which could be quite early during e.g easter. BTW to know when easter falls, your dating system needs to know about the lunar calendar.
This doesn't even begin to touch what happens in countries that celebrate Ramadahn.
Those are some interesting situations, but I think they can still be handled using the 3 types I mentioned.
A Skype call is actually a good example of when not to use a data type which encapsulates 'time zone plus instance in time'. That's because there is not one but multiple time zones associated with a single instance in time - each Skype caller will have his or her own time zone. The time of the call should be treated as a pure 'instance in time' without reference to any particular time zone. When it comes to displaying the time to a particular user, you would have formatting code which takes (a) the user's time zone; (b) the user's language; (c) a format pattern; and (d) the instance in time; and returns (e) a string representation of the time for that user.
If the computer's clock is based UTC it is trivial to find the duration between two 'instants in time': it's just t2 - t1. An instant in time (what I called Type 1) is independent of any calendar system so months, leap years, time zones, etc. don't matter when calculating the duration between two Type 1 values.
However, in practice most system clocks are not based on UTC but rather Unix Time [1]. Due to leap seconds, such clocks do not always 'tick' in a monotonic way. So I accept that it is usually not trivial to accurately find the duration between two times. But the solution is not to burden the programmer with an extra type - instead, just add a method to the existing 'instant in time' class like this: t1.millisecsUntil(t2).
To find the days in any given month, a 'date in the Gregorian calendar' class (what I call Type 2) can handle that easily - just call the daysInMonth() method. Again, no need to create new type.
Easter, Islamic dates, Buddhist dates, banking days, etc. are all based on Type 2 - ie. an abstract concept of a 'day' without reference to any time zone or any particular instant in time.
The Skype call is always in reference to a certain perspective of 'time-of-day'. So, you'd say: I will call you at 13.00 CET, which is 14:00 GMT at my place. When we move it to the next day, it will be in reference to CET and DTS will be taken into account in CET, not in GMT. As long as humans see time as a location dependent aspect, it makes sense to inform them of the reference that has been used (being the timezone). Another way to say this is that the Skype call, or the appointment, will be scheduled at a time and timezone of someone's perspective. The other participants need to see the appointment in reference of that.
The duration in milliseconds is (almost always) easy to find. However, you often want to find the duration in months, weeks, days, etc. etc. This again requires a reference timezone and calendar system.
Your 'type 2' day is actually very difficult to define. For example, when I talk about the 12th of October 2016, I will see it as a time-interval in central European time. Your time-interval might be different. By the way, you can only handle the daysInMonth case. Other examples would fail: weeksInMonth, weeksInYear, hoursOfMonth, etc. etc.
When scheduling an appointment (event) in the future and you need to move it around, these fields are necessary. Repeating events are similar, as you already mentioned. Millisecond precision is just the standard. I would say nanosecond should be the standard, but it isn't yet.
The durations are very useful for basic arithmetic on time. An hour isn't always an 60 times 60 seconds (leap seconds) and a day isn't always 24 hours. So, if you have two buttons in your UI which move your appointment around, or which calculate the end-time of a one-hour appointment, you want to be able to use the type '1 hour', and not 60 times 60 times 1000 milliseconds.
The partial dates are very common and miscalculations are often made. It is notoriously difficult to calculate with partial dates and warrants a type with associated methods/functions. I see myself often writing something like: `new LocalDate(5,5).toInterval().contains(now)` (where obviously, the localdate is automatically deserialized from JSON or sth alike).
1. For events in the past, a timestamp
2. For events in the future, a date/time pair with precision at least milliseconds and a timezone.
3. An ISO 8601 serialization/deserialization method
4. A duration type, which denotes a number of 'hours/minutes/seconds/days/weeks'.
5. A partial representation of date, to denote repeating events or events which last a whole day. The partial representation should optionally include a timezone. This allows me to say: '5th of May', or '22nd of November 1980, in CEST'.
For calculations of dates and times, it is very useful to have a notion of time intervals, durations of time and weekdays.