Getting the Date for Easter on a Given Year

There are a lot of schemes on the internet for calculating Easter, with the most notable being the Gauss and the Meeus Jones Butcher algorithms. However, I found none of them to be clearly written, and the reasons seem to have been lost to time for many of the things that have been fudged into them to make them work. Of course, AI can regurgitate those algorithms in almost any flavor—but not in a human readable way.

On YouTube, there are instructional videos that will show you how to work problems in the algorithms without explaining how they were derived, and other videos that will explore the history of how Easter Sunday came to be calculated without directly discussing any calculation. There's nothing I've found in between.

For those who like getting their heads around this sort of thing, below is a function I recently developed for Ignition that calculates Easter dates for a given year using Jython. I've given everything clear variable names and detailed code comments, so hopefully the algorithm will be easy to understand and follow. Just for fun, I've tested it all the way to year 10,000, and it works.

Happy Easter!

# Easter is defined as the first Sunday after the first full moon
# ...that occurs on or after the Ecclesiastical Vernal Equinox
def getEaster(year):
	
	# The system date months are zero indexed, so for clarity, all months are defined according to what they really are,
	# ...and a -1 correction is added, so the numbers will work as expected in system date functions
	jythonMonthCorrection = -1
	march = 3 + jythonMonthCorrection
	
	# The Ecclesiastical Vernal Equinox that is needed for calculating Easter is always March 21st of the given year
	vernalEquinox = system.date.getDate(year, march, 21)
	
	# The moon's cycles repeat every 19 years
	# This cycle is called the Metonic Cycle,
	# ...and is named after Meton of Athens, the Athenian astronomer who discovered in in 432 BC
	metonicDuration = 19 # years
	
	# Dividing the current year by the total Metonic duration and taking the remainder [Modulus 19]
	# ...determines which metonic cycle to use in the calculation
	currentMetonicCycle = year % metonicDuration # One of the 19 metonic cycles indexed 0 to 18 
	
	# Divide by 100 to get the number of completed centuries since year 0
	# Note: integer division rounds down, so 2025 / 100 = 20
	centuries = year / 100
	
	 # The moon has 12 phases that describe how the moon appears when being observed from the earth.
	 # The synodicMonth is the amount of time it takes to observe all 12 phases,
	 # ...which is slightly longer than the amount if time it takes for the moon to orbit the earth
	 # ...due to the earth's movement in its own orbit around the sun.
	 # The synodic month is not quite 30 days long [29.5 days average],
	 # ...so 29.5 x 12 months in a year gives us a lunar year that is 354 days long
	synodicLunarMonth = 30 # Days
	synodicLunarYear = 354 # Days
	standardSolarYear = 365 # Days
	
	# The difference between the standard solar year and the lunar year is 11 days
	lunarShortagePerYear = standardSolarYear - synodicLunarYear # Days
	
	# Each year of the metonic cycle results in an additional  11 day shift in the phase of the moon,
	# ...so the total shift can be calculated by multiplying the current metonic cycle by the shortage per year
	totalLunarShortage = lunarShortagePerYear * currentMetonicCycle
	
	# Traditionally, the 15th day of the lunar month is about where the full moon is expected to occur
	dayOfFullMoon = 15
	
	# solar leapYears occur every 4 years
	leapYearInterval = 4 # Years
	
	# According to the leap year rules of the Gregorian calendar,
	# ...if a century is divisible by 100, but not divisible by 400, the leap year is skipped
	# Therefore, every 4th turn of the century, a leap year occurs
	leapCenturies = centuries / leapYearInterval # Integer division [21 / 4 = 5]
	
	
	# Lunar Leap Years:
	# ...Every 19 years, the lunar [Metonic] cycle repeats, meaning it syncs back up with where it was in the solar cycle 19 years ago,
	# ...but each 19 year cycle is just barely longer than 19 solar years, 
	# ...so just like the 365 day solar year has to be incremented with occasional leap years,
	# ...the lunar years have to be incremented occasionally to preserve the moons Metonic cycle
	# ...This is done at a rate of 8 leap years every 2500 years,
	# ...Distributed at three hundred years for the first 7 and four hundred years for the 8th
	
	# The Gregorian calendar was implemented to fix inaccuracies that had resulted from the impreciseness of the older Julian calendar.
	# Therefore there is an initial lunar correction of 6 that brings the solar and luner calendars back into sync with their astronomical positions
	# ...with the 1st complete 2500 lunar leap year cycle starting in 1800
	initialLunarCorrection = 6
	
	# Since the 2500 year cycle starts in 1800, the current century needs to be offset by 18 to sync the calculation
	initialLunarCenturies = 18
	currentLeapCenturies = centuries - initialLunarCenturies
	
	# Calculate how many completed 8 increment cycles have occurred.
	# This will be 0 for a couple of millennia, but it's included in case anybody is curious about far future Easter dates
	lunarLeapCycles = currentLeapCenturies / 25 # number of completed cycles
	leapsFromLunarCycles = 8 * lunarLeapCycles # 8 lunar leap years per cycle
	
	# Get the remainder of the leap centuries, and see how many 3 century leap increments are there
	# ...If more than 2400 years have passed since the last completed cycle, this will evaluate to 8,
	# ...which is not possible since the 8th increment requires an extra 100 years to complete,
	# ...so use min to constrain the max additional leaps to 7, so the 8th lunar leap year won't count until the 2500 year cycle has completed
	extraLunarLeaps = min(7, (currentLeapCenturies % 25) / 3)
	
	# Tabulate the leap offsets to know how much to offset the moon phase 
	# Then, combine it with the other offsets and take the modulus with the number of days in a synodicLunarMonth to calculate the phase shift (0 - 29)
	totalLunarLeaps = initialLunarCorrection + leapsFromLunarCycles + extraLunarLeaps
	lunarPhaseShift = (dayOfFullMoon + centuries - leapCenturies - totalLunarLeaps - totalLunarShortage) % synodicLunarMonth

	# After the switch to the Gregorian calendar, the Catholic church added special rules for calculating the date of the Ecclesiastical Full Moon,
	# ...to maintain some consistency with the Easter dates that were permissible under the older Julian calendar.
	# Rule one: the 1st full moon can never be more than 28 days after the vernal Equinox.
	# In centuries where a 29 day phase shift is adjusted and a 28 day phase shift also occurs,
	# ...the 28 day phase shift will also be decremented by 1.
	# This scenario occurs when a lunar phase shift of 28 happenes in metonic cycles of 11 or greater
	if (lunarPhaseShift == 28 and currentMetonicCycle > 10 or lunarPhaseShift == 29):
		lunarPhaseShift -= 1
	
	# Add the calculated phase shift to the the vernal equinox to get the precise date of the ecclesiastical full moon
	ecclesiasticalFullMoon = system.date.addDays(vernalEquinox, lunarPhaseShift)
	
	# Get the integer code for the full moon's day of the week
	fullMoonDayOfWeek = system.date.getDayOfWeek(ecclesiasticalFullMoon)
	
	# Subtract the calculated day of the week from 8 to see how many days are left until the following Sunday (Easter)
	daysTillSunday = 8 - fullMoonDayOfWeek
	
	# Return Easter: the first Sunday on or after the ecclesiastical full moon
	return system.date.addDays(ecclesiasticalFullMoon, daysTillSunday)

...also, just in case there's somebody out there that doesn't know, there is still an active Easter egg hunt in progress here:
Bananas: Easter Egg Challenge! Nobody has found it yet!

7 Likes