espinielli / pycalcal

Calendrical Calculations in Python
Other
30 stars 10 forks source link

Maximum Date Supported #7

Open okeuday opened 1 year ago

okeuday commented 1 year ago

I haven't yet seen the book define the maximum date the lisp routines support. However, based on the function call result in the example below, the maximum Gregorian year should be 2938 or less (at least for the Hindu functions). Once the value is clear, it would be nice if the maximum date (or year) supported was provided by the source code.

>>> import pycalcal as pcc
>>> pcc.hindu_lunar_holiday(9, 1, 2939)
[]

The Gregorian years in ephemeris_correction for the various bounds are much lower than 2938 (i.e., 2019 is the highest Gregorian year, in the first if statement), so the problem above is likely due to the limitations of that function. However, there may be other limitations too.

okeuday commented 1 year ago

Ok, the problem above with Gregorian year 2939 is due to lunar month 9 only existing during that Gregorian year with lunar days 20-30 as shown below:

>>> pcc.hindu_lunar_from_fixed(date(2938, 12, 13).toordinal())
[2995, 9, False, 1, False]
>>> pcc.hindu_lunar_from_fixed(date(2940, 1, 1).toordinal())
[2996, 9, False, 1, False]
>>> pcc.hindu_lunar_from_fixed(date(2939, 1, 1).toordinal())
[2995, 9, False, 20, False]
>>> pcc.hindu_lunar_from_fixed(date(2939, 1, 11).toordinal())
[2995, 9, False, 30, False]

I created a general test (provided below) to ensure a conversion to a Hindu lunar date can be reversed with the same source code. So far, it looks like everything works fine up to Gregorian year 3000 at least:

#!/usr/bin/env python3

import pycalcal as pcc
from datetime import date, timedelta

if __name__ == '__main__':
    done = False
    year = 2019
    d = date(year, 1, 1)
    while not done:
        if year != d.year:
            year = d.year
            print(year)
        fixed_solar = d.toordinal()
        lunar = pcc.hindu_lunar_from_fixed(fixed_solar)
        lunar_month = lunar[1]
        lunar_day = lunar[3]
        lunar_leap = lunar[2] or lunar[4]
        fixed_lunar = pcc.hindu_lunar_holiday(lunar_month, lunar_day, d.year)
        valid = fixed_lunar != []
        if valid:
            if (fixed_solar - fixed_lunar[0]) not in frozenset((0, 1)):
                valid = len(fixed_lunar) == 2 and (
                    (fixed_solar - fixed_lunar[1]) in frozenset((0, 1))
                )
                if not valid and lunar_leap:
                    # ignore lunar leap not matching
                    valid = True
        elif lunar_leap:
            # ignore lunar leap not matching
            valid = True
        if valid:
            d += timedelta(days=1)
        else:
            print('failed at', date.fromordinal(fixed_solar),
                  fixed_solar, fixed_lunar, lunar)
            done = True
okeuday commented 1 year ago

It looks like CALENDRICA 4.0 changes are required to support the Gregorian year range -1999 to +3000, at least for the ephemeris_correction function results. The additional modifications to the ephemeris_correction function are from the NASA Eclipse website.

So, while nothing breaks when reversing the Hindu lunar date conversion in the script above, the accumulated error from ephemeris_correction (and possibly other functions?) should make the maximum Gregorian year supported by CALENDRICA 3.0 less than 3000. The error should be responsible for the Hindu lunar month 9 (Agrahāyaṇa or Mārgaśīrṣa) shifting into January in the output above (month 9 should be during November/December).

okeuday commented 1 year ago

To avoid the Hindu lunar month 9 shifting into January, it is best to only use Gregorian years less than 2310 (with CALENDRICA 3.0) based on the script:

#!/usr/bin/env python3

import pycalcal as pcc
from datetime import date

if __name__ == '__main__':
    done = False
    year = 1900
    while not done:
        year += 1
        lunar = pcc.hindu_lunar_from_fixed(date(year, 1, 1).toordinal())
        lunar_month = lunar[1]
        lunar_day = lunar[3]
        if lunar_month == 9:
            days = [
                date.fromordinal(fixed)
                for fixed in pcc.hindu_lunar_holiday(lunar_month, lunar_day, year)
            ]
            december = False
            for day in days:
                if day.month == 12:
                    december = True
                    break
            if not december:
                print(year, lunar, days)
                done = True
    print('years valid before', year)