This module was designed to provide SWIs with which a programmer can easily convert dates between different calendars. It's design is aimed at historical applications such as Genealogy. Implemented calendars include Gregorian, Hebrew, Islam, Julian and French Revolutionary. The implementation as a module was chosen rather then a C library because SWI calls can be used from any implementation language, such as BASIC, Assembler and C.
Disclaimer
This module is in no way intended to be defining any particular calendar. If you find any errors in the results produced by this module please report them to me and an improved version will be made as soon as feasible.
In fact, you are invited to test the code as much as possible in order to find any possible errors. Please make sure the discrepancy you found is indeed due to an error in the module and not, e.g. due to misreading of a printed calendar.
The implementer of this module uses the Gregorian calendar in daily life, and all knowledge of other calendars comes from books and the Internet.
This module was originally designed to do calendar calculations and conversions for a genealogical application. When investigating your ancestry you will quickly have to deal with dates more than a hundred years ago. Not only must they be written down (a simple string would suffice), calculations are also usefull. For example, you might want to put the children in a family in chronological order of birthdate.
Most programming languages and computer systems supply some kind of built-in date manipulation system. However, they usually don't go very far back in the past. Unix and it's derivatives, and as a result the standard C library functions, use a counter for the number of seconds since 1970Jan1, 00:00 GMT. Other systems count days from 1900Jan1. RISC OS does this.
Another problem is that the built-in systems usually only support the Gregorian calendar. When dealing with history you will bump into the French Revolutionary calendar and the Julian calendar. These will have to be converted to and compared with the Gregorian dates.
Another application is the simple printing of calendars. Here, lots of knowledge other than a simple sequence of dates is usefull: What day of the week is Jan1 in a specific year? How many days in a specific month? What weeknumber should you assign to each week. Etcetera.
Once the notion is there that different calendars need to be supported, more types are quickly added to the list, such as the Hebrew and the Islamic calendar. If routines were to be written to convert from each calender type to each other type, the number of combinations runs quickly out of hand. After N calendars you need to write 2*N routines to add a single extra calendar.
To solve this, an ageold and obvious solution is used. Number the days in history sequentially, starting at a suitable starting point. This makes conversion of a date between two calendars an easy, twostep process. Sorting dates chronologically becomes trivial.
Because the number of days since, say, 1Jan1 is already over 730000, it was decided not to include the time of day into the system. As seen above this would quickly limit the range of the dates that can be calculated in a 32 bit system. This also avoids problems with the different notions of when a day begins: at midnight, at noon, at dawn, at dusk? (Don't laugh, these are valid methods used on a daily basis.)
One thing this module definitely wants to avoid is returning strings for the dates. This means keeping a table not only of January, February, etc, but also of Jan, Feb, etc, with options for passing either or both. This is needed for each calendar type. The problem is compounded by internationalisation: each language has it's own names for the months and the weekdays. And what about the Hebrew or Islamic calendars. The English words for the months are just approximations. Many languages may not even their own representation. And pretty soon someone will appreciate a Unicode version with Hebrew or Arabic characters.
And what about formatting? The user will want to pass a format string specifying the layout.
A total nightmare would be an attempt to interpret dates in strings. Coding for that would be about 100 times more work than only handling numbers.
There are many different calendars in use in the modern day. Predominant in the western world is the Gregorian calendar. In many parts of the world the Islamic calendar is used. The Hebrew calendar is also current. When dealing with historic events you may come across the Julian calendar, which preceded the Gregorian calendar, and the French Revolutionary calendar. Today and in the past, many other calendars are and were used.
Based on the antique Roman calendar, this system was introduced by Julius Caesar in 46 BC. By that time they were 90 days off with the seasons. In his honour the Roman month Quintilis was renamed to Julius. Astronomers had determined that the solar year was 365.25 days long. To handle this, the year was made to count 365 days and a leap day was added every four years to Februarius, which was then the last month of the year.
Application of the new calendar was in the beginning rather messy. Emperor Augustus sorted things out. He was bestowed the same honour as Julius, and month Sextilis was renamed to Augustus. The number of days in each month and their names that were then laid down are still in use in the modern Gregorian calendar.
The years were usually counted by using the year of rule of a monarch or a pope. It was only in the 6th century AD that Dionysius Exiguus established the notion of Anno Domini as a result calculations for future dates of Easter he made.
The Julian calendar is now fully replaced by the Gregorian calendar in the Western world. The Greek- and Russian-Orthodox churches still use the Julian calendar.
This calendar was developed in the 16th century under authority of pope Gregory XIII to resolve inaccuracies in the Julian calendar. The difference is that no longer every year divisible by four is a leap year. Years divisible by 100 are not leap years, except when they are divisible by 400. This means that the year 1600 was a leap year, 1700, 1800 and 1900 were not, but 2000 was. This calendar is expected to be accurate for at least 30000 years.
To synchronise the calendar with the seasons, March21 was desired to fall on the equinox. It was known that during the Council of Nicea (AD 325) the equinox fell on that day. The number of days neccesary to compensate was calculated to be 10. It was therefore decided to introduce the Gregorian calendar by skipping from the 4th of October 1582 to the 15th of October. This happened in the entire Roman Catholic world.
But exactly because this calendar is a Roman Catholic invention, the change was not immediately adopted everywhere. Protestant England and it's colonies for instance waited until 1752. Russian-orthodox Russia held out until after the Russian Revolution (that is why the October Revolution was always celebrated in November in the USSR). Greek-orthodox Greece delayed until 1923. And allthough the Greek and Russian states changed to the Gregorian calendar, the Greek and Russian Orthodox churches still use the Julian calendar, for instance to calculate Easter.
In some places, like the fledgeling Republic of the Netherlands, introduction was a local matter and spread out over several decades. Often both dates were used together in documents or the indication S.V. (stilo veteri, old style) or S.N. (stilo novo, new style) was added.
Note by the way that there is a lot more to reading dates in old documents. For instance, in those days the year was normally taken to begin at Easter, not on the 1st of January. So if you read "19 of February 1555" it is very well possible that this translates to the modern 19th of February 1556, a year later than you might expect at face value.
These kinds of problems interpreting old dates however fall outside the scope of this manual. You are referred to the literature about this subject.
(Note: My knowledge of this calendar is limited. No statement in this section should be taken as irrefutable fact.) The Islamic calendar is a lunar calendar, where each month follows a phase cycle of the moon. Based on the fact that the period between two equal phases of the moon is about 29½ days (29.53069 to be exact) the months alternatively have 29 and 30 days. The calendar has 12 months, adding up to a year of 354 days. Because 12 actual moon cycles make 354.36828 days, a leap day is frequently added. In the Islamic calendar there are 11 leap years in every 30 years, making the average year 354.36666 days long. This misses out only 1 day in about 620 Islamic years. As we are now in year 1422 the difference is no more than 2 days. Not bad for a calendar designed 1380 years ago.
Note that this lunar calendar has no direct connection to the seasons, which are related to the sun. In fact, the Islamic year is about 11 days shorter than the Gregorian year. This means that each year the dates in the Islamic calendar shift slightly when compared to the Gregorian calendar. It goes round once in about every 34 solar years. This also means that 35 Islamic years go by in 34 solar years.
(Note: My knowledge of this calendar is limited. No statement in this section should be taken as irrefutable fact.) The Hebrew calendar as used since 378 AD is a lunisolar calendar. This means it has months of 29 or 30 days that keep in step with the lunar cycle, and the resulting difference of about 11 days with the solar year is compensated by adding an extra month every 7 out of 19 years. Leap days are added for fine tuning.
Although this calendar is highly accurate, it is also very complicated. Three of the thirteen months can be either 29 or 30 days. The extra (leap) month is added in the middle of the year. The calculation of the number of days in a specific Hebrew year is lengthy and contortious.
The French Revolution happened in the middle of the philosophical period known as Rationalism. A clean break was made with outdated silly and irrational institutions. They were replaced with sensible, rational things. One of the great successes is the metric system of measurement, which does away with such silly things as having three separate, non-compatible systems of measuring weight, and using weight units to measure volume. One of it's failures was the Revolutionary calendar.
The year was divided into twelve months of 30 days which were given beautiful poetic names. This gives 360 days. The remaining 5 or 6 days were added to the end of the year as "jours complémentaires" (additional days). While they were at it, the weeks and days were decimalised, with each month divided into three "décades" of ten days each. A day would consist of 10 hours of 100 minutes, with 100 seconds to the minute. (Note that this would result in 100,000 seconds per day instead of the current 86400. The second would have become slightly shorter.)
The 10 hour day was never introduced as there were no clocks to show it and conversion was too complicated. The months and the 10 day week were instituted on the 5th of October 1793. The day the Republic was proclaimed, the 22 of September 1792, also the day of the autum equinox, was chosen as the starting point of the calendar. Note that like many other calendars the year 1 occurs before the calendar is actually introduced.
The 10 day week was abolished on the 31 of march 1802, because it hindered the celebration of Sunday in the still very Roman Catholic France.
The calendar did not last very much longer. Napoleon abolished it on the 11th of September 1805, after it had served less than 12 years.
Before that happened, France under Napoleon had conquered large parts of Europe and introduced their calendar there. So even outside France you may find documents dated with French Revolutionary dates.
As a basis for the conversion, a unique reference number was needed for every day in history. As days always follow one another in an orderly fashion, it is very unlikely that any mistakes were made in the past in counting the days. So, as a reference, each day is given a consecutive number. This module chooses an existing system, know as the Julian Day number. This was introduced by Justus Scaliger in 1583. It's starting point lies in 4713 BC. The Julian Day was chosen because it is an existing system and is in current use, namely in astronomy.
Note: in previous (pre-0.10) versions of the module the starting point was the first day in the Gregorian calendar, the 1st of January in the year 1, which was taken as day 1.
The module implements three SWIs.
The first converts from a Julian Day number to the year, month and day of a particular calendar.
The second does the opposite conversion. A given triplet of year, month and day of the month in a particular calendar is converted to a Julian Day number.
The third is similar to DateToJDay, but uses year, week of the year and day of the week as input data and converts them to a Julian Day number.
This is the reverse of WeekToJDay. Given a Julian Day number it calculates the year, week of the year and day of the week.
Calculating the Julian Day number from the date is relatively straightforward. For example in the Gregorian calendar simply add the day of the month to the number of days in the previous months in this year to get the days in the given year, and then add 365 days times the number of previous years, with corrections for leap years.
The reverse is generally more difficult. As a simple solution, the code makes an educated guess for the date and then uses the Date-To-Gregorian-Day routine described above to see how close the guess is. In more detail it goes like this:
First a low estimate for the year is calculated by dividing the day number by 366 (one day more than the normal number of days in year). Then the year is increased until the first of January of the next year has a day number that is bigger than the one given. We now have the year.
Next, the months in that year are checked in sequence until the month is found of which the last day converts to a day number which is equal to or higher than the given number. We now also have the month.
Finally, the day of the month is simply found by subtracting the day number for the first day of that month in the year we found from the given day number and adding 1.
The effect of this method is that every Julian Day converts to exactly one date in a specific calendar. On the other hand, conversions from a date to a Julian Day accept even the most outrageous values for year, month and day. For example if 2002/1/60 (the 60th of January 2002) is given, it is correctly converted to day number 730909. Converting back results in 2002/3/1, or the first of March in the same year, which is indeed the 60th day in that year. This is useful in cases where one is not certain if a date is valid in a specific calendar.
An effort has been made to keep the assignment of registers the same for all SWIs. R0 is always used to pass the calendar type. R1 always holds the Julian Day number, either on entry or on exit. Year, month and day values are always in R2 upwards. This means that they don't disturb R0 or R1.
The result is that converting back and forth between two calenders in assembly becomes very easy: First call Calendar_DateToJDay with a given year, month and day for one calendar. You then get the Julian Day number in R1. Then set the calender type in R0 to the calendar you want to convert to, and call the reverse function, Calendar_JDayToDate. The converted date is then in R2-R4.
Another feature that is common to all Calendar SWIs is that they are not fussy about the date values. Silly dates like 2000/13/61 are easily converted to the corect Julian Day number.
The SWIs can return errors in the following situations:
More errors may be added in future.
The following codes are available for calendar types:
1 | Julian |
2 | Gregorian |
3 | Hebrew |
4 | Islamic |
5 | Revolutionary (10-day weeks) |
6 | Revolutionary (7-day weeks) |
Some remarks:
Calendar_JDayToDate (SWI &55F80)
Convert a Julian Day number to a date in a specific calendar.
On entry
R0 = Type of calendar
R1 = Julian Day number
On exit
R0 - R1 = preserved
R2 = Year
R3 = Month
R4 = Day of the month
Interrupts
Interrupt status is not altered
Fast interrupts are enabled
Processor Mode
Processor is in SVC mode
Re-entrancy
SWI is re-entrant
Calendar_JDayToDate converts a Julian Day number to the year, month and day of the month in a specific calendar.
Calendar_DateToJDay (SWI &55F81)
On entry
R0 = Type of calendar
R1 = (unused)
R2 = Year
R3 = Month
R4 = Day of the month
On exit
R0 = preserved
R1 = Julian Day number
R2 - R4 = preserved
Calendar_DateToJDay converts the year, month and day of the month in a specific calendar to the Julian Day number.
Calendar_JDayToWeek
On entry
R0 = Type of calendar
R1 = Julian Day number
On exit
R0 - R1 = preserved
R2 = Year
R3 = Week
R4 = Day of the week
Calendar_JDayToWeek converts a Julian Day number to the year, week of the year and day of the week in a specific calendar. For Julian, Gregorian and Revolutionary-7 calendars it follows the ISO 8601. This means that Monday is counted as the first day of the week (day 1), and the first week of the year is the week in which Jan4 lies. For the other types of calendar we have not enough information at the time to implement this SWI. If you have an accurate description of the algorithm or the rules for one of them, please send them to us.
Calendar_WeekToJDay
On entry
R0 = Type of calendar
R1 = (unused)
R2 = Year
R3 = Week of the year
R4 = Day of the week
On exit
R0 = preserved
R1 = Julian Day number
R2 - R4 = preserved
Calendar_WeekToJDay converts a the year, week of the year and day of the week to Julian Day number in a specific calendar. For Julian, Gregorian and Revolutionary-7 calendars it follows the ISO 8601. This means that Monday is counted as the first day of the week (day 1), and the first week of the year is the week in which Jan4 lies. For the other types of calendar we have not enough information at the time to implement this SWI. If you have an accurate description of the algorithm or the rules for one of them, please send them to us.
Calendar_CheckDate
On entry
R0 = Type of calendar
R1 = (unused)
R2 = Year
R3 = Month
R4 = Day of the month
On exit
R0 - R1 = preserved
R2 >=0 if date is valid, <0 if date is not valid
The date given is checked to see if it is a valid date for the given calendar. This is done by the simple method of first converting the date to a Julian Day Number, converting it back again and then checking whether the year, month and day of the month calculated are the same as those given. This catches things like "April 31" and "1999 Feb 29", and also the more radical "2000 Jan 61" or "2001-14-31". These examples are for days of the month or months wich have too high a number, but it should also work for days of the month or months which are zero or negative. In the Hebrew calendar it will also catch out the less trivial case of using of the month Veadar in years when it is not present.