module Data.Time.Calendar.Jalaali
( toJalaali
, toGregorian
, isValidJalaaliDate
, isLeapJalaaliYear
, jalaaliMonthLength
, jalCal
, j2d
, d2j
, g2d
, d2g
, JalaaliYear
, JalaaliMonth
, JalaaliDay
, GregorianYear
, GregorianMonth
, GregorianDay
, JulianDayNumber
, DayInMarch
, LeapOffset
, JalaaliDate
, GregorianDate
) where
type JalaaliYear = Int
type JalaaliMonth = Int
type JalaaliDay = Int
type GregorianYear = Int
type GregorianMonth = Int
type GregorianDay = Int
type JulianDayNumber = Int
type DayInMarch = Int
type LeapOffset = Int
type JalaaliDate = (JalaaliYear, JalaaliMonth, JalaaliDay)
type GregorianDate = (GregorianYear, GregorianMonth, GregorianDay)
toJalaali :: GregorianYear -> GregorianMonth -> GregorianDay -> JalaaliDate
toJalaali gy gm gd = d2j $ g2d gy gm gd
toGregorian :: JalaaliYear -> JalaaliMonth -> JalaaliDay -> GregorianDate
toGregorian jy jm jd = d2g $ j2d jy jm jd
isValidJalaaliDate :: JalaaliYear -> JalaaliMonth -> JalaaliDay -> Bool
isValidJalaaliDate jy jm jd
| jy < (61) = False
| jy > 3177 = False
| jm < 1 = False
| jm > 12 = False
| jd < 1 = False
| jd > jalaaliMonthLength jy jm = False
| otherwise = True
isLeapJalaaliYear :: JalaaliYear -> Bool
isLeapJalaaliYear jy = leap == 0
where (leap, _, _) = jalCal jy
jalaaliMonthLength :: JalaaliYear -> JalaaliMonth -> Int
jalaaliMonthLength jy jm
| jm <= 6 = 31
| jm <= 11 = 30
| isLeapJalaaliYear jy = 30
| otherwise = 29
breaks = [ 61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210
, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
]
firstJump = head (tail breaks) head breaks
jalCal :: JalaaliYear -> (LeapOffset, GregorianYear, DayInMarch)
jalCal jy
| jy < (61) = error ("invalid jalaali year " ++ show jy ++ ", should be >= -61")
| jy > 3177 = error ("invalid jalaali year " ++ show jy ++ ", should be <= 3177")
| otherwise = (leap, gy, dayInMarch)
where
gy = jy + 621
quot4 = (`quot` 4)
quot33 = (`quot` 33)
mod33 = (`mod` 33)
(before, _) = break (jy <) breaks
n = jy last before
jumps = zipWith () (drop 1 before) before
lastJump = last $ firstJump : jumps
leapJ' = foldl (\acc jump -> acc + quot33 jump * 8 + quot4 (mod33 jump)) (14) jumps
leapJ'' = leapJ' + quot33 n * 8 + quot4 (mod33 n + 3)
leapJ = leapJ'' + if mod33 lastJump == 4 && (lastJump n) == 4 then 1 else 0
leapG = quot4 gy quot4 (((gy `quot` 100) + 1) * 3) 150
dayInMarch = 20 + leapJ leapG
n' = n + if lastJump n < 6 then (lastJump) + quot33 (lastJump + 4) * 33 else 0
leap' = (mod33 (n' + 1) 1) `mod` 4
leap = if leap' == 1 then 4 else leap'
j2d :: JalaaliYear -> JalaaliMonth -> JalaaliDay -> JulianDayNumber
j2d jy jm jd = jdn + (jm 1) * 31 (jm `quot` 7) * (jm 7) + jd 1
where
(leap, gy, dayInMarch) = jalCal jy
jdn = g2d gy 3 dayInMarch
d2j :: JulianDayNumber -> JalaaliDate
d2j jdn = (jy, jm, jd)
where
(gy, _, _) = d2g jdn
jy' = gy 621
(leap, _, dayInMarch) = jalCal jy'
jdn1f = g2d gy 3 dayInMarch
k' = jdn jdn1f
k | k' >= 0 && k' <= 185 = k'
| k' >= 0 = k' 186
| otherwise = k' + 179 + if leap == 1 then 1 else 0
jy = jy' if k' < 0 then 1 else 0
jm = if k' >= 0 && k' <= 185
then 1 + (k `quot` 31)
else 7 + (k `quot` 30)
jd = (+) 1 (mod k (if k' >= 0 && k' <= 185 then 31 else 30))
g2d :: GregorianYear -> GregorianMonth -> GregorianDay -> JulianDayNumber
g2d gy gm gd =
d ((((gy + 100100 + ((gm 8) `quot` 6)) `quot` 100) * 3) `quot` 4) + 752
where
d = ((gy + ((gm 8) `quot` 6) + 100100) * 1461) `quot` 4 +
(153 * ((gm + 9) `mod` 12) + 2) `quot` 5 +
gd 34840408
d2g :: JulianDayNumber -> GregorianDate
d2g jdn = (gy, gm, gd)
where
j' = 4 * jdn + 139361631
j = j' + ((((4 * jdn + 183187720) `quot` 146097) * 3) `quot` 4) * 4 3908
i = ((j `mod` 1461) `quot` 4) * 5 + 308
gd = ((i `mod` 153) `quot` 5) + 1
gm = ((i `quot` 153) `mod` 12) + 1
gy = (j `quot` 1461) 100100 + ((8 gm) `quot` 6)