Julian and Gregorian Calendars

Date conversions between different calendar systems


February 14, 2007
URL:http://www.drdobbs.com/parallel/julian-and-gregorian-calendars/197006254

Peter, who currently works in AI research, is the author of the Dolphin C Toolkit and Dolphin Encrypt. He can be reached at http://www.hermetic.ch/.


The Western calendrical system -- known as the Julian calendar and consisting of a year of 12 months and of 365 days with an extra day every fourth year -- was established by Julius Caesar (following the advice of the Alexandrian astronomer Sosigenes) in 46 B.C. The extra day may not have been added consistently until A.D. 8, during the reign of Augustus. Subsequently, this calendar became widespread as a result of the expansion of the Roman Empire. The system of numbering years Anno Domini was instituted in A.D. 525 by the Roman abbot Dionysius Exiguus.

The Julian calendar assumes that the average length of a year is 365 days and six hours (since one day is added every four years). The length of the year assumed in the Julian calendar exceeds the current true value by about 11 minutes, resulting in an error of about three days every 400 years. Thus, as the centuries passed the Julian calendar became increasingly inaccurate with respect to the solar year as defined in terms of the solstices and the equinoxes. This was especially troubling to the Church because it affected the determination of the date of Easter, which by the sixteenth century was slipping gradually into summer. To resolve these problems, the calendar was reformed in 1582 on the authority of Pope Gregory XIII, and the modified calendar is called the Gregorian calendar.

In this article, I'll present a C function which converts any date within an 11-million-year period in either the Gregorian calendar or the Julian calendar into a unique long int, a number in the range of approximately -2,000,000,000 through 2,000,000,000. A function is also given for conversion of a long int back into a date in one of the calendars. This permits conversion between dates in the Julian and Gregorian calendars and provides a basis for other date-manipulation functions. The date-conversion functions given in this article are used in a general C-function library that I developed, the Dolphin C Toolkit.

Universal Date Conversion

According to the Gregorian reform, ten days (or more exactly, dates) were omitted from the c lendar. It was decreed that the day following October 4, 1582 (which was October 5, 1582 in the old calendar) would thenceforth be known as October 15, 1582. In addition, the rule for leap years was changed. In the Julian calendar, a year is a leap year if it is divisible by 4. In the Gregorian calendar, a year is a leap year if it is divisible by 4, with the added criterion that years divisible by 100 must also be divisible by 400. Thus the years 1600 and 2000 are leap years, but 1700, 1800, 1900, and 2100 are not. Finally, it was decreed that new rules for the determination of the date of Easter would be adopted.

Day Numbers

Astronomers use a system of numbering days called Julian-day numbers. The term "Julian-day number" (unlike the term "Julian calendar") does not derive from the name of Julius Caesar. This numbering system is said to have been named after Julius, the father of its inventor. The astronomical system of Julian-day numbers should not be confused with the simpler system of the same name, which associates a date with the number of days elapsed since January first of the same year (according to which December 31, 1993 is Day 365).

The astronomical Julian-day number of the day specified by a date in either the Julian or the Gregorian calendar is the number of days elapsed from the day designated as January 1, 4713 B.C. in the Julian proleptic calendar. (See the textbox entitled, "The Proleptic Calendars" for more information.) Thus, the Julian-day number of 1/1/-4712 (J) is 0. Note that 4713 B.C. is the year -4712. The Julian-day number of 10/10/1992 is 2,448,906.


The Proleptic Calendars

Every date recorded in history prior to October 15, 1582 (Gregorian), such as the coronation of Charlemagne as Holy Roman Emperor on Christmas day in the year 800, is a date in the Julian calendar, since on those dates the Gregorian calendar had not yet been invented. We can, however, identify particular days prior to October 15, 1582 (Gregorian) by means of dates in the Gregorian calendar simply by projecting the Gregorian dating system back beyond the time of its implementation. A calendar obtained by extension earlier in time than its invention is called "proleptic."

