craig-date

[ 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).