Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Julian and Gregorian Calendars


Testing

No function or program can be relied upon unless it is tested thoroughly. The program DATETEST.C in Listing Three tests the functions in Listing Two. DATETEST takes two numbers, n and m, on the command line and performs conversions for n, n+m, n-m, n+2*m, n-2*m, and so on, up to the largest integer that can be handled. It first converts the number to a date using gdn_to_date(), then converts the resulting date back to a number using date_to_gdn(). The program does this for both the Gregorian and the Julian calendars. If the number resulting from converting a number to a date and back to a number were different from the initial number, then a bug would be revealed.

If DATETEST is run with command-line parameters 0 and 1, then all dates forward and backward from October 15, 1582 (Gregorian) are tested, although the program displays the results only for every Nth conversion. (N is currently defined as 3000 in Listing Three.)

/*  DATECONV.C -- Universal date conversion functions --Last mod: 1992-10-10 */
#include <STDLIB.H>     /*  for labs()  */
#include <CTYPE.H>
#undef toupper          /*  so as to use function, not macro  */

#include "DATECONV.H"
int month_length[14]
  = {
    0, 31, 28, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31, 0
    };
/*  months 0 and 13 have 0 days  */
/*  the calendar parameter is always 'G' or 'J'; G = Gregorian, J = Julian  */
int is_leap_year_c(long year,char calendar)
{
calendar = (char)toupper((int)calendar);

if ( year%4 )           /*  if year not divisible by 4  */
    return ( FALSE );
else
    {
    if ( calendar == 'J' )
        return ( TRUE );
    else    /*  calendar == 'G' */
        return ( ( year%100 != 0L || year%400 == 0L ) ?
                 TRUE : FALSE );
    }
}
int is_leap_year(long year)
{
return ( is_leap_year_c(year,'G') );
}
void set_feb_length(long yr,char calendar)
{
month_length[2] = 28 + is_leap_year_c(yr,calendar) ;
}
void reset_feb_length(void)
{
month_length[2] = 28;
}
/*  function to convert date to Gregorian day number sets valid flag to FALSE
 *  if date invalid, otherwise returns number of days before or after the day
 *  the Gregorian calendar came into effect (15-OCT-1582)  */
void date_to_gdn(Date *dt,       /*  dt is a pointer to a structure  */
                 char calendar)  /*  'G' or 'J'  */
{
int day = dt->day;
int month = dt->month;
long year = dt->year;
long gdn;

calendar = (char)toupper((int)calendar);
set_feb_length(year,calendar);
if ( month < 1  || month > 12
        || day < 1  || day > month_length[month] )
    dt->valid = FALSE;
else
    {
    /*  calculate number of days before/after October 15, 1582 (Gregorian) */
    gdn = (year-1)*365 + lfloor(year-1,4L);
    if ( calendar == 'G' )
        gdn += lfloor(year-1,400L) - lfloor(year-1,100L);
    while (--month)
        gdn += month_length[month];
    gdn += day - 577736L - 2*(calendar=='J');
    dt->gdn = gdn;
    dt->valid = TRUE;
    }
reset_feb_length();
}
/*  function to convert gregorian day number to date  */
void gdn_to_date(Date *dt,char calendar)
{
int month, i, exception;
long year, gdn, y4, y100, y400;
calendar = (char)toupper((int)calendar);
gdn = dt->gdn;
gdn += 577735L + 2*(calendar=='J');
y400 = 146100L - 3*(calendar=='G');
y100 =  36525L -   (calendar=='G');
y4   =   1461L;
exception = FALSE;
year = 400*lfloor(gdn,y400);        /*  400-year periods  */
gdn -= y400*lfloor(gdn,y400);
if ( gdn > 0L )
    {
    year += 100*lfloor(gdn,y100);   /*  100-year periods  */
    gdn -= y100*lfloor(gdn,y100);
    exception = ( gdn == 0L && calendar == 'G' );
    if ( gdn > 0L )
        {
        year += 4*lfloor(gdn,y4);   /*  4-year periods  */
        gdn -= y4*lfloor(gdn,y4);
        if ( gdn > 0L )
            {
            i = 0;
            while ( gdn > 365 && ++i < 4 )
                {
                year++;
                gdn -= 365L;
                }
            }
        }
    }
if ( exception )
    gdn = 366L;
   /*  occurs once every hundred years with Gregorian calendar  */
else
    {
    year++;
    gdn++;
    }
set_feb_length(year,calendar);
month = 1;
while ( month < 13 && gdn > month_length[month] )
    gdn -= month_length[month++];
if ( month == 13 )
    {
    month = 1;
    year++;
    }
reset_feb_length();
dt->day = (int)gdn;
dt->month = month;
dt->year = year;
dt->valid = TRUE;
}
long lfloor(long a,long b)               /*  assumes b positive  */
{
return ( a >= 0L ? a/b : ( a%b == 0L ) - 1 - labs(a)/b );
/*  labs() returns the absolute value of its long int argument  */
}
/*  returns day of week for given Gregorian day number; 0=Sunday, 6=Saturday */
int day_of_week(long gdn)
{
return ((int)(((gdn%7)+12)%7));
}
Listing Three

