In reading all of the discussion about Temporal, I have the uneasy feeling that the current development work is falling into the same traps as other languages. It seems to me that the underlying time-measurement paradigm of the developers is too tightly focused on the Christian Gregorian calendar.

The following proposal concerns the time-measurement paradigm. It shows how DateTime arithmetic can be done in a unified way taking into account:
- different timezones (including leap seconds and daylight savings changes);
- different calendars;
- different calendar systems.

The paradigm does not provide any API (role/class/method name or definitions) since I could imagine a number are possible, including ones close to the DateTime module being developed.

It is possible that the developers already implicitly use a time-measurement paradigm like the one suggested here. If this is the case, it would be best to make the paradigm more explicit, perhaps by including the paradigm in the 'semi-internal' section of the Temporal Spec.

We start with a stream of Instants - already specified for perl6, in reality seconds from a single epoch. This stream is universal (our universe being the computers on Earth) in that in whatever time zone a person is, there is the same number of seconds from the start of the epoch. (I believe we can ignore relativity at present.)

Imagine these Instants as a horizontal axis. Now we need to *name* these Instants in a conventional manner.

Over the top of the Instants we have what I am calling a filter, or rather a series of filters. A filter groups the periods in the filter below it into longer periods. Hence, the first filter groups Instants into days. The day filter is like a set of windows over the Instant stream.

The first - day - filter is defined for a particular jurisdiction and time zone. Eg there is a filter for the Moscow time zone. Each element - RU-Mos-day - knows its start and finish in terms of Instants. Ordinarily, the number of Instants (seconds) is fixed per day, but from time to time leap seconds are added to a day. Note that in each time zone the leap seconds may be added at different times in the universal Instant stream. That is, normally leap seconds and daylight saving changes are implemented at midnight in each time zone.

The implementation of a time-zone filter would be the same for all time zones, namely an iterator that generates a new day as a fixed number of Instants after the start of the previous day, plus a list of exceptions when the *end* of a day is extended/contracted by some number of Instants (due to leap seconds of daylight saving). The difference between time zones would be that the start of the filter (the epoch of the filter) is shifted up or down the Instant stream, and the list of exceptions for time zones/jurisdictions would be different, depending on jurisdiction and daylight saving.

It is conventional to refer to days in terms of a calendar system. Calendars introduce a variety of problems.

Significant numbers of people on the planet use the Chinese, Muslim, Jewish and Baha'i calendars - all of which are very different in structure. In some countries, two calendar systems are used simultaneously, with people switching between them according to the function of the day.

For example, in Russia there are two concurrent calendars (the secular and religious) and they are a fixed number of days apart. Hence Christmas - December 25 in both calendars - occurs on December 25 in the secular calendar, but on Jan 5 (I think) according to the secular calendar, but December 25 according to the religious calendar.

For perl6 to be universal, it must be clear how these different calendar systems can be integrated.

In principle I think all calendars can be adapted using the same filter technique. The description below is for the Christian Gregorian calendar as applied in many secular jurisdictions. We then consider how to adapt the approach to other calendars.

Days have two sorts of name:
- Day_of_week, which is a simple modulo 7 of the number of days from the start of the epoch.
- Calendar_day, which is the year-month-day format.

The day filter, eg., Ru-Mos-Day, provides for an arbritary Instant both a day_of_week name (eg. Monday) and an offset in days from the start of the time-zone's epoch. For each day, the filter provides the start and end positions in Instants.

Next layer up is a Month filter. Just as there are leap seconds in days, so too there are leap days in Months. The Month filter keeps track of the start and end of each month in days. Just like the day filter, the month filter provides a Month_of_year name and an offset in Months from the start of the epoch. Each month 'knows' its start and end day.

Similarly for the Year filter.

Consider filters to be windows over the Instant time stream. Looking up from an Instant, the filters provide names for days, months, years.

