Skip to content

Commit

Permalink
Use a more robust jalaali algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
183amir committed Sep 5, 2024
1 parent ee0f845 commit d7ecc69
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 95 deletions.
103 changes: 9 additions & 94 deletions src/khayyam/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,116 +2,31 @@
__author__ = 'vahid'

from math import floor, ceil
from .jalaali import Jalaali


def get_julian_day_from_gregorian(year, month, day):
# Checking #
if year / 4.0 == round(year / 4.0):
if year / 100.0 == round(year / 100.0):
if year / 400.0 == round(year / 400.0):
# Leap year checking #
if month == 2:
assert day <= 29, 'Invalid date'
else:
# Leap year #
if month == 2:
assert day <= 29, 'Invalid date'

y = year + 0.0
m = month + 0.0
d = day + 0.0

# Determine JD
if m <= 2:
y -= 1
m += 12

a = floor(y / 100)
return floor(365.25 * (y + 4716)) + floor(
30.6001 * (m + 1)) + d + (2 - a + floor(a / 4)) - 1524.5

LEAP_REMINDERS = [1, 5, 9, 13, 17, 22, 26, 30]
return Jalaali.g2d(year, month, day)

def is_leap_year(year):
remainder = year - (year // 33) * 33
return remainder in LEAP_REMINDERS
return Jalaali.is_leap_jalaali_year(year)


def days_in_month(year, month):
if 1 <= month <= 6:
return 31
elif 7 <= month < 12:
return 30

assert month == 12, 'Month must be between 1 and 12'

### Esfand(اسفند) ###
if is_leap_year(year):
return 30 #Leap Year
else:
return 29
return Jalaali.jalaali_month_length(year, month)


def julian_day_from_jalali_date(year, month, day):
base = year - ([473, 474][year >= 0])
julian_year = 474 + (base % 2820)
return day + ([
((month - 1) * 30) + 6, (month - 1) * 31
][month <= 7]) + floor(((julian_year * 682) - 110) / 2816) + (
julian_year - 1) * 365 + floor(base / 2820) * 1029983 + (1948320.5 - 1)
return Jalaali.j2d(year, month, day)


def jalali_date_from_julian_day(julian_day):
julian_day = floor(julian_day) + 0.5
offset = julian_day - 2121445.5 # julian_day_from_jalali(475, 1, 1) replaced by its static value
cycle = floor(offset / 1029983)
remaining = offset % 1029983
if remaining == 1029982:
year_cycle = 2820
else:
a1 = floor(remaining / 366)
a2 = remaining % 366
year_cycle = floor(
((2134 * a1) + (2816 * a2) + 2815) / 1028522) + a1 + 1
y = year_cycle + (2820 * cycle) + 474
if y <= 0:
y -= 1
days_in_years = (julian_day - julian_day_from_jalali_date(y, 1, 1)) + 1
m = ceil([(days_in_years - 6) / 30,
days_in_years / 31][days_in_years <= 186])
day = (julian_day - julian_day_from_jalali_date(y, m, 1)) + 1
return y, m, day

date = Jalaali.d2j(julian_day)
return date["jy"], date["jm"], date["jd"]

def gregorian_date_from_julian_day(jd):
y = 0
m = 0

if jd <= 0.0:
raise ValueError('Invalid Date')

jdm = jd + 0.5
z = floor(jdm)
f = jdm - z

alpha = floor((z - 1867216.25) / 36524.25)
b = (z + 1 + alpha - floor(alpha / 4)) + 1524
c = floor((b - 122.1) / 365.25)
d = floor(365.25 * c)
e = floor((b - d) / 30.6001)
day = b - d - floor(30.6001 * e) + f

if e < 14:
m = e - 1
elif e == 14 or e == 15:
m = e - 13

if m > 2:
y = c - 4716
elif m == 1 or m == 2:
y = c - 4715

return y, m, day
date = Jalaali.d2g(jd)
return date["gy"], date["gm"], date["gd"]


def jalali_date_from_gregorian_date(year, month, day):
Expand Down
2 changes: 1 addition & 1 deletion src/khayyam/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__author__ = 'vahid'

MINYEAR = 1
MAXYEAR = 3178
MAXYEAR = 3177

PERSIAN_WEEKDAY_NAMES = {
0: u'شنبه',
Expand Down
257 changes: 257 additions & 0 deletions src/khayyam/jalaali.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
# The MIT License (MIT)
# Copyright (c) 2016 Alireza Aghamohammadi

from __future__ import division


def div(a, b):
"""Helper function for division
:param a: first parameter
:type a: int
:param b: second parameter
:type b: int
:return: integer division result
:rtype: int
"""
return int(a / b)


def mod(a, b):
"""Helper function for modulo
:param a: first parameter
:type a: int
:param b: second parameter
:type b: int
:return: modulo result
:rtype: int
"""
return a - div(a, b) * b


class Jalaali:
@staticmethod
def to_jalaali(gy, gm, gd):
"""Convert a Gregorian date to Jalaali
:param gy: Gregorian Year
:type gy: int
:param gm: Gregorian Month
:type gm: int
:param gd: Gregorian Day
:type gd: int
:return: the converted Jalaali date
:rtype: dict
"""
return Jalaali.d2j(Jalaali.g2d(gy, gm, gd))

@staticmethod
def to_gregorian(jy, jm, jd):
"""Convert a Jalaali date to Gregorian
:param jy: Jalaali Year
:type jy: int
:param jm: Jalaali Moth
:type jm: int
:param jd: Jalaali Day
:type jd: int
:return: the converted Gregorian date
:rtype: dict
"""

return Jalaali.d2g(Jalaali.j2d(jy, jm, jd))

@staticmethod
def is_valid_jalaali_date(jy, jm, jd):
"""Checks whether a Jalaali date is valid or not.
:param jy: Jalaali Year
:type jy: int
:param jm: Jalaali Month
:type jm: int
:param jd: Jalaali Day
:type jd: int
:return: is date valid?
:rtype: bool
"""

year_is_valid = (-61 <= jy <= 3177)
month_is_valid = (1 <= jm <= 12)
day_is_valid = (1 <= jd <= Jalaali.jalaali_month_length(jy, jm))

return year_is_valid and month_is_valid and day_is_valid

@staticmethod
def is_leap_jalaali_year(jy):
"""Checks whether this is a leap year or not.
:param jy: Jalaali Year
:type jy: int
:return: is leap year?
:rtype: bool
"""
return Jalaali.jal_cal(jy)['leap'] == 0

@staticmethod
def jalaali_month_length(jy, jm):
"""Number of days in a given month in a Jalaali year.
:param jy: Jalaali Year
:type jy: int
:param jm: Jalaali Month
:type jm: int
:return: number of days in month
:rtype: int
"""
if jm <= 6:
return 31
if jm <= 11:
return 30
if Jalaali.is_leap_jalaali_year(jy):
return 30
return 29

@staticmethod
def jal_cal(jy):
"""This function determines if the Jalaali (persian) year is
leap(366-day long) or is the common year (365-days), and
finds the day in March (Gregorian calendar) of the first
day of the Jalaali year (jy).
:param jy: Jalaali Year (-61 to 3177).
:type jy: int
:return:
leap: number of years since the last leap year(0 to 4)
gy: Gregorian year of the beginning of Jalaali year
march: the March day of farvardin the 1st (1st day of jy)
:rtype: dict
"""
breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324,
2394, 2456,
3178]
b1 = len(breaks)
gy = jy + 621
leap_j = -14
jp = breaks[0]
jump = None
if jy < jp or jy >= breaks[b1 - 1]:
raise Exception('Invalid Jalaali year ' + str(jy))
# Find the limiting years for the Jalaali year jy.

for i in range(1, b1):
jm = breaks[i]
jump = jm - jp
if jy < jm:
break
leap_j = leap_j + div(jump, 33) * 8 + div(mod(jump, 33), 4)
jp = jm
n = jy - jp

# Find the number of leap years from AD 621 to the beginning of the current
# Jalaali year in the persian calendar
leap_j = leap_j + div(n, 33) * 8 + div(mod(n, 33) + 3, 4)
if mod(jump, 33) == 4 and jump - n == 4:
leap_j += 1

# And the same in the Gregorian calendar (until the year gy)
leap_g = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150

# Determine the Gregorian date of farvardin the 1st
march = 20 + leap_j - leap_g

# Find how many years have passed since the last year
if jump - n < 6:
n = n - jump + div(jump + 4, 33) * 33
leap = mod(mod(n + 1, 33) - 1, 4)
if leap == -1:
leap = 4
return {'leap': leap, 'gy': gy, 'march': march}

@staticmethod
def j2d(jy, jm, jd):
"""Converts a date of the Jalaali calendar to the julian day number.
:param jy: Jalaali Year (1 to 3100)
:type jy: int
:param jm: Jalaali Month (1 to 12)
:type jm: int
:param jd: Jalaali Day (1 to 29/31)
:type jd: int
:return: Julian Day number
:rtype: int
"""
r = Jalaali.jal_cal(jy)
return Jalaali.g2d(r['gy'], 3, r['march']) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1

@staticmethod
def d2j(jdn):
"""Converts the Julian day number to a date in the jalaali calendar.
:param jdn: Julian day number
:type jdn: int
:return: dictionary containing jy, jm, jd
:rtype: dict
"""
gy = Jalaali.d2g(jdn)['gy'] # calculate gregorian year (gy)
jy = gy - 621
r = Jalaali.jal_cal(jy)
jdn1f = Jalaali.g2d(gy, 3, r['march'])
# find number of days that passed since 1 farvardin
k = jdn - jdn1f
if k >= 0:
if k <= 185:
# the first 6 months
jm = div(k, 31) + 1
jd = mod(k, 31) + 1
return {'jy': jy, 'jm': jm, 'jd': jd}
else:
# the remaining months.
k -= 186
else:
# previous jalaali year
jy -= 1
k += 179
k = k + 1 if r['leap'] == 1 else k
jm = 7 + div(k, 30)
jd = mod(k, 30) + 1
return {'jy': jy, 'jm': jm, 'jd': jd}

@staticmethod
def g2d(gy, gm, gd):
"""Calculates the Julian Day number from Gregorian or Julian
calendar dates. This integer number corresponds to the noon of
the date(i.e.12 hours of Universal Time).
The procedure was tested to be good since 1 March, -100100 (of both
calendars) up to few million years into the future
:param gy: Gregorian Year
:type gy: int
:param gm: Gregorian Month (1 to 12)
:type gm: int
:param gd: Gregorian Day (1 to 28/29/30/31)
:type gd: int
:return: Julian Day Number
:rtype: int
"""
d = div((gy + div(gm - 8, 6) + 100100) * 1461, 4) + div(153 * mod(gm + 9, 12) + 2, 5) + gd - 34840408
d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752
return d

@staticmethod
def d2g(jdn):
"""Calculates Gregorian and Julian calendar dates from the Julian day number
(jdn) for the period since jdn=-34839655 (i.e. the year -100100 of both calendars)
to some millions years ahead of the present.
:param jdn: Julian Day Number
:type jdn: int
:return: dictionary containing gy, gm, gd
:rtype: dict
"""
j = 4 * jdn + 139361631 + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908
i = div(mod(j, 1461), 4) * 5 + 308
gd = div(mod(i, 153), 5) + 1
gm = mod(div(i, 153), 12) + 1
gy = div(j, 1461) - 100100 + div(8 - gm, 6)
return {'gy': gy, 'gm': gm, 'gd': gd}
Loading

0 comments on commit d7ecc69

Please sign in to comment.