catull / calendariale

Calendrical calculations library for TypeScript
BSD 3-Clause "New" or "Revised" License
17 stars 1 forks source link

Persian Calendar should use timezone longitude instead actual Tehran's location #633

Closed ebraminio closed 3 years ago

ebraminio commented 3 years ago

Calendariale probably is more accurate however, Update: see the next post

According to officially published Calendar Center here is the official leap years of Persian Calendar in use in Iran as the main calendar,

            1210, 1214, 1218, 1222, 1226, 1230, 1234, 1238, 1243, 1247, 1251, 1255, 1259, 1263,
            1267, 1271, 1276, 1280, 1284, 1288, 1292, 1296, 1300, 1304, 1309, 1313, 1317, 1321,
            1325, 1329, 1333, 1337, 1342, 1346, 1350, 1354, 1358, 1362, 1366, 1370, 1375, 1379,
            1383, 1387, 1391, 1395, 1399, 1403, 1408, 1412, 1416, 1420, 1424, 1428, 1432, 1436,
            1441, 1445, 1449, 1453, 1457, 1461, 1465, 1469, 1474, 1478, 1482, 1486, 1490, 1494,
            1498

which is also what .NET also does,

var persianCalendar = new System.Globalization.PersianCalendar();

// https://calendar.ut.ac.ir/Fa/News/Data/Doc/KabiseShamsi1206-1498-new.pdf
var officiaLeapYears = new int[] {
    1210, 1214, 1218, 1222, 1226, 1230, 1234, 1238, 1243, 1247, 1251, 1255, 1259, 1263,
    1267, 1271, 1276, 1280, 1284, 1288, 1292, 1296, 1300, 1304, 1309, 1313, 1317, 1321,
    1325, 1329, 1333, 1337, 1342, 1346, 1350, 1354, 1358, 1362, 1366, 1370, 1375, 1379,
    1383, 1387, 1391, 1395, 1399, 1403, 1408, 1412, 1416, 1420, 1424, 1428, 1432, 1436,
    1441, 1445, 1449, 1453, 1457, 1461, 1465, 1469, 1474, 1478, 1482, 1486, 1490, 1494,
    1498 };
for (int i = 1206; i <= 1498; ++i)
{
    var result = persianCalendar.IsLeapYear(i) == officiaLeapYears.Contains(i);
    Console.WriteLine(result);
    if (!result) throw new Exception();
}

But calendariale fails on 1469 so the following (Persian Calendar is now in 1400 so this is 69 years from now, no worries 😄),

    const officialLeapYears = [
      1210, 1214, 1218, 1222, 1226, 1230, 1234, 1238, 1243, 1247, 1251, 1255, 1259, 1263,
      1267, 1271, 1276, 1280, 1284, 1288, 1292, 1296, 1300, 1304, 1309, 1313, 1317, 1321,
      1325, 1329, 1333, 1337, 1342, 1346, 1350, 1354, 1358, 1362, 1366, 1370, 1375, 1379,
      1383, 1387, 1391, 1395, 1399, 1403, 1408, 1412, 1416, 1420, 1424, 1428, 1432, 1436,
      1441, 1445, 1449, 1453, 1457, 1461, 1465, 1469, 1474, 1478, 1482, 1486, 1490, 1494,
      1498
    ];

    for (let year = 1206; year <= 1498; ++year) {
      console.log(year);
      expect(cal.isLeapYear(year)).toBe(officialLeapYears.includes(year));
    }

Which expects 1469 to be 1470 and can be resolved with this change on calendariale

-  LOCATION_TEHRAN: new Location(35.696111, 51.423056, 1100, 7 / 48),
+  LOCATION_TEHRAN: new Location(35.696111, 52.5, 1100, 7 / 48),

To make it match with dotnet's implementation https://github.com/microsoft/referencesource/blob/5697c29/mscorlib/system/globalization/CalendricalCalculationsHelper.cs#L291 which uses 52.5

