Packages

Simple library to get the date of Easter for a given year.

Current section

Files

Jump to
easter lib easter.ex
Raw

lib/easter.ex

defmodule Easter do
@moduledoc """
Functions for getting the date of Easter.
"""
@doc """
Gets the date of Easter on a given year.
## Examples
iex> Easter.get_date(2023)
{:ok, ~D[2023-04-09]}
iex> Easter.get_date(10_000)
{:error, :invalid_date}
"""
@spec get_date(integer) :: {:ok, Date.t()} | {:error, atom}
def get_date(year) do
# Derived from https://www.algorithm-archive.org/contents/computus/computus.html
# Year's position on the 19 year metonic cycle
a = rem(year, 19)
# Century index
k = div(year, 100)
# Shift of the metonic cycle, add a day offset every 300 years
p = div(13 + 8 * k, 25)
# Correction for non-observed leap days
q = div(k, 4)
# Correction to starting point of calculation each century
m = rem(15 - p + k - q, 30)
# Number of days from March 21st until the full moon
d = rem(19 * a + m, 30)
# Finding the next Sunday
# Century-based offset in weekly calculation
n = rem(4 + k - q, 7)
# Correction for leap days
b = rem(year, 4)
c = rem(year, 7)
# Days from d to next Sunday
e =
with e <- rem(2 * b + 4 * c + 6 * d + n, 7) do
# Historical corrections for April 26 and 25
if (d == 29 and e == 6) or (d == 28 and e == 6 and a > 10) do
-1
else
e
end
end
# Determination of the correct month for Easter
if 22 + d + e > 31 do
Date.new(year, 4, d + e - 9)
else
Date.new(year, 3, 22 + d + e)
end
end
@doc """
Gets the date of Easter on a given year.
## Examples
iex> Easter.get_date!(2023)
~D[2023-04-09]
iex> Easter.get_date!(10_000)
** (ArgumentError) cannot get Easter date, reason: invalid_date
"""
@spec get_date!(integer) :: Date.t()
def get_date!(year) do
case get_date(year) do
{:ok, date} ->
date
{:error, reason} ->
raise(ArgumentError, message: "cannot get Easter date, reason: #{reason}")
end
end
end