For example, although the Gregorian calendar was implemented on October 15, 1582 (Gregorian), we can still say that the date one year before was October 15, 1581 (Gregorian), even though people alive on that day would have said that the date was October 5, 1581 (the Julian date at that time). As another example, the date of the coronation of Charlemagne was December 29, 800 in the Gregorian proleptic calendar.

Similarly, dates after October 15, 1582 (Gregorian) have equivalent, but different, dates in the Julian calendar. For example, this article was completed on October 10, 1992 in the Gregorian calendar, but we could equally well say that it was completed on September 28, 1992, in the Julian calendar. As another example, the date of the winter solstice in the year 2012 is December 21, 20012 (Gregorian), which is December 8, 2012 (Julian).

Thus, any day in the history of the Earth, either in the past or in the future, can be specified as a date in either of these two calendrical systems. The dates will generally be different. In fact, they will be the same only for dates from March 1, 200 to February 28, 300. The dates in neither calendar will coincide with the seasons in the distant past or distant future, but that does not affect the validity of these calendars as systems for uniquely identifying particular days.

Astronomers designate years by B.C. by means of negative numbers. In order to avoid a hiatus between the year 1 and the year -1, there has to be a year 0. Thus, astronomers adopt the convention: A.D. 1 is equal to Year 1; 1 B.C. is equal to Year 0; 2 B.C. is equal to Year -1; and so on. More generally, a year popularly designated n B.C. is designated by astronomers as the year -(n-1). Finally, the rules for leap years in both calendars are valid for the year 0 and for negative years, as well as for positive years.

--P.M.


There is a simple relationship between Gregorian-day numbers, as used in this method of date conversion, and Julian-day numbers. Given a Gregorian-day number, the corresponding Julian-day number is obtained by adding 2,299,161, the Julian-day number of October 15, 1582).

Listing One contains the header file, DATECONV.H, used by the date-conversion functions presented in this article. A structure Date contains values for day, month, and year, plus a value gdn (for Gregorian-day number, as explained shortly) and a flag indicating the validity of the date. A day/month/year value is ambiguous until the particular calendar is specified. A date is completely specified using an instance of the structure together with an instance of a separate char variable that has the value G or J.

/*  DATECONV.H -- Header file for date functions -- Last mod.: 1992-10-10  */
#define TRUE  1
#define FALSE 0
typedef struct
      {
      int day;
      int month;
      long year;
      long gdn;            /*  gdn and valid are for internal use  */
      int valid;           /*  by functions in this library  */
      } Date;
/*  declarations of functions defined in DATECONV.C  */
void date_to_gdn(Date *dt, char calendar);
void gdn_to_date(Date *dt, char calendar);
long lfloor(long a, long b);
int is_leap_year_c(long year, char calendar);
int is_leap_year(long year);
void set_feb_length(long yr, char calendar);
void reset_feb_length(void);
int day_of_week(long gdn);
Listing One

We first need to ascertain whether a given year is a leap year (in the Julian or Gregorian calendars). Functions to do this are given in DATECONV.C (see Listing Two). A universal date-integer conversion method, as understood here, consists of two functions: The first takes a date (either Julian or Gregorian) and returns a positive or negative integer (long int); the second takes a long int and a calendrical specification (G or J) and returns a date in that calendar. It does not matter which date corresponds to day 0 as long as there is a quickly computable, one-to-one correspondence between dates in a calendar and numbers.

/*  DATETEST.C -- Tests date conversion routines -- Last mod.: 1992-10-10  */
/*  Link with DATECONV.OBJ by using CL DATETEST.C DATECONV.C */

#include 
#include      /*  for exit()  */
#include 

#include "DATECONV.H"

void main(int argc, char **argv);
void display(long n);
void test(long n, char calendar);

Date date;

