NavList:
A Community Devoted to the Preservation and Practice of Celestial Navigation and Other Methods of Traditional Wayfinding
Re: Julian Day Number algorithms
From: Paul Hirose
Date: 2024 Mar 20, 13:12 -0700
From: Paul Hirose
Date: 2024 Mar 20, 13:12 -0700
There seems to be a rule that calendar algorithms are not allowed to use any obvious methods such as table lookup, branching, or iteration. Tradition requires clever hacks with minimum lines of code. Here I violate tradition with a Julian day number algorithm designed for easy understanding. It's valid for any BC or AD date in the Gregorian or Julian calendar. The time span is limited only by integer overflow. I begin with conversion from calendar date to GI (Gregorian integer), where GI 0 is the last day of year 0 (1 BC) in the Gregorian proleptic calendar. Days are numbered in sequence in both directions from that date: GI -1 is year 0 Dec 30 and GI +1 is year 1 Jan 1. In this algorithm the year number (y) uses the astronomical convention where 1 BC = 0, 2 BC = -1. etc. All divisions use integer math. For example, 2024 / 100 is exactly 20. 1. If an AD year, decrement y. If BC, reverse the sign of y. 2. Compute GI at the "0th" day of this year (last day of previous year). If the year is BC the formula gives a wrong result, but that's corrected in the next step. Remember, in integer division the remainder is discarded. GI = y * 365 + y / 4 - y / 100 + y / 400 3. If BC, add 366 and negate. For example, in year 0 (1 BC), step 2 is zero. Adding 366 and reversing the sign gives -366, which is the correct GI at the 0th day of year 0 (which has 366 days according to the leap year rules). In similar fashion we can validate the result in year -1 (2 BC): step 2 is 365, corrected to -731 in step 3. That's the GI at the 0th day of year -1. 4. At this point we have the GI at the 0th year of the year. Now add the days in the whole months. E.g., for a March date add 31 and 28 (or 29 if a leap year). 5. At this point we have the GI at the 0th day of this month. Add day of month. For example, calculate GI at 2024 March 19. Per step 1, adjust y to 2023. Step 2 is GI = 2023 * 365 + 2023 / 4 - 2023 / 100 + 2023 / 400 = 738 885. Step 3 does not apply since the date is AD. Step 4 adds 31 and 29. In step 5 add 19 to get 738 964 for the GI. For dates in the Julian calendar a Julian integer can be calculated with a similar method. In step 2 use the formula JI = y * 365 + y / 4. In step 4 use Julian rules to determine the number of days in February. For example, calculate JI at 2024 March 6 (equivalent to the Gregorian calendar date just calculated). Step 2 is JI = 2023 * 365 + 2023 / 4 = 738 900. Add 31 and 29 for January and February. Add 6 for day of month. JI = 738 966, or 2 more than GI on the same day. Since GI and JI proceed in parallel into the past and future, JI is always GI + 2. GI and JI also have constant differences from JDN (Julian day number). To determine the differences, calculate JI at the JDN zero point: -4712 Jan 1, Julian calendar. Per step 1, change -4712 to +4712. Step 2 is JI = 4712 * 365 + 4712 / 4 = 1 721 058. Per step 3, add 366 and negate. Result is -1 721 424. Step 4 is not applicable since there are no whole months before a January date. In step 5 add 1 for day of month to get JI -1 721 423. Thus, JDN 0 = JI -1 721 423. And since GI is always 2 less than JI, we can also say that JDN 0 = GI -1 721 425. Or to put it another way, JDN = GI + 1 721 425. To verify, convert the previously calculated GI (738 964 at 2024 March 19) to JDN. Result is JDN 2 460 389. That agrees with the table in Almanac section K: https://archive.org/details/binder1_202003/page/n539/mode/1up?view=theater In a computer implementation I use a 2-dimensional array in step 4: {{31, 28, 31...}, {31, 29, 31...}} and iterate to add the days in the whole months. (With the same array you can verify day of month does not exceed the legal range.) To select the correct row I wrote function IsLeapYear(y, julian). The "julian" parameter is true for Julian calendar dates. The function returns 0 if y is a normal year or 1 if leap. That value indexes into the correct row of the array. (The year passed to the function must be the actual value, before it's modified in step 1.) I'll explain the reverse conversion (JDN to calendar date) in a followup. -- Paul Hirose sofajpl.com