The Easter calculation of Donald Knuth

Karl-Heinz Lewin

I am using the formulas for the Julian and Gregorian calendars from the article The Calculation of Easter... by Donald Knuth:


Julian calendar  Meaning
golden_number = year mod 19 + 1 Golden number
epact = ( 11 * golden_number - 4 ) mod 30 + 1 Age of the calendar moon at the beginning of the year
extra_days = 5 * year div 4 mod 7 determines when it is Sunday in March
om = 44 - epact, if om < 21 then om += 30 Day of the Easter moon, the first full moon of spring, counted from 1 March.
os = om + 7 - ( om + extra_days ) mod 7 Day of Easter Sunday, counted from 1 March
Gregorian calendar  Meaning
golden_number = year mod 19 + 1 Golden number
century = year div 100 + 1 Century
gregorian_correction = ( 3 * century ) div 4 - 12 Number of past years such as 1700, 1800, 1900, which were not leap years
clavian_correction = ( century - 16 - ( century - 18 ) div 25 ) div 3
epact = ( 11 * golden_number + 20 + clavian_correction - gregorian_correction ) mod 30
if ( epact <= 0 ) then epact += 30
if ( epact == 25 and golden_number > 11 ) or epact == 24 ) then epact += 1
Age of the calendar moon at the beginning of the year
extra_days = ( 5 * year div 4 - gregorian_correction - 10 ) mod 7 determines when it is Sunday in March
om = 44 - epact, if om < 21 then om += 30 Day of the Easter moon, the first full moon of spring, counted from 1 March.
os = om + 7 - ( om + extra_days ) mod 7 Day of Easter Sunday, counted from 1 March

Note: The bold operations "mod 7" in the calculations of the "extra_days" are missing in the publication of the CACM. Although this omission is compensated for in the calculation of Easter Sunday in the last line, it would make the presentation of the intermediate results incomprehensible. For example, extra_days = 2530 for 2024 (Julian), but mod 7 would result in extra_days = 3.

Implementation in a spreadsheet programme

(Here Microsoft Excel 2007, German licence)

I am only showing the solution for the Julian calendar here; the solution for the Gregorian calendar would be much more complicated, which is why I leave it out

Field

Contents

Meaning

A1

'Year

Year number

B1

'GoldenN

Golden number

C1

'Epact

Epacr

D1

'ED

Extra days

E1

'EM

Day of the Easter moon

F1

'EMcorr

corrected day of the Easter moon

G1

'ES

Day of Easter Sunday, counted from 1 March

H1

'ES

Day date of Easter Sunday

I1

'Month

Month of Easter Sunday

A2

532

Start year

B2

=REST(A2;19)

C2

=REST(11*B2-4;30)+1

D2

=REST(GANZZAHL((5*A2)/4);7)

E2

=44-C2

F2

=WENN(E2<21;E2+30;E2)

G2

=21+E2)

H2

=WENN(G2>31;G2-31;G2)

I2

=WENN(G2>31;4;3)

A3

=A2+1

B3:I3

Copy of B2:I2

A4:I96

Copy of A3:I3

Output

  Year    GoldenN Epact   ED    EM  EMcorr  ES  ESdate  Month
   532        1      8     0    36    36    42    11      4  
   533        2     19     1    25    25    27    27      3  
   534        3     30     2    14    44    47    16      4  
   535        4     11     3    33    33    39     8      4  
   536        5     22     5    22    22    23    23      3  
   537        6      3     6    41    41    43    12      4  
   538        7      4     0    30    30    35     4      4  
   539        8     25     1    19    49    55    24      4  
   540        9      6     3    38    38    39     8      4  
   541       10     17     4    27    27    31    31      3  
   542       11     28     5    16    46    51    20      4  
   543       12      9     6    35    35    36     5      4  
   544       13     20     1    24    24    27    27      3  
   545       14      1     2    43    43    47    16      4  
   546       15     12     3    32    32    39     8      4  
   547       16     23     4    21    21    24    24      3  
   548       17      4     6    40    40    43    12      4  
   549       18     15     0    29    29    35     4      4  
   550       19     26     1    18    48    55    24      4  
   ...                                                       