#define ESCAPE '\x1B'
#define N 3000
#define MAXIMUM_GDN 2146905911
/*  largest Gregorian day number that can be handled  */
void main(int argc, char **argv)
{
long n, m;
if ( argc < 3 )
    {
    printf("Syntax: DATETEST start increment\n");
    return;
    }
n = atol(argv[1]);          /*  number to start with  */
m = atol(argv[2]);          /*  increment  */
printf("Quit with Escape.");
while ( TRUE )
    {
    if ( n%(N*m) == 0 )
        {
        display(n);
        display(-n);
        }
    test(n,'G');
    test(n,'J');
    test(-n,'G');
    test(-n,'J');
    if ( n > MAXIMUM_GDN-m )
        {
        printf("\ngdn = %ld",n);
        return; /*  since next n is too large  */
        }
    n += m;
    if ( kbhit() )
        {
        if ( getch() == ESCAPE )
            break;
        }
    }
}
void display(long n)
{
date.gdn = n;
printf("\ngdn =%12ld",n);
gdn_to_date(&date,'G');
printf("   %02d/%02d/%ld (G)",date.month,date.day,date.year);
gdn_to_date(&date,'J');
printf("   %02d/%02d/%ld (J)",date.month,date.day,date.year);
}
void test(long n,char calendar)
{
date.gdn = n;
gdn_to_date(&date,calendar);
if ( !date.valid )
    {
    printf("\ngdn = %ld  %d/%d/%ld (%c)  Date invalid!",
        date.gdn,date.month,date.day,date.year,calendar);
    exit(1);
    }
date.gdn = 0L;
date_to_gdn(&date,calendar);
if ( date.gdn != n )
    {
    printf("\ngdn = %ld  %d/%d/%ld (%c)   n = %ld!",
        date.gdn,date.month,date.day,date.year,calendar,n);
    exit(1);
    }
}
Listing Two

The method presented here converts dates into the number of days before or after October 15, 1582--the day that the Gregorian calendar came into effect. Thus, October 15, 1582 (Gregorian) corresponds to day 0; October 16, 1582 (Gregorian) to day 1; and October 14, 1582 (Gregorian) to day -1. The number corresponding to a date is thus called the "Gregorian-day number." Dates in the Julian calendar, as well as those in the Gregorian calendar, are mapped into Gregorian-day numbers. Thus, the day preceding October 15, 1582 in the Gregorian calendar is both October 4 in the Julian calendar and October 14 in the Gregorian calendar--both have Gregorian-day number -1.

The code for the function to convert a date in one of the calendars to a Gregorian-day number, and for the function to convert a Gregorian-day number to a date in a specified calendar, is given in Listing Two. The Date structure uses long int variables (signed) for the year and the Gregorian-day number. The largest integer representable as a signed long int is 2,147,483,647. Due to the method of calculation, however, the largest Gregorian-day number that can be used is 2,146,905,911. This corresponds to the dates July 11, 5,879,611 (Gregorian) and October 19, 5,879,490 (Julian). By that time, dates in the Gregorian and Julian calendars will differ by about 121 years. (This will be of no practical importance since by that time both calendars will likely have been superseded.)

More Details

To use the conversion functions, declare a structure of type Date (defined in Listing One) and pass to the functions a pointer to the structure along with a calendrical specification (G or J). If you are converting from Gregorian-day number to date, define the gdn structure variable before calling gdn_to_date(). Conversely, define the variables day, month, and year before calling date_to_gdn(). On return from the functions, extract either gdn or the date values from the structure. Before using the gdn value, it is advisable to check the validity flag, which will be TRUE if the date passed was a valid date in the specified calendar, and FALSE otherwise. For example, the attempt to convert February 29, 1900 (Gregorian) to a Gregorian-day number will produce a FALSE in the validity variable.

The lfloor() function in Listing Two is analogous to the Microsoft floating-point library floor() function, and overcomes a small problem in integer arithmetic. The date-conversion functions described earlier need a long-integer division operation such that, for all long integers a and b, a/b is the largest integer not greater than the real number a/b. MSC's division operator produces this result if a and b are positive, but not if a is negative and b is positive. The lfloor() function provides what is needed.

Finally, DATECONV.C also contains a function to convert a Gregorian-day number into a day of the week. This is independent of the particular calendar used.

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      /*  for labs()  */
#include 
#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.


Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.