波斯歷
外觀
這本書旨在收集關於伊朗目前使用的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