Calculating the date of Easter for a particular year

A long time ago, in a galaxy far, far away – OK, it was in London in 1993 – I added a DateEaster function to my personal Dates unit, written in Borland Pascal 7. So: 16-bit DOS for all you oldies. For a bit of fun, I shall present it here with some commentary.

At the time I was really proud of this unit: the majority of it was written in assembly for speed reasons (it was used in a swaps trading app I was writing) and it could calculate things like days between two dates (including on a 360-day basis), adding/subtracting number of days, parsing date strings (including things like t+3d for “today plus 3 days”). All routines worked with dates from 1-Jan-1980 to 31-Dec-2099 (by the way, once of the reasons for stopping there is that 2100 is not a leap year, so by not including it meant that many calculations were simpler).

I even devised a packed format for the date type I was using, a longint that stored the day, month, year, and number of days since 1-Jan-1980 such that typecasting it back to a longint would ensure that date comparisons were valid and calculating text representations of dates was instantaneous.

{=Notes===============================================================

The TDate has the internal format:
   bits  0- 4 : day in month (1..31)
   bits  5- 8 : month (1..12)
   bits  9-15 : years since 1980 (0..119)
   bits 16-31 : days since 01-Jan-1980 less 22,000

This format ensures date comparisons can be made by typecasting to
longint.
=====================================================================}

At some point, I had to maintain a list of bank holiday dates as well, since no trading would occur on those days. The usual culprits: Christmas, New Year, the May and August bank holidays? No sweat, easy peasy. Easter on the other hand? Oh wow.

So I did some research on how Easter dates were calculated. These days this would be simple: Google “calculate date of Easter” and you’ll get to a wikipedia page on Computus. To be honest though, at this remove I cannot remember which reference book I used. Anyway, Easter Sunday is defined as the first Sunday that follows the first full moon that follows the Spring Equinox. Since the Spring Equinox falls on March 21, Easter Sunday can be any date between March 22 and April 25 in the Gregorian calendar. It so happens that, since the calculation of Easter is determined by both the lunar and the solar cycles, the dates of Easter repeat every 19 years, especially given the date range I was using. The variance in this lunar/solar cycle is given by so-called Golden Numbers (see the wikipedia article for details). This resulted in this routine:

{=DateEaster==========================================================
Calculates the date for Easter Sunday for a given year.
01Mar93 JMB
======================================================================}
function DateEaster(Y : word) : TDate;
  const
    _Mar = $60; _Apr = $80; {ie 3 and 4 in the top 3 bits }
    GoldenNumberToFullMoon : array [0..18] of byte =
       (14+_Apr,  3+_Apr, 23+_Mar, 11+_Apr, 31+_Mar, 18+_Apr,  8+_Apr,
        28+_Mar, 16+_Apr,  5+_Apr, 25+_Mar, 13+_Apr,  2+_Apr, 22+_Mar,
        10+_Apr, 30+_Mar, 17+_Apr,  7+_Apr, 27+_Mar);
    DaysToSunday : array [DayOfWeek] of byte = (6, 5, 4, 3, 2, 1, 7);
  var
    FullMoon : TDate;
    FullMMDD : byte;
  begin
    if (Y > 99) and ((Y < FirstYear) or (Y > LastYear)) then
      DateEaster := BadDate
    else
      begin
        FullMMDD := GoldenNumberToFullMoon[Y mod 19];
        FullMoon := DateFromYMD(Y, ((FullMMDD and $E0) shr 5), FullMMDD and $1F);
        DateEaster := DateAddDays(FullMoon, DaysToSunday[DateDOW(FullMoon)]);
      end;
  end;

I think I was so enamored with this code that I just left it as that. This Julian from 2016, on the other hand, would have used it in a throw-away program to calculate all the dates of Easter Sunday in the date range I allowed and added them as an fixed array to the unit. There’s only 120 of them, after all, so a mere 480 bytes to store them; for even more efficiency, I could have halved that by just storing the YMD values as a word.

The other thing that reminded me of this particular set of units is someone mentioning on some thread I was reading that you should be able to read and use code that you wrote 30 years ago. Personally I disagree with this viewpoint – this particular code is 23 years old and is fairly unusable now because it uses 16-bit DOS assembly language. An example:

{=DateToday===========================================================
Interrogates DOS for the system date and returns it as a TDate.
01Mar93 JMB
======================================================================}
function DateToday : TDate;
assembler;
  asm
    mov ax, 2A00h                   { Ask DOS for date                }
    int 21h                         { dd/mm/yy = dl / dh / cx         }
    push cx                         { Push year                       }
    xor ax, ax                      { Clear ax to get month           }
    xchg al, dh                     { Put month in ax                 }
    push ax                         { Push month                      }
    push dx                         { Push day                        }
    call DateFromYMD                { Convert to TDate                }
  end;

(And that’s the easiest bit of assembly in there.) Although I was inordinately proud of this code at the time, and it’s fun to see what kind of gyrations I used to go through to try and wrest every CPU cycle from Pascal code I could, there is no way I’d ever use it again. The world has moved on from these simpler early days. DOS is dead. Borland Pascal 7, although a great compiler/IDE, is dead. There are different ways of programming now, different methodologies to tame complexity (generic collections, functional programming, lambdas, etc). This code is an exemplar of its time, but that’s about it.

Painting the bedroom

Album cover for ElectrifiedNow playing:
Boris Blank - Future Past
(from Electrified)

Loading similar posts...   Loading links to posts on similar topics...

1 Response

 avatar
#1 Paul said...
27-Mar-16 1:57 PM

Great post. Gone are the days of writing memory conscious methods and as you put it 'wrestling ever CPU cycle'

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response