波斯历
外观
这本书旨在收集关于伊朗目前使用的Jalali/波斯历的数据。我希望其他人帮助我完成历史信息,但我想分享的是一些关于如何计算这个日历的闰年函数。
这一年是从春分w:equinox开始计算的,约为365.24219天(实际值为365.2422464天)[1]。为了评估一年时间的长短,Khayyam 制定了一个2820年的循环规则来找到闰年。闰年有366天,其他年份有365天。这里我们解释这个规则并编写其算法。2820年的循环被分成21个128年的子循环,每个2820年循环的末尾是一个132年的子循环。128年的子循环由一个29年的子子循环组成,后面是3个33年的子子循环。最后,132年的子循环由一个29年的子子循环组成,后面是两个33年的子子循环,以及一个最后的37年子子循环。这些年在每个循环中编号。用n表示循环内一年的编号,如果n > 1并且n mod 4 = 1,则这一年是闰年。[2] 这个算法用python3编程语言表示[3]
# This is the implementation of Khayyam rules. year is an integer parameter.
def isLeapYearReal(year):
# The 2820-year cycle includes 21 128-year subcycles, and a 132-year subcycle
cycle2820 = ((21,128),(1,132))
# The 128-year subcycle includes a 29-year sub-subcycles, and three 33-year sub-subcycle
cycle128 = ((1,29),(3,33))
cycle132 = ((1,29),(2,33),(1,37))
cycle29 = ((1,5),(6,4))
cycle33 = ((1,5),(7,4))
cycle37 = ((1,5),(8,4))
if year > 0:
realYear = (year + 37) % 2820 # realYear includes zero
elif year < 0:
# 38 years separating the beginning of the 2820-year cycle from Hejira
realYear = (year + 38) % 2820
else:
return None # There is no zero year!!
wi = whereIs(cycle2820, realYear) # find what subcycle of 2820-year cycle includes the realYear
if(wi[0] == 128): # if realYear is inside of 128-year subcycle
wi1 = whereIs(cycle128, wi[1]) # find what subcycle of 128-cycle includes the wi[1]
if(wi1[0] == 29): # if realYear is inside of 29-year sub-subcycle
wi2 = whereIs(cycle29, wi1[1])
if wi2[1] == wi2[0] - 1: # if wi2[1] mod wi2[0] becomes wi2[0] - 1 (wi2[0] is 4 or 5)
return True
elif(wi1[0] == 33): # if realYear is inside of 33-year sub-subcycle
wi2 = whereIs(cycle33, wi1[1])
if wi2[1] == wi2[0] - 1:
return True
elif(wi[0] == 132): # if realYear is inside of 132-year subcycle
wi1 = whereIs(cycle132, wi[1])
if(wi1[0] == 29):
wi2 = whereIs(cycle29, wi1[1])
if wi2[1] == wi2[0] - 1:
return True
elif(wi1[0] == 33):
wi2 = whereIs(cycle33, wi1[1])
if wi2[1] == wi2[0] - 1:
return True
elif(wi1[0] == 37):
wi2 = whereIs(cycle37, wi1[1])
if wi2[1] == wi2[0] - 1:
return True
return False
def whereIs(cycle, year): # a function to find what subcycle includes the year
y = year
# for example p is (21,128), which means this cycle have 21 of 128-year subcycles
for p in cycle:
if y < p[0]*p[1]: # if y is inside one of subcycles
# p[1] is the length of subcycle
# y % p[1] is y mod p[1], which gives the position of y inside one of p[1]s
return (p[1], y % p[1])
y -= p[0]*p[1] # if y is not inside of p[1] subcycle prepare for next subcycle
其中38代表着2820年循环的开始到伊斯兰历(穆罕默德从麦加到麦地那的逃亡之年,对应于公元621-622年)之间的年份,Jalali科学家小组将此作为伊朗历的第一年。[4] 如你所见,这个算法太长太慢。为了改进计算,以下是上述函数的推断
# a function to extrapolate leap years just like isLeapYearReal(year)
def isLeapYear(year):
a = 0.025 # a and b are two parameters. which are tuned
b = 266
if year > 0:
# 38 days is the difference of epoch to 2820-year cycle
leapDays0 = ((year + 38) % 2820)*0.24219 + a # 0.24219 ~ extra days of one year
leapDays1 = ((year + 39) % 2820)*0.24219 + a
elif year < 0:
leapDays0 = ((year + 39) % 2820)*0.24219 + a
leapDays1 = ((year + 40) % 2820)*0.24219 + a
else:
# In case of using isLeapYear(year - 1) as last year. Look FixedDate function
return True
frac0 = int((leapDays0 - int(leapDays0))*1000) # the fractions of two consecutive days
frac1 = int((leapDays1 - int(leapDays1))*1000)
# 242 fraction, which is the extra days of one year, can happened twice inside
# a 266 interval so we have to check two consecutive days
if frac0 <= b and frac1 > b : # this year is a leap year if the next year wouldn't be a leap year
return True
else:
return False
其中a和b是两个被调整的参数。另一个在编程中非常有用的函数是如何推断从纪元(FARVARDIN 1, 1)到每一年第一天(FARVARDIN 1, 年)所经过的天数。
# find the interval in days between FARVARDIN 1 of this year and the first one
def FixedDate(year):
if year > 0:
realYear = year - 1 # realYear includes zero
elif year < 0:
realYear = year
else:
return None # There is no zero year!!
cycle = (realYear + 38) % 2820 # cycle is (realYear + 38) mod 2820
base = int( (realYear + 38) / 2820)
if realYear + 38 < 0: base -= 1
days = 1029983 * base # 1029983 is the total days of one 2820-year cycle
days += int((cycle - 38) * 365.24219) + 1
if cycle - 38 < 0: days -= 1
extra = cycle * 0.24219 # 0.24219 ~ extra days of one year
frac = int((extra - int(extra))*1000) # frac is the fraction of extra days
if isLeapYear(year - 1) and frac <= 202: # 202 is a tuned parameter
days += 1
return days
只要Kayyam的规则是正确的,这些函数就没有限制。为了使人信服,任何人都可以使用这个测试函数。
def test():
days = 1 # The first day of calendar, FARVARDIN 1, 1
for year in range(1,2850):
# check if the estimated function is the same as the real one
if isLeapYear(year) != isLeapYearReal(year):
print("wrong!!")
if FixedDate(year) != days:
print("wrong!!")
if isLeapYear(year): # add 366 days for leap years
days += 366
else:
days += 365
days = 1 # The first day of calendar, FARVARDIN 1, 1
for year in range(-1,-2850,-1): # do the same for negative years
if isLeapYear(year) != isLeapYearReal(year):
print("wrong!!")
if isLeapYear(year):
days -= 366
else:
days -= 365
if FixedDate(year) != days:
print("wrong!!")
- ↑ Kazimierz M. Borkowski,"热带年和太阳历",加拿大皇家天文学会杂志 85/3 (1991年6月) 121–130。
- ↑ http://www.ortelius.de/kalender/pers_en.php
- ↑ 主函数的代码库。 http://github.com/hadilq/persian-calendar-important-functions/blob/master/persianCalendar.py.
- ↑ http://www.aitotours.com/aboutiran/14/iranian-calendar/default.aspx