Skip to content

Instantly share code, notes, and snippets.

@dc0d
Created October 31, 2018 12:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dc0d/26782eae4d7a3f73960eff771475611d to your computer and use it in GitHub Desktop.
Save dc0d/26782eae4d7a3f73960eff771475611d to your computer and use it in GitHub Desktop.
Persian Calendar Functions for Elixir
defmodule PersianCalendar do
@moduledoc """
for converting persian calendar to/from gregorian calendar
"""
@breaks [
-61,
9,
38,
199,
426,
686,
756,
818,
1111,
1181,
1210,
1635,
2060,
2097,
2192,
2262,
2324,
2394,
2456,
3178
]
@doc """
returns {pyear, pmonth, pday}
"""
def gregorian_to_persian(gyear, gmonth, gday) do
jd = g2jd(gyear, gmonth, gday, 0)
jd2p(jd)
end
@doc """
returns {gyear, gmonth, gday}
"""
def persian_to_gregorian(pyear, pmonth, pday) do
jd = p2jd(pyear, pmonth, pday)
jd2g(jd, 0)
end
defp jd2p(jdn) do
{ll, _, _} = jd2g(jdn, 0)
jy = ll - 621
{leap, _, march} = edge(jy)
jdn1f = g2jd(ll, 3, march, 0)
k = trunc(jdn - jdn1f)
cond do
k >= 0 && k <= 185 ->
jm = 1 + div(k, 31)
jd = rem(k, 31) + 1
{jy, jm, jd}
k >= 0 ->
k = k - 186
jm = 7 + div(k, 30)
jd = rem(k, 30) + 1
{jy, jm, jd}
true ->
jy = jy - 1
k = k + 179
k =
if leap == 1 do
k + 1
else
k
end
jm = 7 + div(k, 30)
jd = rem(k, 30) + 1
{jy, jm, jd}
end
end
defp p2jd(jy, jm, jd) do
{_, igy, march} = edge(jy)
g2jd(igy, 3, march, 0) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1
end
defp edge(jy) do
gy = jy + 621
leapj = -14
jp = Enum.at(@breaks, 0)
if jy < jp || jy > Enum.at(@breaks, 19) do
# Exception Invalid Jalaali year number:',Jy,' (=',Gy,' Gregorian)'
end
{jump, jp, leapj} = find_jump(0, 0, jy, leapj, jp)
nn = jy - jp
leapj = leapj + div(nn, 33) * 8 + div(rem(nn, 33) + 3, 4)
leapj =
if rem(jump, 33) == 4 && jump - nn == 4 do
leapj + 1
else
leapj
end
leapg = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150
march = 20 + leapj - leapg
nn =
if jump - nn < 6 do
nn - jump + div(jump + 4, 33) * 33
else
nn
end
leap = rem(rem(nn + 1, 33) - 1, 4)
leap =
if leap == -1 do
4
else
leap
end
{leap, gy, march}
end
defp find_jump(_jump, j, jy, leapj, jp) when j <= 19 do
jm = Enum.at(@breaks, j)
jump = jm - jp
if jy < jm do
leapj = trunc(leapj)
{jump, jp, leapj}
else
leapj = leapj + div(jump, 33) * 8 + div(rem(jump, 33), 4)
jp = jm
find_jump(jump, j + 1, jy, leapj, jp)
end
end
defp find_jump(jump, _j, _jy, leapj, jp) do
leapj = trunc(leapj)
{jump, jp, leapj}
end
defp g2jd(l, m, n, j1g0) do
res =
div((l + div(m - 8, 6) + 100_100) * 1461, 4) + div(153 * rem(m + 9, 12) + 2, 5) + n -
34_840_408
case j1g0 do
0 ->
res - div(div(l + 100_100 + div(m - 8, 6), 100) * 3, 4) + 752
end
end
defp jd2g(jd, j1g0) do
j = 4 * jd + 139_361_631
j =
case j1g0 do
0 ->
j + div(div(4 * jd + 183_187_720, 146_097) * 3, 4) * 4 - 3908
end
i = div(rem(j, 1461), 4) * 5 + 308
n = div(rem(i, 153), 5) + 1
m = rem(div(i, 153), 12) + 1
l = div(j, 1461) - 100_100 + div(8 - m, 6)
{l, m, n}
end
end
defmodule PersianCalendarTest do
use ExUnit.Case
doctest PersianCalendar
@years [
{{:leap, 1354}, {1975, 3, 21}},
{{:normal, 1355}, {1976, 3, 21}},
{{:normal, 1356}, {1977, 3, 21}},
{{:normal, 1357}, {1978, 3, 21}},
{{:leap, 1358}, {1979, 3, 21}},
{{:normal, 1359}, {1980, 3, 21}},
{{:normal, 1360}, {1981, 3, 21}},
{{:normal, 1361}, {1982, 3, 21}},
{{:leap, 1362}, {1983, 3, 21}},
{{:normal, 1363}, {1984, 3, 21}},
{{:normal, 1364}, {1985, 3, 21}},
{{:normal, 1365}, {1986, 3, 21}},
{{:leap, 1366}, {1987, 3, 21}},
{{:normal, 1367}, {1988, 3, 21}},
{{:normal, 1368}, {1989, 3, 21}},
{{:normal, 1369}, {1990, 3, 21}},
{{:leap, 1370}, {1991, 3, 21}},
{{:normal, 1371}, {1992, 3, 21}},
{{:normal, 1372}, {1993, 3, 21}},
{{:normal, 1373}, {1994, 3, 21}},
{{:normal, 1374}, {1995, 3, 21}},
{{:leap, 1375}, {1996, 3, 20}},
{{:normal, 1376}, {1997, 3, 21}},
{{:normal, 1377}, {1998, 3, 21}},
{{:normal, 1378}, {1999, 3, 21}},
{{:leap, 1379}, {2000, 3, 20}},
{{:normal, 1380}, {2001, 3, 21}},
{{:normal, 1381}, {2002, 3, 21}},
{{:normal, 1382}, {2003, 3, 21}},
{{:leap, 1383}, {2004, 3, 20}},
{{:normal, 1384}, {2005, 3, 21}},
{{:normal, 1385}, {2006, 3, 21}},
{{:normal, 1386}, {2007, 3, 21}},
{{:leap, 1387}, {2008, 3, 20}},
{{:normal, 1388}, {2009, 3, 21}},
{{:normal, 1389}, {2010, 3, 21}},
{{:normal, 1390}, {2011, 3, 21}},
{{:leap, 1391}, {2012, 3, 20}},
{{:normal, 1392}, {2013, 3, 21}},
{{:normal, 1393}, {2014, 3, 21}},
{{:normal, 1394}, {2015, 3, 21}},
{{:leap, 1395}, {2016, 3, 20}},
{{:normal, 1396}, {2017, 3, 21}},
{{:normal, 1397}, {2018, 3, 21}},
{{:normal, 1398}, {2019, 3, 21}},
{{:leap, 1399}, {2020, 3, 20}},
{{:normal, 1400}, {2021, 3, 21}},
{{:normal, 1401}, {2022, 3, 21}},
{{:normal, 1402}, {2023, 3, 21}},
{{:leap, 1403}, {2024, 3, 20}},
{{:normal, 1404}, {2025, 3, 21}},
{{:normal, 1405}, {2026, 3, 21}},
{{:normal, 1406}, {2027, 3, 21}},
{{:normal, 1407}, {2028, 3, 20}},
{{:leap, 1408}, {2029, 3, 20}},
{{:normal, 1409}, {2030, 3, 21}},
{{:normal, 1410}, {2031, 3, 21}},
{{:normal, 1411}, {2032, 3, 20}},
{{:leap, 1412}, {2033, 3, 20}},
{{:normal, 1413}, {2034, 3, 21}},
{{:normal, 1414}, {2035, 3, 21}},
{{:normal, 1415}, {2036, 3, 20}},
{{:leap, 1416}, {2037, 3, 20}},
{{:normal, 1417}, {2038, 3, 21}},
{{:normal, 1418}, {2039, 3, 21}},
{{:normal, 1419}, {2040, 3, 20}}
]
test "persian to gregorian" do
g = PersianCalendar.persian_to_gregorian(1397, 8, 3)
assert g == {2018, 10, 25}
end
test "gregorian to persian" do
p = PersianCalendar.gregorian_to_persian(2018, 10, 25)
assert p == {1397, 8, 3}
end
test "persian leap years" do
[_ | current] = @years
zipped = Enum.zip(current, @years)
for {{{_year_type, py}, {gy, gm, gd}}, {{year_type0, _py0}, {_gy0, _gm0, _gd0}}} <- zipped do
pdate = PersianCalendar.gregorian_to_persian(gy, gm, gd)
assert pdate == {py, 1, 1}
gdate = PersianCalendar.persian_to_gregorian(py, 1, 1)
assert gdate == {gy, gm, gd}
{_py, _pm, pd} = PersianCalendar.gregorian_to_persian(gy, gm, gd - 1)
case year_type0 do
:normal ->
assert pd == 29
:leap ->
assert pd == 30
end
end
end
test "persian leap years recursive" do
check_leap(@years)
end
def check_leap([prev | [current | t]]) do
{{{_year_type, py}, {gy, gm, gd}}, {{year_type0, _py0}, {_gy0, _gm0, _gd0}}} = {current, prev}
pdate = PersianCalendar.gregorian_to_persian(gy, gm, gd)
assert pdate == {py, 1, 1}
gdate = PersianCalendar.persian_to_gregorian(py, 1, 1)
assert gdate == {gy, gm, gd}
{_py, _pm, pd} = PersianCalendar.gregorian_to_persian(gy, gm, gd - 1)
case year_type0 do
:normal ->
assert pd == 29
:leap ->
assert pd == 30
end
check_leap(t)
end
def check_leap([h | t]) do
{{_year_type, py}, {gy, gm, gd}} = h
pdate = PersianCalendar.gregorian_to_persian(gy, gm, gd)
assert pdate == {py, 1, 1}
gdate = PersianCalendar.persian_to_gregorian(py, 1, 1)
assert gdate == {gy, gm, gd}
check_leap(t)
end
def check_leap([]) do
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment