Amsat-NA Logo

Amsat-UK's Oscar News, 1985 Dec No.56 p.6-9

30.6 DAYS HATH SEPTEMBER

by

James Miller G3RUH


All satellite programs involve manipulating dates in some way and if you
ever need an example of ugly coding, look no further than the typical
amateur calendar routine!  I recently came across one Loony program that
took over 30 program lines just to manipulate two dates and got it wrong. 
Yet the job can be done in as few as 5 lines of code.

I first encountered date algorithms some years ago in the applications 
program  handbook for the HP25C pocket calculator, and whilst researching
this piece they have turned up many places (refs 1,4-6).  Where they
actually originated I don't know.

I shall give the rules for getting from date to day numbers and vice versa
in two ways;  one where the date is in the familiar 1986-Jan-01 form, and
the other in Year, Day-of-year.  The latter is so simple that it can be
memorised.

Day Numbers
-----------
The first step to giving a sense of order to calendars is to think in
terms of day numbers.  All days can have a unique number assigned to them. 
Then elapsed times are easily found by subtracting day numbers, and
calendar routines have a rational system in which to work. Here are some
systems I know of:
                                   EXAMPLE OF DAY NUMBER
        System   Datum (Day 0)        for 1986 Jan 1st
        ------------------------------------------------
        AMSAT     1978 Jan 01               2922
        NASA(1)   1957 Sep 18              10332
        NASA(2)   1957 Jan 01              10592
        ESOC      1950 Jan 01              13149
        GENERAL    approx AD 1            725022
        Julian    4713 BC, Jan 1.5       2446431.5
        -----------------------------------------------

The Julian Day numbering system is a universal standard, particularly in
astronomic matters because it is the only unambiguously and continously
defined system going back to the dark ages.  NASA(2) is used in Goddard
Space Flight Centre's attitude determination programs; NASA(1) is used in
their orbit programs, and is called the Julian Day for Space (JDS).  It's
time elapsed since the first Julian day divisible by 100 prior to the
launch of Sputnik-1.  (see ref 3).  AMSAT day numbers are telemetered by
Oscar-10 and appear in AMSAT-US keplerian element sets (as the time of
reference perigee).  Note that 1978 Jan 1st is day 0, not 1 as some AMSAT
folk would tell you!

No  system  is  intrinsically  'the best'.  The choice of datum is quite
arbitrary and it's obvious that you can hop from one to another merely by
adding/subtracting a constant number.  For example you can get from a
NASA(2) to an ESOC day number by adding 2557, (which is 13149 - 10592).


The Calendar Rules
------------------
I suppose Pope Gregory XIII's calendar rules are well known; 365 days to a
year; an extra 'leap-day' if the year is divisible by 4, excepting century
years - unless they are divisible by 400.  30 days hath September, etc etc
and February has 28 days or 29 in a leap year.  Thus 1700, 1800, 1900 are
NOT leap-years, whereas 1600 and 2000 are. (Ref 2).

Leap year days create a 400 year cycle of 146097 days, which is an average
of 365.2425 days a year.  This is near enough 365.2422 days, the Earth's
orbital period about the Sun.  If it wasn't - and prior to Gregory (1582)
it was not - the seasons would drift.

Towards an Algorithm
--------------------
Gregory has a lot to answer for!  The main reason that calendar 
algorithmists get in a mess is that February which hath a variable number
of days is in the middle of the year.  Secondly, the other months have an
apparently haphazard mixture of 30/31 days.

Basically what we need is a rule to give a day number for the start of a
year (DSY), and rule for the start of each month (DSM).  Then the day
number is simply their sum, viz DAYNO = DSY + DSM + DOM + D0, where DOM is
the day of the month and D0 is a datum constant which chosen to suit the
day number system adopted (see above).

The secret to rational date algorithms is to start the computational
'year' on March 1st, not at the beginning of January.  So, 1980-Feb-29 is
regarded as 1979, month 12, day 29 (last day of 'year'), and the real
1980-March-01 is regarded as 1980, month 1, day 1.

                IF M <= 2 THEN M = M+12: Y = Y-1

This trick now gives us a run of 11 months containing 30/31 days, and
February now at the end.  It's no longer awkward in this position because
its days are simply added in to the year + month sub-total, just as for
any other month.

The 30/31 isn't such a pain in the neck; starting with March, the sequence
is 31 30 31 30 31 31 30 31 30 31 31 days.  Taking out 30 for clarity it's
1 0 1 0 1 1 0 1 0 1 1 days, which has a discernable 5 month rhythm to it,
viz 10101-10101-1.  Each bar has 3 beats, an average of 0.6 beats a month.

If you now jot down INT(0.6 * M) for M = 0,1,2 etc you get the sequence
0 1 1 2 2 3 4 4 5 .... which increments with the rhythm 10101-101..!  Put
back in the 30 and, if you're still with me, it should be apparent that
the function INT(30.6 * M) provides exactly the basic cadence we need for
a start of month rule.  

Leap Years
----------
Leap years are handled like this; ignoring century years, since there are
an average of 365.25 days a year, the day for start of year DSY can be
simply found from DSY = INT(year*365.25).  Because of the 0.25, every 4th
year the leap year increment appears automatically. 