Now consider DateTime arithmetic:
Given a date with a year part, month part and offset in days from the start of the month, it is possible to fix (for a given time zone) the day as an offset from the start of the epoch.

Date arithmetic is done by going from a date name down to the appropriate filter level. Hence adding a fixed number of days to a yy-mm-dd date is done by finding the time-zone-day offset from the start of the epoch for that date, adding the number of days, then using the filters to create the yy-mm-dd date of the result.

Adding months, requires a descent to the months filter, adding the number of months, and then finding the result's year part. This assumes that the user is not interested in the day offset. However, if the offset does matter to the user, then extra logic needs to be added. Eg., if the initial date is 30-January-<someyear>, and we add a month, what does the user want with the offset from the start of the month? It could be the day before the end of February, in which case the offset is taken from the month's end (eg., Ru-Mos-month(xxx+1).end, where xxx is the number of months from the start of the epoch of the original date), less one. Since the month filter keeps track of leap days, the offset of the result (end of month less one day) would be 27 in some years and 28 in leap years. Alternatively, the user may want a week-day of the same name one month later. In which case, a different algorithm would be needed.

Suppose we want to add days and then have the result in another time-zone. We take the yy-mm-dd date, find the number of days in the time zone, add the days, then because the filter knows the start and end of each day in Instants, we can find the start (or end, depending on application) Instant of the result (that is in the universal Instant stream), and then use the appropriate day filter for the result time-zone, and then the Calendar filters to get the date. Since time-zone day filters keep track of leap seconds and summer/winter time, we obtain the correct result automatically.

Time of day naming of Instants uses a similar approach, except that hours and minutes of a day start from the start of a day in a time zone. Minutes and hours can be considered filters over the Instant stream whose epoch is the start of the day. This is actually quite important: when there are daylight-saving changes, some days have more/less than 24 hours.

A hour-minute time on a particular day is an offset in Instants from the start of a day, which is at a known point in the universal Instant stream. Time arithmetic starts by taking a specified time, obtaining the Instant stream position, doing the arithmetic, using the appropriate time-zone filter to get the day result, and if necessary the Calendar filters to get the full name of the day.

The Calendar filters (months/years) should be the same independent of time zone. Hence for a fairly universal implementation, it is necessary to create different filters for all time zones, but the same calendar filters.

Now for different Calendars.

In Russia, the difference between the two calendars (secular and religious) is that leap days were taken away from the secular calendar just after the Bolshevik revolution. Hence the Months filter is shifted relative to the days filter. But now the religious calendar uses the same Ru-(time-zone)-days filters when it comes to leap days and leap seconds. So a Russian Orthodox day naming system can be built on top of the time-zone-day filter that is built for the secular calendar, with only a change to the epoch of the Months filter.

If a calendar system, eg., Chinese, Muslim and Jewish, defines days in the same way, eg., starting at midnight and incorporating leap seconds, for a time-zone, then the naming of the days is done by creating appropriate filters for the grouping of days from the start of the time-zone epoch into the equivalents of months and years. Note that the lunar calendars are not synchronised with solar calendars, but for a time-zone, both types of calendar can be synchronised at the day level.

The day_of_week name may not be appropriate for different calendars, in which case the naming system for the time-zone-day filter needs to be modified. The number of days from the start of the epoch would not be changed.

It is possible to have calendars that use different definitions for days, eg. the Bahai calendar defines days starting and ending at sunset. To accommodate such a calendar, a different set of time-zone filters would need to be constructed. But the filter system could still be used.

Even if a calendar system is not synchronised at a day level, they are all synchronised at the Instant level.

This means that if an implementation of a DateTime module using this paradigm and has the filters for different time-zones and different calendars, then it would be possible to take a date-time in one zone and one calendar, do date-time arithmetic at the Instant level, then use the resulting Instant to find the result date-time in another time-zone and another calendar. All the leap second, leap days, and daylight saving differences would automatically be taken care of.

Richard Hainsworth (finanalyst)

Reply via email to