Module to calculate Easter dates and dates relative to Easter.
Calculate
Function to calculate Easter date or date of the event related to Easter.
arguments
Field | Purpose | Example argument |
---|---|---|
1 | Year | 2024 or 1882
|
method | Calculation method of the Easter date:
|
Eastern to calculate Easter date in the Orthodox rite.
|
day | Name of the event related to Easter date or integer number of days before (negative) or after (positive) Easter Sunday. The following days are available by name:
|
49 or interchangeably Pentecost
|
format | Describes date format in the final output, which is the same as used in #time parser function. Default format is Y-m-d . There is a special value none that prevents from final date formatting.
|
j xd
|
examples
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}}}
- 2024-03-31
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|method=Eastern}}
- 2024-05-05
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|format=[[j xg]]}}
- 31 March
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|method=Eastern|format=[[j xg]]}}
- 5 May
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|day=Ash Wednesday}}
- 2024-02-14
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|method=Eastern|day=Ash Wednesday}}
- 2024-03-20
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|format=[[j xg]] (l)|day=Ash Wednesday}}
- 14 February (Wednesday)
{{#invoke:Easter|Calculate|{{CURRENTYEAR}}|method=Eastern|format=[[j xg]] (l)|day=Ash Wednesday}}
- 20 March (Wednesday)
local m = {} local EasterData = { defaultMethod = 3, -- default method of Easter date calculation when Easter type is not given defaultFormat = "Y-m-d", -- default date output format noFormat = "none", -- prevent from final date formatting defaultOffset = 0, -- the Easter date minimumOffset = -63, -- Septuagesima maximumOffset = 69, -- Feast of the Immaculate Heart of Mary -- API apiEaster = "Calculate", -- public function name argEasterYear = 1, -- index or name of the argument with year argEasterMethod = "method", -- index or name of the argument with calculation method argEasterOffset = "day", -- index or name of the argument with offset in days relative to the calculated Easter Sunday argEasterFormat = "format", -- index or name of the argument with date output format (#time style) -- errors errorMissingYear = "Missing mandatory argument 'year'", errorInvalidYear = "Incorrect argument 'year': '%s'", errorInvalidOffset = "Incorrect argument 'day': '%s'", errorInvalidMethod = "Incorrect argument 'method': '%s'", errorYearOutOfRange = "Easter dates are available between years 326 and 4099; year: %d", errorIncorrectMethod = "Western or Orthodox Easter exists since 1583; year: %d", errorUnknownMethod = "Unknown method: %d", methods = { ["Julian"] = 1, ["Eastern"] = 2, ["Orthodox"] = 2, -- alias for Eastern ["Coptic"] = 2, -- alias for Eastern ["Ethiopian"] = 2, -- alias for Eastern ["Western"] = 3, ["Gregorian"] = 3, -- alias for Western ["Roman"] = 3, -- alias for Western }, -- the Meletian/Revised Julian Calendar from 1923 used by some Orthodox churches -- and any proposed reformed algorithms are not supported (yet): -- * astronomically observed Nicean rule at the meridian of Jerusalem (Aleppo 1997 proposal), differs from Gregorian in -- * fifteenth Sunday of the year: Sunday in 099–105 day of the year -- * Sunday after second Saturday in April (UK): Sunday in 9–15 April -- * second Sunday in April: Sunday in 8–14 April -- * Sunday after 6 April (Pepuzite sect): Sunday in 7–13 April -- * World Calendar: day 099, any day of the week in Gregorian/Julian calendar -- * Positivist Calendar: day 098, any day of the week in Gregorian/Julian calendar -- * Sunday of ISO week 14: Sunday in 099–105 day of the year -- * Sunday of ISO week 15: Sunday in 106–112 day of the year -- * Nisan 14: any day of the week -- * Nisan 15: any day of the week relativeDates = { ["Septuagesima"] = -63, ["Sexagesima"] = -56, ["Fat Thursday"] = -52, ["Quinquagesima"] = -49, -- Estomihi, Shrove Sunday ["Shrove Monday"] = -48, -- Rose Monday ["Shrove Tuesday"] = -47, -- Mardi Gras, Carnival ["Ash Wednesday"] = -46, ["Invocabit Sunday"] = -42, ["Reminiscere Sunday"] = -35, ["Oculi Sunday"] = -28, ["Laetare Sunday"] = -21, -- Mothering Sunday ["Holy Week"] = -7, ["Palm Sunday"] = -7, ["Holy Monday"] = -6, ["Holy Tuesday"] = -5, ["Holy Wednesday"] = -4, ["Maundy Thursday"] = -3, ["Good Friday"] = -2, -- Crucifixion ["Holy Saturday"] = -1, ["Easter"] = 0, -- Easter Sunday, Resurrection ["Easter Monday"] = 1, ["Divine Mercy"] = 7, ["Misericordias Domini"] = 14, ["Jubilate Sunday"] = 21, ["Cantate Sunday"] = 28, ["Vocem jucunditatis"] = 35, ["Ascension Thursday"] = 39, -- Ascension ["Pentecost"] = 49, -- Whitsun ["Whit Monday"] = 50, ["Trinity Sunday"] = 56, ["Corpus Christi"] = 60, -- Body and Blood of Christ ["Sacred Heart"] = 68, ["Immaculate Heart"] = 69, }, } local function formatEasterError(message, ...) if select('#', ... ) > 0 then message = string.format(message, ...) end return "<span class=\"error\">" .. message .. "</span>" end local function loadEasterYear(year) if not year then return false, formatEasterError(EasterData.errorMissingYear) end local result = tonumber(year) if not result or math.floor(result) ~= result then return false, formatEasterError(EasterData.errorInvalidYear, year) end return true, result end local function loadEasterMethod(method, year) local result = EasterData.defaultMethod if method then result = EasterData.methods[method] if not result then return false, formatEasterError(EasterData.errorInvalidMethod, method) end end if year < 1583 then result = 1 end return true, result end local function loadEasterOffset(day) if not day then return true, "" end local data = EasterData.relativeDates local offset = tonumber(day) if not offset then offset = data[day] end if not offset or offset ~= math.floor(offset) or offset < EasterData.minimumOffset or offset > EasterData.maximumOffset then return false, formatEasterError(EasterData.errorInvalidOffset, day) end if offset < -1 then return true, string.format(" %d days", offset) elseif offset == -1 then return true, " -1 day" elseif offset == 0 then return true, "" elseif offset == 1 then return true, " +1 day" else -- if offset > 1 then return true, string.format(" +%d days", offset) end end local function loadEasterFormat(fmt) if fmt == EasterData.noFormat then return true, nil elseif not fmt then return true, EasterData.defaultFormat else return true, fmt end end --[[ PURPOSE: This function returns Easter Sunday day and month for a specified year and method. INPUTS: Year - Any year between 326 and 4099. Method - 1 = the original calculation based on the Julian calendar 2 = the original calculation, with the Julian date converted to the equivalent Gregorian calendar 3 = the revised calculation based on the Gregorian calendar OUTPUTS: None. RETURNS: 0, error message - Error; invalid arguments month, day - month and day of the Sunday NOTES: The code is translated from DN OSP 6.4.0 sources. The roots of the code might be found in http://www.gmarts.org/index.php?go=415 ORIGINAL NOTES: 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! ]] local function calculateEasterDate(year, method) if year < 326 or year > 4099 then -- Easter dates are valid for years between 326 and 4099 -- Method 2 would have to support dates in June thereafter return 0, formatEasterError(EasterData.errorYearOutOfRange, year) end if year < 1583 and method ~= 1 then -- Western or Orthodox Easter is valid since 1583 return 0, formatEasterError(EasterData.errorIncorrectMethod, year) end -- intermediate result local firstDig = math.floor(year / 100) local remain19 = year % 19 local temp = 0 -- table A to E results local tA = 0 local tB = 0 local tC = 0 local tD = 0 local tE = 0 -- Easter Sunday day local d = 0 if method == 1 or method == 2 then -- calculate PFM date tA = ((225 - 11 * remain19) % 30) + 21 -- find the next Sunday tB = (tA - 19) % 7 tC = (40 - firstDig) % 7 temp = year % 100 tD = (temp + math.floor(temp / 4)) % 7 tE = ((20 - tB - tC - tD) % 7) + 1 d = tA + tE if method == 2 then -- convert Julian to Gregorian date -- 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 year > 1600 then temp = temp + firstDig - 16 - math.floor((firstDig - 16) / 4) end d = d + temp end elseif method == 3 then -- calculate PFM date temp = math.floor((firstDig - 15) / 2) + 202 - 11 * remain19 if firstDig > 26 then temp = temp - 1 end if firstDig > 38 then temp = temp - 1 end if firstDig == 21 or firstDig == 24 or firstDig == 25 or firstDig == 33 or firstDig == 36 or firstDig == 37 then temp = temp - 1 end temp = temp % 30 tA = temp + 21 if temp == 29 then tA = tA - 1 end if temp == 28 and remain19 > 10 then tA = tA - 1 end -- find the next Sunday tB = (tA - 19) % 7 tC = (40 - firstDig) % 4 if tC == 3 then tC = tC + 1 end if tC > 1 then tC = tC + 1 end temp = year % 100 tD = (temp + math.floor(temp / 4)) % 7 tE = ((20 - tB - tC - tD) % 7) + 1 d = tA + tE else -- Unknown method return 0, formatEasterError(EasterData.errorUnknownMethod, method) end if d > 61 then -- when the original calculation is converted to the Gregorian -- calendar, Easter Sunday can occur in May return 5, d - 61 elseif d > 31 then return 4, d - 31 else return 3, d end end local function Easter(args) local ok local year ok, year = loadEasterYear(args[EasterData.argEasterYear]) if not ok then return year end local method ok, method = loadEasterMethod(args[EasterData.argEasterMethod], year) if not ok then return method end local offset ok, offset = loadEasterOffset(args[EasterData.argEasterOffset]) if not ok then return offset end local format ok, format = loadEasterFormat(args[EasterData.argEasterFormat]) if not ok then return format end local month, day = calculateEasterDate(year, method) if month == 0 then return day end local result = string.format("%04d-%02d-%02d%s", year, month, day, offset) if format then result = mw.language.getContentLanguage():formatDate(format, result) end return result end m[EasterData.apiEaster] = function (frame) return Easter(frame.args) end return m