And what of century years?  Easy; every 100 years remove the leap day
created above with -INT(year/100), and every 400 years put it back again
with +INT(year/400).

Day Number to date
------------------
The inverse problem is extracting the date from a day number.  Essentially
one is unscrambling the rules just given.  It's slightly messy because the
funtion INT(.) doesn't have an inverse as such, so one has to synthesise
it very carefully.

WARNING - Ignore This
---------------------
INT(X) means "the largest integer smaller than X".  Thus INT(-1.5) is -2. 
Some machines will give -1.  The definition given is regular through zero. 
If your machine gives -1 take great care - and complain to the
manufacturer!

In addition it is assumed that your computer/calculator can multiply 0.6 by
5, or divide 21 by 7 and get the result 3, not 2.9999999. If it doesn't you
may need to take corrective action.  John Morris (Reference 6) is excellent
on this.
 
ALGORITHM 1:  DATE to DAY NUMBER
--------------------------------
Takes a date in the form of year, month and day of month and calculates
its day number.  Valid from 1582 onwards:

D0 = -722528:   REM For AMSAT day number        )
D0 =    -428:   REM For GENERAL day number      )) CHOOSE ONE ONLY
D0 = 1720982:   REM For Julian Day at noon      )

REM Enter wih Year YR e.g. 1986, Month MN, Day DY. Result is Day Number DN
Y = YR: M = MN: D = DY:                 REM Preserve YR, MN, DY
IF M <= 2 THEN M = M+12: Y = Y-1
DN = -INT(Y/100)+INT(Y/400)+15  + INT(Y*365.25) + INT((M+1)*30.6) + D + D0

NOTES:
1. You may omit the century calculations so that:
        DN = INT(Y*365.25) + INT((M+1)*30.6) + D + D0
   This restricts the algorithm to  1900 Mar 01 until 2100 Feb 28,

2. Three values for D0 are given; choose only one though!


ALGORITHM 2:  DAY NUMBER TO DATE
--------------------------------
REM Enter with day number (DN). Results are Year (Y), Month (M) and
REM  Day (D), the day (D$) and month (M$) as strings.
D = DN - D0:                                            REM Note 4
DW = (D+5) - 7*INT((D+5)/7):                            REM Note 1
D = D + INT( INT((D+36387)/36524.25) * 3/4) - 15:       REM Note 2
Y = INT((D-122.1)/365.25):      D = D-INT(Y*365.25)
M = INT(D/30.61):               D = D-INT(M*30.6)
M = M-1: IF M > 12 THEN M = M-12: Y = Y+1
D$ = MID$("SunMonTueWedThuFriSat",3*DW+1,3):            REM Note 1
M$ = MID$("JanFebMarAprMayJunJulAugSepOctNovDec",3*M-2,3): REM Note 3

NOTES:
1. DW is day-of-the-week, and is 0 for Sunday.  Omit if you don't need.
2. You may omit this line for dates within 1900 Mar 01 - 2100 Feb 28 
3. Omit if you don't want the month in letters.
4. Value for D0 must be as chosen for date to day number algorithm.


QUICK ALGORITHMS 3:
-------------------
The following two algorithms will give you GENERAL day numbers from the
year and day of the year (Jan 1st = 1):

Date to Day Number
        DN = INT((YEAR-1)*365.25) + DAY

Day Number to Year/Day of Year
        YEAR = INT((DN+365)/365.25)
        DAY  = DN - INT((YEAR-1)*365.25)

Valid from 1901 Jan 01 - 2100 Dec 31 (General day numbers 693976-767024).

The GENERAL day number here is the SAME as for algorithms 1 and 2 above.

ISO Date Presentation
---------------------
Now the commercial.  Does "1st Jan 1986 @ 1432 UTC" or worse, "Jan 1st
1986" strike you as crazy?  What you need the International Standards
Organistation (ISO-8601) presentation, which has the all the digits in
descending order of significance, just like any other number:

                        1986 Jan 01 @ 1432 UTC

                       Happy New Year de G3RUH

REFERENCES
----------
1. HP19c/HP-29c Applications Book. Hewlett-Packard Company 1977, page 44.

2. Explanatory Supplement to the Astronomical Ephemeris. 1961 Rev. 1974.
   ISBN: 0-11-880578-9, HMSO. Chapter 14.

3. Spacecraft Attitude Determination and Control. Ed. James R. Wertz, D.
   Reidel Publishing Co. 1984 ISBN 90-277-1204-2.   Page 20.

4. Amateur Satellite Report No. 109 (1985 Sep 14). Note by Tom Clark W3IWI 

5. Oscar-10 Program for Sharp PC1246 by Dr Karl Meinzer DJ4ZC

6. Amateur Radio Software, John Morris GM4ANB, RSGB.  ISBN: 0-900612-71-1.
   Definitive, authoritative, stylish and fun.  Order one now.




Feedback on these pages to Webmaster. Feedback on the article should be sent to James Miller

Created: 1994 Nov 13 -- Last modified: 2005 Oct 29