跳转到内容

波斯历

来自维基教科书,开放的书籍,开放的世界

这本书旨在收集关于伊朗目前使用的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

其中ab是两个被调整的参数。另一个在编程中非常有用的函数是如何推断从纪元(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!!")
  1. Kazimierz M. Borkowski,"热带年和太阳历",加拿大皇家天文学会杂志 85/3 (1991年6月) 121–130。
  2. http://www.ortelius.de/kalender/pers_en.php
  3. 主函数的代码库。 http://github.com/hadilq/persian-calendar-important-functions/blob/master/persianCalendar.py.
  4. http://www.aitotours.com/aboutiran/14/iranian-calendar/default.aspx
华夏公益教科书