Implementation in JavaScript

	function DaynumberToDayAndMonth( daynumber ) {
		// assert( daynumber > 0 && daynumber < 62 );
		if ( daynumber <= 31 ) {
			this.dd = daynumber;
			this.mm = 3;
		} else {
			this.dd = daynumber - 31;
			this.mm = 4;
		}
		return this;
	}
	
	function floor( a ) {
		return Math.floor( a );
	}

	function div( a, b ) { // integer division
		return floor( a / b );
	}

	function DonKnuthGregorianEasterTableLine( year ) {
		var golden_number, century, gregorian_correction, clavian_correction, extra_days, epact;
		// assert( year >= 0 && year <= 4999 );
		golden_number = this.k_gn;
		century = div( year , 100 ) + 1;
		gregorian_correction = div( 3 * century, 4 ) - 12;
		clavian_correction = div( century - 16 - div( century - 18, 25 ), 3 );
		extra_days = ( div( 5 * year, 4 ) - gregorian_correction - 10 ) % 7 ;
		epact = ( 11 * golden_number + 20 + clavian_correction  - gregorian_correction ) % 30;
		if ( epact <= 0 ) {
			epact += 30;
		}
		if ( epact == 25 && golden_number > 11 || epact == 24 ) {
			epact += 1;
		} 
		this.k_ep = epact;
		this.k_ed = extra_days;
		return this;
	}

	function DonKnuthJulianEasterTableLine( year ) {
		var golden_number, extra_days, epact;
		// assert( year >= 0 && year <= 4999 );
		golden_number = this.k_gn;
		extra_days = ( div( 5 * year, 4 ) ) % 7 ;
		epact = ( 11 * golden_number - 4 ) % 30 + 1;
		this.k_ep = epact;
		this.k_ed = extra_days;
		return this;
	}

	function DonKnuthEasterTableLine( year, cal ) {
		var golden_number, day, epact, extra_days, ee;
		// assert( year >= 0 && year <= 4999 );
		golden_number = year % 19 + 1;
		this.k_yr = year;
		this.k_gn = golden_number;
		switch ( cal ) {
			case 'J': // Julian calendar
			    ee = DonKnuthJulianEasterTableLine( year );
				break;
			case 'G': // Gregorian calendar
			    ee = DonKnuthGregorianEasterTableLine( year );
				break;
			case 'A': // Occidental calendar ("Abendländischer Kalender")
			    ee = ( year <= 1582 ) 
				   ? DonKnuthJulianEasterTableLine( year )
				   : DonKnuthGregorianEasterTableLine( year );
				break;
		}
		epact = this.ep;
		extra_days = this.k_ed;
		day = 44 - epact;
		if ( day < 21 ) {
			day += 30;
		}
		this.k_om = day;
		day += 7 - ( day + extra_days ) % 7;
		this.k_os = day;
		return this;
	}

	function DonKnuthEasterTable( annus, times, cal, outputformatter ) {
		// assert( annus >= 0 && annus <= 4996 );
		// assert( times >= 4 && times <= 532 );
		// assert( annus + times <= 5000 );
		for ( let y = annus; y < annus + times; y++ ) {
			var line = DonKnuthEasterTableLine( y, cal );
			outputformatter( line );
		}
	}


Formal verification of the algorithm

If you click on the button below, a calculation of the Easter moon and Easter Sunday dates with all intermediate results generated according to the algorithm just developed with the JavaScript functions shown appears for comparison with the first 19-year section of the spreadsheet programme output shown above.
An Easter calculator with this algorithm, where you can choose the starting year, the number of years and the type of calendar (Julian, Gregorian, Occidental (i.e. Julian until 1582, Gregorian from 1583)), can be found at The Easter calculation by Donald Knuth as a table calculator.



Literature

Donald Knuth (1962): The calculation of Easter...; Communications of the ACM (CACM), Volume 5, Issue 4; April 1962; S. 209-210; https://doi.org/10.1145/366920.366980


The author is a mathematician and worked as a software developer.

Karl-Heinz Lewin, Haar: karl-heinz.lewin@t-online.de

Copyright © Karl-Heinz Lewin, 2024