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.
Regards,
Richard Hainsworth (finanalyst)