home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
listings
/
v_11_08
/
twilling
/
jd2greg.c
< prev
Wrap
Text File
|
1993-02-25
|
17KB
|
376 lines
/**********************( GREGORIAN CALENDAR MODULE )**************************
Routines for converting between Gregorian Calendar dates and Julian Days, for
validating date input, for date arithmetic, and for learning day of the week
and of the year.
(c) 1991, 1993 by Bob Twilling.
C Users Journal readers may use this code for any purpose.
/-------------------( Export to header file "JD2GREG.H" )--------------------*/
typedef struct { short mo;
short dy;
short yr; } MDY;
extern MDY * jd2greg( long /*jd*/, MDY * /*date*/ );
extern long greg2jd( short /*month*/, short /*day*/ , short /*year*/ );
extern long ValidateDate(short /*month*/, short /*day*/ , short /*year*/ );
extern long DDays( MDY * /*date1*/, MDY * /*date2*/ );
extern MDY * DatePlus( MDY * /*date*/ , long /*ddays*/, MDY * /*newdt*/ );
extern int DayOfWeek( short /*month*/, short /*day*/ , short /*year*/ ,
char * /*name*/ );
extern int DayOfYear( short /*month*/, short /*day*/ , short /*year*/ );
/*=======================( MONTH TO DAY-OF-YEAR )===========================/
Given a month, calc day of year up until. This is a macro used by the next
two functions, see discussion below for the logic. Assumes rectified years
(March is month 1). Can change the #if to 0 to save 24 bytes of near heap
at a slight speed cost.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#if 1
static short m2doy[] =
{ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 };
#define M2DOY(mo) (m2doy[mo-1])
#else
#define M2DOY(mo) ((((mo) * 979) >> 5) - 30)
#endif
/*=========================( GREGORIAN TO JD )==============================/
Given a month, day, and year, returns a long integer -- the Julian Day number
at noon of that date.
ALGORITHM:
1> Rectify the date so that the year begins on March 1. This simplifies
calculations by putting oddball leapdays last.
2> Now calculate the day of year, e.g. Oct 28th is day number 242, Jan 3rd
is day number 309 (of the previous year! See above.) A lookup table is
the fastest way to account for the varied number of days in a month, at
a cost of 24 bytes of near heap space. Or we can use the formula:
doy = ((mo * 979) / 32) - 30 + dy;
March is the first month, remember. There's no theory behind these magic
numbers -- a lot of other ones work too, but I like my 32 (a nice round
number). 979/32, or 367/12, or 30.6, all equal about the average days
per month, ignoring February. The magic of integer arithmetic, and the
lucky fact that our 30-day months are well distributed, does the rest.
3> Compute the number of days up to the beginning of the year using the
simple Gregorian formula:
yrdays = (365 * yr) + (yr / 4) - (yr / 100) + (yr / 400);
There are some nice round numbers in this formula too, begging to be
replaced by shifts.
4> Add the result of the last two steps. Then add a constant for an origin
transform to the Julian Day system.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern long greg2jd( short mo, short dy, short yr ) {
short lp;
if ((mo -= 2) <= 0) { mo += 12; yr--; } // 1> Roll-under the date
dy += M2DOY(mo); // 2> Calc day of year
lp = yr >> 2; // 3> Add and subtract leap days
dy += lp;
lp /= 25;
dy -= lp;
dy += lp >> 2;
return (long)yr * 365 + dy + 1721119; } // 4> One div, one mul!
/*=========================( JD TO GREGORIAN )==============================/
Given a long Julian Day and a pointer to a month-day-year structure, fills in
that structure with the calendar date and returns the same pointer to it.
ALGORITHM:
1> Transform the origin of the Julian Day to the start of some 400-year
period. Because we are assuming a JD valid in the Gregorian Calendar we
subtract 2305507, so that day 1 is 1-Mar-1600. As the routine is currently
written this gives an algorithm valid from 1200 AD.
2> Subtract or divide out 400-year (146097 day) blocks, then 100-year (36524
day) blocks, noting the number of blocks removed. Since 16-bit machines
and/or compilers do long division like paint dries, we use successive
subtraction -- sort of. Actually, we subtract too far, then add back, for
a slight speed gain. When we migrate to 32-bits, rewrite this part for
speed and comprehensibility.
3> Divide out 4-year (1461 day) and 1-year (365 day) blocks. Calculate the
calendar year from the number and size of blocks removed; the remainder is
the day of the year. If the Julian Day represented a Feb 29, the algorithm
fails here because 1461/365 equals more than four. Check and adjust for
this special case.
4> Calculate the month of the year from the magic-number formula:
mo = ((doy + 30) * 32) / 979;
Surprisingly, this is faster on a 386 than scanning a twelve member table.
Compute the day of the month by subtracting the days up until that month
using either the table or the formula declared in M2DOY() above.
5> Roll over the month and year if we found a date in January or February.
6> Be careful when changing this code: 146097/36524 and 1461/365 both equal
4, which blows up the algorithm on JD's representing Feb 29's. As written,
we avoid the first by a tricky origin shift, the second by explicitly
limiting 1461/365 to a max of three.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include <stdlib.h> //for div()
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern MDY * jd2greg( long jd, MDY * date ) {
unsigned short x, y, d;
div_t sd;
y = 1600; jd -= 2305507; // 1> new origin 0 = 2/29/1600
while (jd > 0) { y += 400; jd -= 146097; } // 2> lop off 400-yr blocks
do { y -= 100; } while ((jd += 36524) < 0); // and add back 100-yr ones
d = (unsigned short)jd; // 3> within 16-bit range now
x = d / 1461; // pity ain't no ANSI udiv()
y += x << 2; // note 4-yr blocks
d -= x * 1461; // and lop them off
sd = div( d, 365); // does % and / in same op
date->yr = y + sd.quot; // got years, provisionally
d = ++sd.rem; // 4> day-of-year (base 1)
if (sd.quot == 4) { date->yr--; d = 366; } // case we hit a leap-year
x = ((d + 30) << 5) / 979; // x = month, March is 1
date->dy = d - M2DOY(x); // got day of month
if ((x += 2) > 12) { date->yr++; x -= 12; } // 5> roll around Jan and Feb
date->mo = x; // got month
return(date); }
/*==========================( VALIDATE DATE )===============================/
Given a month, day, and year, returns a positive long integer representing the
corresponding Julian Day at 12h UTC, unless the passed date is invalid in the
Gregorian Calendar, in which case returns zero. This somewhat-boolean retval
will save the user another call to greg2jd() if the date does prove valid.
ALGORITHM:
1> Convert the passed Gregorian date into its Julian Day.
2> Check that the date is not earlier than 15 Oct 1582, the first day
Gregory's calendar was in use anywhere. An earlier date would indicate
that the caller should have used Julian Calendar (Old Style) conversion
routines.
3> Check that the date is not later than 28 Feb 4000. On somewhat shaky
ground here. If our planet's orbital speed doesn't change much, Greg's
calendar will lose a day every 3300 years. I read somewhere of a proposal
for a Revised Gregorian Calendar which drops the leap year in 4000 AD and
every four thousand years thereafter. But I don't know if the proposal has
been widely accepted, don't know what's going to happen culturally or
astronomically over two millennia, and don't really care since this
module's goal is to implement only Gregory's original system.
Just to be safe, I have provisionally and unilaterally declared 4000 AD
to be the end of the Gregorian Calendar. When the time comes, make sure
either to remove this restriction or to rewrite the formulae.
4> If the Julian Day passes the boundary conditions, convert it back to a
calendar date and check that that date equals the caller's. This shows up
mistakes like 31 Nov 1991, or 3.15.1992 (when the caller was expecting
Canadian-style input).
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern long ValidateDate( short mo, short dy, short yr) {
MDY date;
long jd = greg2jd( mo, dy, yr);
if ((jd >= 2299161) && (jd <= 3182088)) {
jd2greg(jd, &date);
if (date.dy == dy && date.mo == mo && date.yr == yr) {
return jd; } } //suceeded
return 0; } //failed
/*=========================( DIFFERENCE IN DAYS )============================
Given two pointers to month-day-year structures, returns the positive or
negative difference between date2 and date1. Value is positive if the second
date is later than the first, in keeping with the HP-41 function even though
this seems to violate RPN logic. No date validation is done, and the
subtraction could fail for dates outside the original Gregorian range. Could
easily inline this function.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern long DDays( MDY * date1, MDY * date2) {
return greg2jd(date2->mo, date2->dy, date2->yr)
- greg2jd(date1->mo, date1->dy, date1->yr); }
/*===========================( DATE ADDITION )===============================
Given two pointers to month-day-year structures and a (positive or negative)
long integer, adds that integer number of days to the first MDY and fills in
the second with the answer. Returns the pointer to the second structure.
Another good candidate for inlining.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
extern MDY * DatePlus( MDY * date, long dys, MDY * newdate) {
return jd2greg((greg2jd( date->mo, date->dy, date->yr) + dys), newdate ); }
/*============================( DAY OF WEEK )===============================/
Given month-day-year, returns an integer representing the day of the week.
Zero is Monday. Caller can also get the weekday name by passing a pointer to
a string workspace at least 7 characters long. Most such callers will want to
either abbreviate the string to 3 characters, or add a "day" onto it using
strcat() or as part of a printf() format. Callers that do not want the string
must pass a NULL pointer. Algorithm is simply (JD mod 7).
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include <string.h> //for strcpy()
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int DayOfWeek( short mo, short dy, short yr, char * strg) {
static char * dnm[] = { "Mon","Tues","Wednes","Thurs","Fri","Satur","Sun" };
int d = (short)(greg2jd(mo,dy,yr) % 7);
if (strg) strcpy(strg, dnm[d]);
return d; }
/*============================( DAY OF YEAR )===============================/
Given a month-day-year, returns the day of that year. Jan first is 1. Handy
for numbering invoices, etc. An invalid Gregorian date returns 0. The
routine is correct for Gregory's short year 1582, although we could probably
save the code -- the result will usually be meaningless for historical
periods, when the year began in March. Algorithm is simple JD subtraction.
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int DayOfYear( short mo, short dy, short yr) {
long jd = ValidateDate( mo, dy, yr);
if (jd) {
if (yr != 1582) {
return (int)(jd - greg2jd( 1, 1, yr)) + 1; }
else { //10 days missing from 1582
return (int)(jd - 2299161 + 278); } }
else return 0; }
/*=============================( CAUTIONS )=================================/
1> All callers, be aware that some of these routines work only when passed a
valid Julian Day. As a matter of course, validate your calendar dates.
2> Astronomy callers, be aware....
The Julian Day passed to and from these routines is at 12h UTC. So
ephemerides should use this formula to calculate the proper fractional
Julian Day.:
JD = (double)greg2jd() - 0.5 + (UTC / 24.0);
3> Non-astronomy users, you can safely assume that Local Time equals UTC only
so long as you are not crossing different time zones.
4> Geneology and History callers, be aware for two reasons....
High school history may have taught us that Europeans changed to the
Gregorian Calendar on 15 Oct 1582. 'Taint so. Only the Papal States
changed on that date. Even other Catholic countries took a up to a couple
years to switch, after putting down their rent riots. (What tenant farmer
wants to pay full price for a 20 day month?) And, on the principle that a
good idea from my enemy is a bad idea, Protestant countries took a couple
hundred years to change over, Orthodox ones even longer. Washington was
born on 11 Feb, he thought, until England switched in 1752 to give us our
Feb 22 holiday. Russia didn't switch until the Revolution (the 1917, not
this one).
Reason two: New Years Day wasn't always the first of January. Again,
Christendom switched from a date, usually in March, to Jan 1 at various,
seemingly whimsical, times.
The upshot? That date -- in the back of an old Bible, on the Margraves'
and Metropolitans' proclamations -- may or may not be in Gregory's system.
You'll need to know the calendar used during your period of study better
than this program does.
/===============================( HISTORY )=================================/
Feb 1582: Pope Gregory XIII, acting on recommendation of a scientific
commission, reforms Julius Caesar's calendar to drop leap days
on years divisible by 100 unless also divisible by 400. He
decrees next October will drop 10 days to eliminate accumulated
error so that Easter gets back on track.
1582: Joe Scaliger, perhaps inspired by the Mayans, invents a "long
count" calendar as a standard of comparison between different
chronological systems. His epoch begins 4713 BC and lasts 7980
years. He writes routines for both the Julian and Gregorian
calendars. He names his epoch "Julian Days" after his father-in-
law (or father or uncle, sources vary), to the eternal confusion
of students' who imagine it has something to do with the Caesar.
My sources don't say whether Joe is a member of the papal
commission or whether it is news reports of their findings that
inspires him to program his new system.
1898: Sam Newcomb publishes Julian Day formulae for the "American
Ephemeris" and "Nautical Almanac." Astronomers are still using
Joe's epoch because it handily predates all recorded (even
Chinese and African) observations.
1979: Jean Meeus publishes "Astronomical Formulae for Calculators" in
Belgium. A French edition follows in 1980, an American one in
1985. The calendar routines in his books are adopted by many
programmers to replace previous buggy ones. I adopt them in
1989. Still, after 400 years, Julian Days are the basis for
time calculation. Nice job, Joe.
Aug 1991: I write a program in which Meeus' calendar routines are the only
ones using floating-point math. And they are pulling in a whole
16K emulation library. I rewrite to use long integers instead of
floats. And I drop Julian Calendar conversions -- they will be
in a separate module should I ever need them.
9 Feb 1993: I squeeze a little more speed from the integer routines, add
excessive comments, and add a 4000 AD validation check.
/===================================================================ENDIT====*/