Home > Easter Date Algorithms |
This page is for computer programmers who wish to include Easter data calculation code in their programs. Please be aware that many shorter code segments are littered throughout the Internet. These usually have year restrictions and only calculate one type of Easter date (there are 3 types - read below). Even worse, many don't even state what their limitations are, or claim extraordinary coverage such as from 1AD to forever (this is just nonsense - the earliest valid year for any algorithm is 326 AD in the Julian calendar, and the Gregorian calendar is valid until about 4099 AD).
Algorithms on this page show different ways to calculate Easter Sunday dates. It is important to understand which method, which calendar and which year range applies to each method.
The Julian calendar applies to the original calculation method from 326 AD, which was the first year a unified Easter Dating method was used. Some methods claim to work from 1 AD, which is just plain silly - Easter was not celebrated before Christ's death, and as already stated, a unified method was not used until 326 AD. The Julian calendar had fallen out of alignment in measuring solar years (keeping months aligned with seasons) well before the Gregorian calendar was introduced. Nevertheless, the Julian calendar is still used today by Orthodox churches as the basis for their Easter.
The Gregorian calendar was first introduced in October 1582 in Italy, and has subsequently replaced the Julian calendar over following years in other countries. The last country to adopt the Gregorian calendar was Greece in 1923. This calendar is highly accurate, but will need a day adjustment in or shortly after 4100 AD. So Easter algorithms using the Gregorian calendar apply to years 1583 AD to 4099 AD.
Note that there is a new Easter Sunday date calculation that applies to the Gregorian calendar, but it was not necessarily adopted in all countries at the same time the Gregorian calendar was introduced. So you may need to do some research to determine how different countries implemented these changes. Of course, churches still using the Julian calendar simply convert their Julian Easter Sunday date to the equivalent date in the Gregorian calendar.
So there are 3 possible ways to calculate Easter date. The method depends on when the Gregorian calendar was used in the country of interest, and if applicable, when the Western calculation was implemented.
Is there a single definitive and correct algorithm? - YES! The algorithm based on the extraordinary research by Ron Mallen is correct for all years, using either method and the appropriate calendar. Ron's research was conducted over a period of more than 20 years, and covers aspects of astronomy, calendar history and the Easter Dating methods. You can see his Easter information at the Astonomical Society of South Australia site.
What Ron has done is to condense the approx 19 tables used in the Christian prayer books that define Easter into 5 tables that allow anyone to calculate Easter with just a calculator. This has been cross-checked with known historic dates, astronomical publications, other algorithms and various scientific references. Note that he has also discovered that many encyclopaedia and almanac definitions have heavily plagiarised a wrong definition from around the mid 1970's.
I wrote the algorithm that implements these tables, and have shown it below as EasterMallen. I have reverse-engineered this algorithm to reproduce the source tables and they match exactly. It is not the shortest algorithm, but because it can be proved to be directly derived from the published definition, I am confident this method is 100% accurate.
Henk Reints has a good site showing how different algorithms work. If you use any of these, or even your algorithm, I strongly suggest you check it against EasterMallen for accuracy.
You can cut and paste the code below. Most code is in Visual Basic, however, there are also implementations for C, Delphi (object Pascal) Javascript (or ECMA Script) and Matlab.
To keep things well defined, I have used the following method constants in Visual Basic code below, and have included argument checking in all VB algorithms.
This example shows how to call the procedure EasterMallen() for the year 1999 and method 3 in Visual Basic.
'declarations (all integers) Const EDM_JULIAN = 1 Const EDM_ORTHODOX = 2 Const EDM_WESTERN = 3 'in an event procedure ... Dim d As Integer Dim m As Integer Dim y As Integer Dim method As Integer 'get year and method from some user input y = 1999 method = EDM_WESTERN If EasterDate(d, m, y, method) then ' _ is a line continuation character MsgBox "Easter in " & Format$(y) & " occurs on Sunday " _ & Format$(DateSerial(y, m, d), "Medium date"), vbInformation End If
In Visual Basic, this is correct for all methods for all years in the appropriate calendars. Function EasterOudin (d, m, ByVal y, ByVal method) As Boolean '========================================================== 'This algorithm is adapted from a faq document by Claus Tondering 'URL: http://www.pip.dknet.dk/~pip10160/calendar.faq2.txt 'E-mail: c-t@pip.dknet.dk. 'The FAQ algorithm is based in part on the algorithm of Oudin (1940) 'as quoted in "Explanatory Supplement to the Astronomical Almanac", 'P. Kenneth Seidelmann, editor. 'Additions by GM Arts: '(1) checks year and method arguments are valid '(2) adds method 2 calculations (original calculation, converted to Gregorian date) '(3) modified d and m calculations to account for later dates produced by method 2 '========================================================== ' Validate arguments ' returns true if y (year) and method combination is valid ' y and method are both integers d = 0 'default values for invalid arguments m = 0 EasterOudin = False If method < 1 Or method > 3 Then MsgBox "Method must be 1, 2 or 3", vbExclamation Exit Function ElseIf method = 1 And y < 326 Then MsgBox "The original calculation applies to all years from 326 AD", vbExclamation Exit Function ElseIf (method = 2 Or method = 3) And (y < 1583 Or y > 4099) Then MsgBox "Gregorian calendar Easters apply for years 1583 to 4099 only", vbExclamation Exit Function Else EasterOudin = True End If Dim g 'golden year - 1 Dim c 'century Dim h '= (23 - Epact) mod 30 Dim i 'no of days from March 21 to Paschal Full Moon Dim j 'weekday for PFM (0=Sunday, etc) Dim p 'no of days from March 21 to Sunday on or before PFM '(-6 to 28 methods 1 & 3, to 56 for method 2) Dim e 'extra days to add for method 2 (converting Julian date to Gregorian date) g = y Mod 19 If method = 1 Or method = 2 Then 'old method i = (19 * g + 15) Mod 30 j = (y + y \ 4 + i) Mod 7 If method = 2 Then 'extra dates to convert Julian to Gregorian date e = 10 If y > 1600 Then e = e + y \ 100 - 16 - (y \ 100 - 16) \ 4 End If ElseIf method = 3 Then 'new method c = y \ 100 h = (c - c \ 4 - (8 * c + 13) \ 25 + 19 * g + 15) Mod 30 i = h - (h \ 28) * (1 - (h \ 28) * (29 \ (h + 1)) * ((21 - g) \ 11)) j = (y + y \ 4 + i + 2 - c + c \ 4) Mod 7 End If 'return day and month p = i - j + e ' p can be from -6 to 56 corresponding to dates 22 March to 23 May ' (later dates apply to method 2, although 23 May never actually occurs) d = 1 + (p + 27 + (p + 6) \ 40) Mod 31 m = 3 + (p + 26) \ 30 End Function |
This is correct for method 1, for all years from 326 AD in the Julian calendar. Function EasterGauss(d, m, y, method) As Boolean 'returns Orthodox Easter Sundays in the Julian calendar ' Validate arguments ' returns true if y (year) and method combination is valid ' y and method are both integers d = 0 'default values for invalid arguments m = 0 EasterGauss = False If method <> 1 Then MsgBox "Method must be 1", vbExclamation Exit Function ElseIf y < 326 Then MsgBox "The original calculation applies to all years from 326 AD", vbExclamation Exit Function Else EasterGauss = True End If Dim R1, R2, R3, R4, R5 Dim RA, RB, RC R1 = y Mod 19 R2 = y Mod 4 R3 = y Mod 7 RA = 19 * R1 + 16 R4 = RA Mod 30 RB = 2 * R2 + 4 * R3 + 6 * R4 R5 = RB Mod 7 RC = R4 + R5 d = RC + 21 m = 3 If d > 31 Then d = d - 31 m = 4 End If End Function |
Accurate for all years and all methods in the appropriate calendar. Function EasterKershaw(d, m, ByVal y, ByVal method) As Boolean 'method posted by Simon Kershaw '<webmaster@ely.anglican.org> ' Validate arguments ' returns true if y (year) and method combination is valid ' y and method are both integers d = 0 'default values for invalid arguments m = 0 EasterKershaw = False If method < 1 Or method > 3 Then MsgBox "Method must be 1, 2 or 3", vbExclamation Exit Function ElseIf method = 1 And y < 326 Then MsgBox "The original calculation applies to all years from 326 AD", vbExclamation Exit Function ElseIf (method = 2 Or method = 3) And (y < 1583 Or y > 4099) Then MsgBox "Gregorian calendar Easters apply for years 1583 to 4099 only", vbExclamation Exit Function Else EasterKershaw = True End If Dim golden, solar, lunar, pfm, dom golden = (y Mod 19) + 1 'the GOLDEN number If method = 1 Or method = 2 Then 'original calculation dom = (y + (y \ 4) + 5) Mod 7 'the DOMINICAL number - finding a Sunday pfm = PosMod(3 - (11 * golden) - 7, 30) 'uncorrected date of Paschal Full Moon ElseIf method = 3 Then 'revised calculation dom = PosMod(y + (y \ 4) - (y \ 100) + (y \ 400), 7) 'the DOMINICAL number - finding a Sunday solar = (y - 1600) \ 100 - (y - 1600) \ 400 'solar and lunar corrections lunar = (((y - 1400) \ 100) * 8) \ 25 pfm = PosMod(3 - (11 * golden) + solar - lunar, 30) 'uncorrected date of Paschal Full Moon End If If (pfm = 29) Or (pfm = 28 And golden > 11) Then pfm = pfm - 1 'corrected PFM date (days after 21 March) d = pfm + PosMod(4 - pfm - dom, 7) + 1 'Easter as no of days after 21 March If method = 2 Then 'convert to Gregorian calendar d = d + 10 If y > 1600 Then d = d + (y \ 100) - 16 - (((y \ 100) - 16) \ 4) End If If d < 11 Then m = 3 d = d + 21 ElseIf d < 41 Then m = 4 d = d - 10 Else m = 5 d = d - 40 End If End Function |
Accurate for method 3 (Western Easters only) from 1583 to 4099 in the Gregorian calendar. Function EasterHodges(dy, mth, ByVal y, ByVal method) As Boolean 'by David Hodges, derived by refining the "Butcher's Ecclesiastical Calendar" rule 'eliminating one step in the process Dim a, b, c, d, e, f, g, h, j, k, m, n, p ' Validate arguments If method <> 3 Or y < 1583 Or y > 4099 Then EasterHodges = False d = 0 m = 0 MsgBox "Hodges method only applies to the revised calculation in the Gregorian calendar from 1583 to 4099 AD" Exit Function End If EasterHodges = True a = y \ 100 b = y Mod 100 c = (3 * (a + 25)) \ 4 d = (3 * (a + 25)) Mod 4 e = (8 * (a + 11)) \ 25 f = (5 * a + b) Mod 19 g = (19 * f + c - e) Mod 30 h = (f + 11 * g) \ 319 j = (60 * (5 - d) + b) \ 4 k = (60 * (5 - d) + b) Mod 4 m = (2 * j - k - g + h) Mod 7 n = (g - h + m + 114) \ 31 p = (g - h + m + 114) Mod 31 dy = p + 1 mth = n 'Easter Sunday is g - h + m days after March 22nd '(the earliest possible Easter date) End Function |
C Implementation of GetEasterDate() The author calls functions in a date class. Below are 2 functions; Please be aware of the following: - The date class has 3 obvious members, but month numbers are 0-11. - We have overloaded Date+Int to add or subtract days from a date - GetDayOfWeek() simply returns 0 (Sunday) to 6 (Saturday) - The line 'if(nCent>20)' is simply a performance optimization because practical use will be for years in this and next century. - As you can see I have chosen a different algorithm for the Orthodox Easter. Since we write commercial software I preferred an algorithm that was not in a computer language. // - The code to find the Paschal Full Moon is adapted from a // Visual Basic program found on www.auslink.net/~gmarts. Date Date::RomanEasterSunday(int nYear) { int nCent=nYear/100; int nRemain19=nYear%19; // - n1 is the number of days since 21-Mar of the PFM int n1=(nCent-15)/2 + 202 - 11*nRemain19; if(nCent>20) { if(nCent>26) --n1; if(nCent>38) --n1; if(nCent==21 || nCent==24 || nCent==25 || nCent==33 || nCent==36 || nCent==37) --n1; } n1%=30; if(n1==29 || (n1==28 && nRemain19>10)) --n1; // - This can only be in March or April Date dtPFM; dtPFM.m_year=nYear; if(n1>10) { dtPFM.m_month=3; dtPFM.m_date=n1-10; } else { dtPFM.m_month=2; dtPFM.m_date=n1+21; } int nWeekDay=dtPFM.GetDayOfWeek(); return dtPFM+(7-nWeekDay); } // -This function is based off a non program based algoritm // described in cssa.stanford.edu/~marcos/ortheast.html. Date Date::OrthodoxEasterSunday(int nYear) { int nRemain19=nYear%19; int nRemain7=nYear%7; int nRemain4=nYear%4; // - Things are a bit simpler in the Julian Calendar, this is // a formula by Gauss for the number of days after 21-Mar. int n1=(19*nRemain19 + 16) % 30; int n2=(2*nRemain4+4*nRemain7+6*n1) % 7; int n3=n1+n2; // - Then convert to the Gregorian Calendar (1583 onwards) int nCent=nYear/100; n3+=nCent-nCent/4-2; // - The Orthodox Easter in the Gregorian calendar can fall in May // (and can not possibly fall in March anytime after year 1582). Date dt; dt.m_year=nYear; if(n3>40) { dt.m_month=4; dt.m_date=n3-40; } else if(n3>10) { dt.m_month=3; dt.m_date=n3-10; } else { dt.m_month=2; dt.m_date=n3+21; } return dt; } |
Delphi Implementation of GetEaterDate() procedure GetEasterDate (y, method : word; var d, m : integer); var FirstDig, Remain19, temp, {intermediate results} tA, tB, tC, tD, tE : integer; {table A to E results} begin (* :=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:= * This algorithm is an arithmetic interpretation * of the 3 step Easter Dating Method developed * by Ron Mallen 1985, as a vast improvement on * the method described in the Common Prayer Book * Published Australian Almanac 1988 * Refer to this publication, or the Canberra Library * for a clear understanding of the method used * Because this algorithm is a direct translation of the * official tables, it can be easily proved to be 100% * correct * It's free! Please do not modify code or comments! * 11.7.99 - Pascal converting by Thomas Koehler, www.thkoehler.de :=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=*) FirstDig := y div 100; {first 2 digits of year} Remain19 := y mod 19; {remainder of year / 19} if (method = 1) or (method = 2) then begin {calculate PFM date} tA := ((225 - 11 * Remain19) mod 30) + 21; {find the next Sunday} tB := (tA - 19) mod 7; tC := (40 - FirstDig) mod 7; temp := y mod 100; tD := (temp + temp div 4) mod 7; tE := ((20 - tB - tC - tD) mod 7) + 1; d := tA + tE; if method = 2 then {convert Julian to Gregorian date} begin {10 days were skipped in the Gregorian calendar from 5-14 Oct 1582} temp := 10; {Only 1 in every 4 century years are leap years in the Gregorian calendar (every century is a leap year in the Julian calendar)} if y > 1600 then temp := temp + FirstDig - 16 - ((FirstDig - 16) div 4); d := d + temp; end; end else begin {calculate PFM date} temp := (FirstDig - 15) div 2 + 202 - 11 * Remain19; if (FirstDig > 26) then temp := temp - 1; if (FirstDig > 38) then temp := temp - 1; if (FirstDig = 21) Or (FirstDig = 24) Or (FirstDig = 25) Or (FirstDig = 33) Or (FirstDig = 36) Or (FirstDig = 37) then temp := temp - 1; temp := temp mod 30; tA := temp + 21; if (temp = 29) then tA := tA - 1; if (temp = 28) and (Remain19 > 10) then tA := tA - 1; {find the next Sunday} tB := (tA - 19) mod 7; temp := (40 - FirstDig) mod 4; {//tC := temp - (temp > 1) - (temp := 3)} tC := temp; if temp > 1 then tC := tC + 1; if temp = 3 then tC := tC + 1; temp := y mod 100; tD := (temp + temp div 4) mod 7; tE := ((20 - tB - tC - tD) mod 7) + 1; d := tA + tE; end; {return the date} m := 3; if (d > 61) then begin d := d - 61; {when the original calculation is converted to the} m := 5; {Gregorian calendar, Easter Sunday can occur in May} end; if (d > 31) then begin d := d - 31; m := 4; end; end; |
Javascript Easter Date Calculator() Here's the form HTML code: <form name="UserInput"> Enter a year from 326 to 4099 AD ... <input name="YearEntry" type="text" value="2000"> <input type="button" value="Show Easters" onclick="ShowEasters(UserInput.YearEntry.value)" id="button1" name="button1"> </form> And here's the script: <script language="JavaScript"> <!-- // (C)opyright GM Arts 1997-1999 // GLOBAL VARIABLES // ~~~~~~~~~~~~~~~~ var jDay = 0; var jMonth = 0; var oDay = 0; var oMonth = 0; var wDay = 0; var wMonth = 0; var year = 0; // STARTUP CODE // ~~~~~~~~~~~~ // check browser is version 2 or more if (parseFloat(navigator.appVersion) < 2) alert ("You may need to upgrade your browser to reliably use the Easter Date calculator"); // FUNCTIONS // ~~~~~~~~~ function ShowEasters(yr) { year = parseInt(yr, 10); if (isNaN(year)) year = 0; // reset vars jDay = 0; jMonth = 0; oDay = 0; oMonth = 0; wDay = 0; wMonth = 0; //get relevant Easter dates if ((year <= 325) || (year > 4099)) alert("select a year from 326 to 4099"); else { EasterJulian(); // used for all calculations if ((year > 325) && (year <= 1582)) { alert("Easter Sunday date for " + year + ": " + GetMonth(jMonth) + " " + GetOrdinal(jDay) + " in the Julian calendar "); } else { // year is 1583 to 4099 EasterOrthodox (year, jDay, jMonth); EasterWestern (); if (year <= 1923) { // clarify that this is a Gregorian date // last known use of the Julian calendar was in 1923 alert("Easter Sunday date for " + year + ": " + GetMonth(wMonth) + " " + GetOrdinal(wDay) + ".\n\n" + "Orthodox Easter Sunday date for " + year + ": " + GetMonth(oMonth) + " " + GetOrdinal(oDay) + "\n(in the Gregorian calendar), which is the same day as\n" + GetMonth(jMonth) + " " + GetOrdinal(jDay) + " in the Julian calendar\n(for regions using that calender at the time)."); } else { alert("Easter Sunday date for " + year + ": " + GetMonth(wMonth) + " " + GetOrdinal(wDay) + ".\n\n" + "Orthodox Easter Sunday date for " + year + ": " + GetMonth(oMonth) + " " + GetOrdinal(oDay) + ",\n" + "which is the same day as\n" + GetMonth(jMonth) + " " + GetOrdinal(jDay) + " in the Julian calendar (no longer in use)."); } } } } function IntDiv (num, dvsr) // performs integer division of num/dvsr - eg IntDiv(9,4)=2 { var negate = false; var result = 0; if (dvsr == 0) return null; else { if (num * dvsr < 0 ) negate = true; if (num < 0) num = -num; if (dvsr < 0) dvsr = -dvsr; result = ((num - (num % dvsr)) / dvsr); if (negate) return -result; else return result; } } function GetMonth(m) { //switch is better, but not supported in JavaScript ver 1.0 if (m==3) return ("March"); if (m==4) return ("April"); if (m==5) return ("May"); } function GetOrdinal(d) { var rmdr = 0; rmdr = d % 10; if (((d >= 4) && (d <= 20)) || (rmdr == 0) || (rmdr > 3)) return (d + "th"); else { //switch is better, but not supported in JavaScript ver 1.0 if (rmdr==1) return (d + "st"); if (rmdr==2) return (d + "nd"); if (rmdr==3) return (d + "rd"); } } function EasterJulian() { var g = 0; var i = 0; var j = 0; var p = 0; g = year % 19; i = (19 * g + 15) % 30; j = (year + IntDiv(year, 4) + i) % 7; p = i - j + 28; jDay = p; jMonth = 4; if (p > 31) jDay = p - 31; else jMonth = 3; } function EasterWestern() { var g = 0; var c = 0; var h = 0; var i = 0; var j = 0; var p = 0; g = year % 19; c = IntDiv(year, 100); h = (c - IntDiv(c, 4) - IntDiv(8 * c + 13, 25) + 19 * g + 15) % 30; i = h - IntDiv(h, 28) * (1 - IntDiv(h, 28) * IntDiv(29, h + 1) * IntDiv(21 - g, 11)); j = (year + IntDiv(year, 4) + i + 2 - c + IntDiv(c, 4)) % 7; p = i - j + 28; wDay = p; wMonth = 4; if (p > 31) wDay = p - 31; else wMonth = 3; } function EasterOrthodox (yr, jDay, jMonth) /* Even though the Julian calendar is no longer in use Orthodox Easters are still based on this calendar NOTE! JULIAN Easter Date must be calculated first! This function converts Julian March and April Easter Sunday dates to Gregorian calendar dates */ { var extra = 0; var tmp = 0; oDay = 0; oMonth = 0; if ((yr > 1582) && (yr <= 4099)) { extra = 10; if (yr > 1600) { tmp = IntDiv(yr, 100) - 16; extra = extra + tmp - IntDiv(tmp, 4); } oDay = jDay + extra; oMonth = jMonth; if ((oMonth == 3) && (oDay > 31)) { oMonth = 4; oDay = oDay - 31; } if ((oMonth == 4) && (oDay > 30)) { oMonth = 5; oDay = oDay - 30; } } } //--> </script> |
Matlab Easter Date Calculator() function [m, d] = easter(y) % EASTER Easter date calculation for years 1583 to 4099 % % y is a 4 digit year 1583 to 4099 % % Using [m, d] = easter(y) % d returns the day of the month of Easter % m returns the month of Easter % % Using s = easter(y) % s returns the Matlab serial date number for Easter % % Easter Sunday is the Sunday following the Paschal Full Moon % (PFM) date for the year % % This algorithm is an arithmetic interpretation of the 3 step % Easter Dating Method developed by Ron Mallen 1985, as a vast % improvement on the method described in the Common Prayer Book % % Because this algorithm is a direct translation of the % official tables, it can be easily proved to be 100% correct % % This algorithm derives values by sequential inter-dependent % calculations, so ... DO NOT MODIFY THE ORDER OF CALCULATIONS! % % All variables are integer data types % % It's free! Please do not modify code or comments! % ========================================================== % Dim FirstDig, Remain19, temp % intermediate results % Dim tA, tB, tC, tD, tE % table A to E results FirstDig = fix(y / 100); % first 2 digits of year Remain19 = mod(y, 19); % remainder of year / 19 % calculate PFM date temp = fix((FirstDig - 15) / 2) + 202 - 11 * Remain19; if FirstDig > 26, temp = temp - 1; end; if FirstDig > 38, temp = temp - 1; end; % A really long line :) Use Matlab's ... 3dots to break it up. if ((FirstDig == 21) | (FirstDig == 24) | (FirstDig == 25) | ... (FirstDig == 33) | (FirstDig == 36) | (FirstDig == 37)), temp = temp - 1; end; temp = mod(temp, 30); tA = temp + 21; if temp == 29, tA = tA - 1; end; if (temp == 28) & (Remain19 > 10), tA = tA - 1; end; % find the next Sunday tB = mod((tA - 19), 7); tC = mod((40 - FirstDig), 4); if tC == 3, tC = tC + 1; end; if tC > 1, tC = tC + 1; end; temp = mod(y, 100); tD = mod((temp + fix(temp / 4)), 7); tE = mod((20 - tB - tC - tD) , 7) + 1; d = tA + tE; % return the date if d > 31, d = d - 31; m = 4; else m = 3; end; % check output arguments if nargout == 0, m = datenum(y,m,d); elseif nargout == 1, m = datenum(y,m,d); end; return; |