[ Originally pubished in Datafile, Vol 10 No 8, December 1991, page 16. ] DAY NUMBER ROUTINES USING INTEGER ARITHMETIC Craig A. Finseth (member number ???) I have seen numerous articles on routines for converting dates to day count (typically Julian) and back again. These articles have covered various HP calculators and all used the usual HP arithmetic. However, I recently had to solve this problem in a program for the 95LX in a slightly different way: using 32-bit integer arithmetic. The results are interesting. The problem has six parts which cover conversion in each direction for 360 day, 365 day, and actual calendars. GENERAL I chose the day range to be from 1 January 1583 through 31 December 9999. This carefully avoides the Gregorian / Julian calendar question for most of the world, anyways. I also chose to count days starting from 1 January 1 in the Gregorian calendar. This is not a Julian day number, but differs by a constant. The examples are written in mostly-Basic, assuming integer arithmetic. CONVERTING FROM A DAY / MONTH / YEAR TO A DAY NUMBER Input: year (1583 - 9999), month (0 - 11), day (1 - 31) Output: dayn mdays is an array of cumulative days. mdays(0) = 0, mdays(1) = 31, mdays(2) = 31 + 28, etc. This array has thirteen entries, so mdays(12) = 365. 360 day calendar. This is easy. dayn = 360 * year + 30 * month + day - 1 365 day calendar. This is easy, too. dayn = 365 * year + mdays(month) + day - 1 Actual calendar. This starts like 365 day calendar:; dayn = 365 * year + mdays(month) + day - 1 Then Jan and Feb get previous year's leap year counts. if month <= 1 then year = year - 1 dayn = dayn + year / 4 ! add leap years dayn = dayn - year / 100 ! subtract non-leap centuries dayn = dayn + year / 400 ! add back 400 years CONVERTING FROM A DAY NUMBER TO A DAY / MONTH / YEAR Output: dayn Input: year (1583 - 9999), month (0 - 11), day (1 - 31) mdays is as above. 360 day calendar. This is easy. year = dayn / 360 month = mod(dayn, 360) / 30 day = mod(dayn, 30) + 1 365 day calendar. This starts to get tricky. year = dayn / 365 dayn = mod(dayn, 365) for month = 0 to 12 step 1 if dayn < mdays(month + 1) then goto out next month out: day = dayn - mdays(month) + 1 Actually, the loop will never get month to 12. At some point before then, the cumulative days of the next month will be greater than the day number. Remember that dayn must be in the range 0 to 364 due to the mod and that mdays(12) = 365. Actual calendar. This gets REALLY tricky and is to a large extent the entire reason for the article. We have a day number in the range 0 to 3 600 000 (= 365 * 10 000). What we want to do is to divide this number by 365,2422 to get very close to the correct year (+/- 1). We must do this with integer arithmetic, so we multiply by 10 000 and divide by 3 652 422. But multiplying a day number by 10 000 exceeds a 32-bit integer (roughly 36 000 000 000 vs. about 2 100 000 000). We have to reduce the 10 000 by a factor of 20 or so in order not to overflow. If we call the orgininal number 365,2425 and so get 10 000 and 3 652 425 as the numerator and denominator, we can remove a factor of 25 and obain 400 and 146 097. We thus no longer overflow our integer. This works but is incorrect to the tune of 3 parts in (roughly) 3 000 000 or 1 in 1 000 000. As there are only about 2 500 leap year days that can foul things up, we are still close enough. Later steps will correct any error. temp = dayn * 400 temp = temp / 146097 Now the corrections start. First, make sure that we are before the correct year. In theory, we can be off by up to one year, so let's subtract two to be on the safe side. year = temp - 2 Now, we count up until we get to the correct year. month = 0; day = 1; for i = 0 to dayn step 0 i = ToDayNumber(year, month, day, "actual") if i = dayn then return ! we are done year = year + 1 next i done: year = year - 2 This loop calculates the day number for 1 January of each year until it surpasses our day number. At that point, we have to subtract one >from the year because we have, in fact, passed the correct year and another one because we incremented the year before we tested. Of course, if the day number matches exactly, the date is for 1 January and we can stop here. We now have the correct year. On to the month and day. Subtract off the day number of 1 January so that we have a number in the range 0 to 365 (if leap year). dayn = dayn - ToDayNumber(year, month, day, "actual") Check for January. if dayn < mdays(1) then day = dayn + 1 return ! done endif ! I said this was mostly-Basic Check for up to Febuary 28. if dayn < mdays(2) then month = 1 day = dayn - mdays(1) + 1 return endif So, our date must either be 29 Febuary (if we have a leap year) or some day after that. See if we are a leap year. if year / 4 = 0 and (year / 100 <> 0 or year / 400 = 0) then ! we have a leap year if dayn = mdays(2) then month = 1 day = 29 return endif dayn = dayn - 1 ! not 29 Feb, so convert it to endif ! a non-leap year day for month = 2 to 12 step 1 if dayn < mdays(month + 1) then goto out next month out: day = dayn - mdays(month) + 1 And that's it. I hope that you found this as interesting to read as I did to write (both the program and the article).
I am Craig A. Finseth.
Last modified Saturday, 2012-02-25T17:29:03-06:00.