Subject: Re: date routine
Organization: From Home
Date: Wed, 30 Dec 1992 13:02:00 +0000

David Young asked :

> Does anyone know of a routine or method for calculating the difference
> between two given days?  Thanks in advance.


Here is a description and set of algorithms I have used in the past :

      NOTE:  This document is based on a software notebook which appeared some
      time ago in "Computing".  It was entitled "Computing the Calendar" by
      Martin Vlietstra.

      At some stage in their careers, most programmers use various features
      associated with the calendar.  Many operating systems have a number of
      system calls which can do such manipulations, but they are not always
      suitable or portable.  In such circumstances, programmers must write
      their own procedures.  This notebook describes some algorithms that have
      been successfully used.

      Ancient man based his calendar on two phenomena, the phase of the moon
      and phase of the sun.  Unfortunately, one phase does not divide into the
      other exactly, so compromises had to be made.  The Ancient Romans used a
      year of 365 days and a complex system of months.  By the time of Julius
      Caesar, this calendar, which used the founding of Rome in 753BC as its
      baseline, was badly out of phase with the seasons.  Caesar, on the
      advice of his astronomer Sosigenes, ordered that the year 46BC have 445
      days to bring the calendar back into phase with the seasons.  He also
      ordered that an extra day should be added to every fourth year, thereby
      creating leap years, and he formalised the use of 12 months.

      The months that he defined are virtually those we know today.  This was
      known as the Julian Calendar.  In 816AD, the Council of Chelsea fixed
      the Christian Calendar by adopting the Julian Calendar, but using the
      birth of Christ as a baseline for numbering the years.  Historians now
      believe that there was a four-year error in the new baseline.

      By the 16th centrury a discrepancy of 10 days had crept into the
      calendar, so Pope Gregory decreed that 10 days should be skipped during
      October 1582 and that in future any year ending in 00 should not be a
      leap year unless it was divisible by 400.  Protestant England, being
      suspicious of anything coming from Rome, only adopted the Gregorian
      Calendar in 1752.  The years 1800, 1900, 2100, 2200 are not leap years,
      whereas 1600, 2000 and 2400 are.  Thus people who assume that any year
      which is divisible by four is a leap year will only be in error when
      dealing with dates 111 years hence or 89 years ago.

      The Julian day which is unconnected with the Julian year was introduced
      by Joseph Scaliger in 1582.  Named in honour of Scaliger's father, it is
      a count of days since 1 January 4713BC.  It is widely used by astronomers
      to record various happenings.  The Julian day starts at noon, rather than
      at midnight, so is cumbersome to use.

      The International Standards Organisation has taken over the role of
      Caesar, the Pope and Parliament in standardising the calendar, among
      other things.  Many ISO standards have equivalent British Standards
      Institute Numbers.  Among the standards adopted by ISO, the following
      are used in this paper.

      ISO 2711:  The standard week starts on a Monday and ends on a Sunday.  If
      numbers are to be associated with the days of the week, then Monday is
      deemed to be a day one and Sunday day seven.

      ISO 2015:  Week one of the year contains the first Thursday of the year.
      Thus it always contains at least four days of the year with which it is
      associated.  A few days from one year are usually deemed to be part of
      the last week of the proceeding year or part of the first week of the
      succeeding year.  For example, if 31 December falls on a Monday, it is
      deemed to be day one of week one of the new year.

      In the algorithms that follow, all variables are positive integers; all
      arrays are indexed from 1 (not from 0); comments are denoted in the
      standard C style thus: /* This is a comment */.  The syntax of the
      algorithms has also been converted to C from the original article's
      pseudo code format.

      If a computer program is required to manipulate dates, then the dates
      should have both an internal format and an external format and a pair of
      routines to convert from one to the other.  The external format is the
      one with which the program interacts with the outside world.  In
      practice, it would be a set of three integers representing the day,
      month and year.

      The internal format is chosen to simplify manipulation of the date.  The
      one used in this paper is the day_number - a count of days since a
      particular base date.  Such a format is compact and very useful when
      calculating the number of days between two events - it is the difference
      between the respective day_numbers.

      This representation allows reasonable scope for specifying dates.  Even
      if 16-bit integers are used to hold the day-number and these are
      restricted to the range -32768 to +32767.  A range of +/-88 years can
      still be defined.

      For the period 1 March 1900 to 28 February 2100, the relationship
      between the date and the day of the week calendar follows a 28-year
      cycle, because there are seven days of the week and every fourth year is
      a leap year.

      The algorithms require the base date to be a Monday and 1 January of any
      year following a leap year.  Suitable base dates include 1 January 1917,
      1945, 1973 and 2001.  As the algorithm describes below only works for
      positive numbers, users should exercise care in their choice of base
      date.  The base date is deemed to be day one.

      The algorithms for conversion between date and day of the week use two
      arrays M0 and M1, each with 12 elements.  These contain the number of
      days before the first day of each month for non-leap years and leap
      years respectively.  The elements of the arrays correspond to January,
      February and so on.

         int M0[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}

         int M1[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}

      y, m and d are the calendar year, month and day, dd it a temporary
      parameter.

      The following hard-coded constants are used:

      1461 = 365 * 4 + 1   -- the number of days in 4 years
      1095 = 365 * 3       -- the number of days in 3 years

      The algorithm for computing day_number, given y, m and d is:

         if(y % 4 == 0)
            day_number = ((y - base_year/4) * 1461 + 1095 + M1[m] + d;
         else
            day_number = ((y - base_year/4)*1461+ ((y-1) % 4)* 365+ M0[m] + d;

      The algorithm for computing y, m and d, given day_number is:

         if((day_number % 1461) == 0)   /* The last day of a leap year */
         {
            y = base_year + (day_number/1461)*4 - 1;
            m = 12;
            d = 31;
         }
         else
         {
            dd = day_number % 1461;
            y  = base_year+ (day_number/1461)*4 + (dd-1)/365;
            dd = ((dd-1) % 365) + 1;
            m  = 12;

            if((y % 4) == 0)   /* a leap year */
               while(dd <= M1[m])
                  m = dd - M1[m];
            else
            {
               while(dd < Mo[m])
                  m = m - 1;
               d : = dd-Mo(m);
            }
         }

      Numeric values associated with the day of the week and with the week
      number are defined by ISO Standards 2015 and 2711.  The day of the week
      is easily calculated, given the day number as described above.  It is

         day_of_week = ((day_number-1) % 7) + 1

      The week number is a little more complex.  Since the last few days of the
      old year and the first few days of the new year do not necessarily fall
      in a week assocaiated with that year, the algorithm for computing the
      week number does not use the calendar year.  Instead it uses the 20 year
      cycle of the calendar and makes reference to a 28-element array.  Each
      element of the array contains the number of weeks in each 'year' (either
      52 or 53) of the cycle.  The elements have the following values:

         int W[] =  {52,  52,  52,  53,  52,  52,  52
                     52,  53,  52,  52,  52,  52,  52
                     53,  52,  52,  52,  52,  53,  52
                     52,  52,  52,  52,  53,  52,  52}

      The output variables, yr and wk are the year and week number.  The other
      variables are the same as used earlier. The following hard-coded constant
      is used:

         10227 = (365*4+1)*7   /* the number of days in 28 years */

      The agorithm is:

         dd = day _count % 10227;
         if(dd == 0)       /* the last day of a 28 year cycle  */
         {
            yr = 0;
            wk = 52;
         }
         else
         {
            yr = 1;
            wk = ((dd-1) / 7) + 1;
            while(wk > W[yr])
            {
               wk = wk - W[yr];
               yr = yr+1;
            }
         }
         yr = yr + base_year + (day_count/10227)*28 - 1;

      Easter falls on the Sunday following the first full moon occurring on or
      after 21 March.  As far as english law is concerned, the exact means of
      caluclating Easter was laid down by an Act of Parliament in 1784, but
      this act merely confirms the decision taken at the Council of Nicaea in
      325AD.  The algorithm that follows makes use of the fact that every 19
      years the Paschal new moon falls on the same day.

      The Golden Year as defined by the Council of Nicaea is represented in
      the algorithm by G.  The number of day from 21 March until the Paschal
      new year for each year in the 19-year cycle is given by the array
      'offset'.  It contains the following values:

         int offset[] = {24, 13,  2, 21, 10, 28, 18,  7, 26, 15,
                          4, 23, 12,  1, 20,  9, 27, 17,  6}

      Day_no is a function which returns the day_number of year, month and day
      specified.  The algorithm of finding the day_number associated which
      Easter for the year y is

         G = 1 + (y % 19):
         Easter = ((offset[G]+ day_no(y , 3, 21)) / 7 + 1) * 7

      Most of the festivals in the Christian Calendar use either Christmas or
      Easter as their baseline.  Some of these festivals, such as Shrove
      Tuesday, Pancake day, have traditinal celebrations, while others, such as
      Whit Monday are bank holidays in many EC countries.  These days include:

         Shrove Tuesday = Easter - 40
         Good Friday    = Easter -  2
         Easter Monday  = Easter +  1
         Ascension Day  = Easter + 39
         Whit Monday    = Easter + 50

      These festivals can easily be calculated by adding or substracting the
      appropriate number from the day_number representing Easter and then
      converting back to the user's (year, month, day) format.


  Anthony Roby                   aroby@robocop.demon.co.uk
  7 Delcombe Avenue,             aroby@cix.compulink.co.uk
  Worcester Park,
  Surrey.  ENGLAND.              Voice : +44 81 337 0913
Comments: Gated by NETNEWS@AUVM.AMERICAN.EDU