skyfielders / python-skyfield

Elegant astronomy for Python
MIT License
1.43k stars 213 forks source link

Issue with Julian Date conversion #992

Open izzatzubir opened 3 months ago

izzatzubir commented 3 months ago

I was running a calendar conversion for Islamic hijri calendar. I stumbled across an error in interpreting one of the dates, which is between JD = 1976791,1976792. In julian calendar, that date is supposed to be 29 February 700AD. I believe the conversion in skyfield, converted wrongly somehow, to 1st of March 700, and skipped the leap day.

Here's a snippet of the code for the above issue:

    ts = load.timescale()
    ts.julian_calendar_cutoff = GREGORIAN_START
    t = ts.utc(700, 3, 1, 12, 0)
    print(f"jd: {t}")
    print(t.utc_datetime())
    print(t.astimezone(timezone("Asia/Kuala_Lumpur")))

For julian calendar, 29 February 700 exists. but for the above code, it will return:

File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/skyfield/timelib.py", line 544, in utc_datetime_and_leap_second dt = datetime(year, month, day, hour, minute, second, micro, utc) ValueError: day is out of range for month

I did check on the following lines in skyfield/timelib.py, the day was indeed 29 on that Julian date. but I believe since it passed this date to python datetime in dt = datetime(year, month, day, hour, minute, second, micro, utc), it raises that error. Seems that python datetime doesn't support Julian calendar? Is there another way around?

        year, month, day, hour, minute, second = self._utc_tuple(0.5e-6)
        micro = (second * 1e6).astype(int)
        second, micro = divmod(micro, 1000000)
        leap_second = second // 60
        second -= leap_second  # fit within limited bounds of Python datetime
        if self.shape:
            zone = [utc] * self.shape[0]
            argsets = zip(year, month, day, hour, minute, second, micro, zone)
            dt = array([datetime(*args) for args in argsets])
        else:
            dt = datetime(year, month, day, hour, minute, second, micro, utc)
        return dt, leap_second
izzatzubir commented 3 months ago

Just a quick scroll on the current open issue and found this: https://github.com/skyfielders/python-skyfield/issues/957

Should I close this?

reza-ghazi commented 1 week ago

If you want to work with historical dates as they are, you must use something other than Python datatime. Python uses the proleptic Gregorian calendar, which does not support Julian dates before or after the Gregorian reform (October 1582). So, it's better to use Skyfield Date.

Timezones as we know them today were standardized in the late 19th century (1884 with the International Meridian Conference). Before that, local time was typically based on solar time.

For converting timezone for historical dates, it's better to use the straightforward calculation, which is:

$$\Large{\text{Timezone correction} = \text{Greenwitch (UTC)} \pm \frac{longitude}{15}}$$

But be careful. It is a simplified version. It would be best to find the "true local solar time," which is the "mean solar time" for the point, corrected with the Equation of Time (EOT).