Hopefully this would be a useful information to you, please note that Persian arithmetic is way off, if performance is a concern with the implementation, something like this can be done (I also have the non-lookup table one here based on the dotnet implementation, I could use the calendariale one but this minor issue which I wasn't aware how easily can be resolved made me to go to port the .NET one)

Thanks!

ebraminio commented 3 years ago

Per https://en.wikipedia.org/wiki/Iran_Standard_Time

Iran Standard Time (IRST) or Iran Time (IT) is the time zone used in Iran. Iran uses a UTC offset UTC+03:30. IRST is defined by the 52.5 degrees east meridian, the same meridian which defines the Iranian calendar and is the official meridian of Iran.

So the use of 52.5 makes sense actually.

catull commented 3 years ago

Dear @ebraminio

Both the official leap year for AP 1469 (Gregorian year - GY 2090) and the one calculated in this project for AP 1470 (GY 2091) are projected.

This project bases its calculation on the sources of the book Calendrical Calculations, which comes with source code in Common Lisp.

From here on, the discussion is about the Persian Astronomical calendar, not the Persian Arithmetic calendar (which has a cycle of 2880 years).

The calculations take into account the exact geo-location of the observatory in Tehran, as well as its altitude, which is around 1100 metres above the sea level.

The location in the source code and the definition of "Iran Standard Time" zone are two different things. IRST is used to specify the time difference relative to Greenwich (London), it is a time zone which is valid for all of Iran.

The source code mentioned defines Tehran's location as

(defconstant tehran
  ;; TYPE location
  ;; Location of Tehran, Iran.
  (location (deg 35.68L0) (deg 51.42L0)
                  (mt 1100) (hr (+ 3 1/2))))

It does not specify it as (deg 52.5L0).

So, in order to identify if a given Persian year is leap, the time-span between Favardin 1 of the following year and Favardin 1 of the year in question is calculated; if it is 365 [days], the year is not leap. It is only a leap year for 366 [days].

In order to determine if AP 1469 or AP 1470 are leap years, we run this code in SBCL, an open source Common Lisp implementation:

;; is AP 1469 a leap year ?
( - (fixed-from-persian (persian-date (1470 1 1))
     (fixed-from-persian (persian-date (1469 1 1))
)

;; is AP 1470 a leap year ?
( - (fixed-from-persian (persian-date (1471 1 1))
     (fixed-from-persian (persian-date (1470 1 1))
)

fixed-from-persian means, convert the persian astronomical date to Julian Day Number (JDN).

The evaluation of both expressions gives us 365 and 366 respectively. This means that AP 1469 is not a leap year, but 1470 is. That is, if you go with the algorithms published in the book introduced above.

I am willing to extend the test suite with more cases.

However, I am not changing the code as suggested.

Given the fact that we do not exactly know how the official leap years were calculated, and the fact that there is only one discrepancy between them and the leap years in this project, I am sticking with the book's algorithms.

I am aware that this discrepancy may disqualify its usage for certain parties.

ebraminio commented 3 years ago

Thank you, good pointers and no problem, I'm actually here more for the sake information exchange reason and wasn't aware altitude have any interference to the calculation, thanks!

catull commented 3 years ago

I really appreciate your contributions.

I am very interested in the .NET implementation, thank you for pointing it out!

ebraminio commented 3 years ago

A better link for it is this, https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendricalCalculationsHelper.cs which is solely used in its implementation which is tested here https://github.com/dotnet/runtime/blob/main/src/libraries/System.Globalization.Calendars/tests/System/Globalization/PersianCalendarTests.cs I think "CalendricalCalculationsHelper" in its name somehow indicates that also used the same source? Interesting to know who they found they should use 52.5 instead which actually matches with what Iran's Calendar Center publishes also.

ebraminio commented 3 years ago

The Calendar Center also publishes Spring equinoxes but they have some differences less than a minute with Spring equinox algorithms I've found in the net, as you may know Spring equinox moment is highly related to Persian calendar leap years those but somehow I couldn't find yet a way to extract that from the code. If calendariale could provide them somehow I can check whether they match official ones better or not, here is its test https://github.com/persian-calendar/equinox/blob/main/src/test/java/io/github/persiancalendar/MainTests.java#L14 which has a difference less than one minute.

ebraminio commented 3 years ago

Interesting this topic is mentioned on https://www.fourmilab.ch/documents/calendar/ and IRST is chosen there apparently.

There is some controversy about the reference meridian at which the equinox is determined in this calendar. Various sources cite Tehran, Esfahan, and the central meridian of Iran Standard Time as that where the equinox is determined; in this implementation, the Iran Standard Time longitude is used, as it appears that this is the criterion used in Iran today. As this calendar is proleptic for all years prior to 1925 c.e., historical considerations regarding the capitals of Persia and Iran do not seem to apply.

catull commented 3 years ago

It would be great to have a list of leap years prior to 1925 C.E.

6 years ago I replicated the site you mentioned Fourmilab, enhanced it, see CalendarConverter.

I contacted the author of the site to incorporate the enhancements.

ebraminio commented 3 years ago

It would be great to have a list of leap years prior to 1925 C.E.

The dotnet has something about it https://github.com/dotnet/runtime/blob/main/src/libraries/System.Globalization.Calendars/tests/System/Globalization/PersianCalendarTests.cs#L246

But even better this page (nojum, the site's domain, means astronomy) has something about it, specially look at this section of the page, "اصول کبیسه‌ها در تقویم هجری شمسی" which interestingly mentions IRST's 52.5 also,

image

The table says something about leaps years between -940 to 1498 of Persian Calendar

6 years ago I replicated the site you mentioned Fourmilab, enhanced it, see CalendarConverter.

Found its link from your GitHub page but didn't know the history, thanks

catull commented 3 years ago

Just checked that list of 2270 leap years.

The algorithm gets 19 "wrong", which is less than 1 % (0.84 %).

Using 52.5 for Tehran's location indeed misses none of the leap years.