Since there are over four million input numbers that could be tested, exhaustive testing is not practical unless you can run the program on a very fast computer. It has been shown, however, that when using DATETEST 0 1, no bugs are revealed for all Gregorian-day numbers in the range -14,235,000 through 14,235,000. The corresponding dates include all dates in both calendars for all years from -37,390 to 40,555.

Derivative Calendrical Functions

Given the basic date-number conversion functions, it is not difficult to develop other calendrical functions. In fact, there are 38 date functions in the Dolphin C Toolkit, including functions to determine which of two dates is earlier, how many days separate two dates, and how many weekdays separate two dates. There is also a function to format a date in hundreds of different ways.

Conclusion

The Dolphin C Toolkit includes a demonstration program, CAL_FNS, which allows conversion between dates and Julian-day numbers. This program also provides the day of the week of any date and the number of days between two dates. CAL_FNS allows us to determine, for example, that the storming of the Bastille occurred on a Tuesday. We can also discover that on the evening of April 18, 1521, when Luther defended himself against charges of heresy before the Holy Roman Emperor, Charles V, it was a Thursday. Finally, the coronation of Charlemagne as Holy Roman Emperor in Rome on Christmas day, 800, occurred on a Friday. We may reasonably suppose that festivities continued throughout the weekend.


Adopting the Gregorian Calendar

There was no necessity for 10 days, rather than, say, 12 days to have been omitted from the calendar. In fact, the calendar could have been reformed so as to keep the year in step with the seasons without omitting any days at all, since only the new rule for leap years is required to keep the calendar synchronized with the equinoxes. In fact, ten days were omitted in order to fix the date for the Spring equinox at March 21, which was the date of the equinox at the time of the Council of Nicea in the fourth century.

Upon the promulgation of Pope Gregory's decree, the Gregorian calendar was adopted immediately in Italy, Spain, Portugal, and Poland, and shortly thereafter in France and Luxembourg. During the next two years, most Catholic regions of Germany, Belgium, Switzerland, and the Netherlands came on board. Hungary followed in 1587. The rest of the Netherlands, Denmark, Germany, and Switzerland made the change in 1699-1701.

By the time the British were ready to acquiesce, the old calendar had drifted off by one more day, requiring a correction of 11 days, rather than 10, to locate the Spring equinox (usually) at March 21. The Gregorian calendar was adopted in Britain (and in the British colonies) in 1752, with September 2, 1752 being followed immediately by September 14, 1752.

In many countries, the Julian calendar was used by the general population long after the official introduction of the Gregorian calendar. Thus, events were recorded in the sixteenth to eighteenth centuries with various dates, depending on which calendar was used. Dates recorded in the Julian calendar were marked "O.S." for "Old Style," and those in the Gregorian calendar were marked "N.S." for "New Style."

To complicate matters further, the first day of the year was celebrated in different countries and regions on January 1, March 1, March 25, or December 25. With the introduction of the Gregorian calendar in Britain and the American colonies, people ceased to celebrate New Year's Day on March 25, as had been their custom, and instead began to celebrate it on January 1. Previously, March 24 of one year had been followed by March 25 of the next year. Thus George Washington's birthday, which was 2/11/1731 O.S., became 2/22/1732 N.S.

Sweden adopted the Gregorian calendar in 1753, Japan in 1873, Egypt in 1875, China and Albania in 1912, Bulgaria in 1915 or 1916, Romania in 1919, and Turkey in 1927. Following the Bolshevik Revolution in Russia, it was decreed that the day following January 31, 1918 O.S., would become February 14, 1918 N.S.

In 1923, the Eastern-Orthodox church adopted a modified form of the Gregorian calendar. Whereas in the Gregorian calendar a century year is a leap year only if division by 4 leaves a remainder of 1, 2, or 3, in Eastern system a century year is a leap year only if division of 9 leaves a remainder of 2 or 6. This renders the calendar slightly more accurate. October 1, 1923 in the Julian calendar became October 14, 1923 in the Eastern-Orthodox calendar. The date of Easter is determined by reference to modern lunar astronomy (in contrast to the more approximate, rule-based, lunar model of the Gregorian system.)

--P.M.


This article includes source code and executables available via FTP in the sourcecode/ddj/1993/9303.zip directory. See dateconv.zip.



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.