1
/*****************************************************************************\
4
* DateOps contains misc. time and date operations
6
* author: mark huss (mark@mhuss.com)
7
* Based on Bill Gray's open-source code at projectpluto.com
9
\*****************************************************************************/
15
/* General calendrical comments:
17
This code supports conversions between JD and five calendrical systems:
18
Julian, Gregorian, Hebrew, Islamic, and (French) revolutionary.
19
Comments pertaining to specific calendars are found near the code for
22
For each calendar, there is a "get_(calendar_name)_year_data( )" function,
23
used only within this source code. This function takes a particular year
24
number, and computes the JD corresponding to "new years day" (first day of
25
the first month) in that calendar in that year. It also figures out the
26
number of days in each month of that year, returned in the array
27
month_data[]. There can be up to 13 months, because the Hebrew calendar
28
(and some others that may someday be added) can include an "intercalary
29
month". If a month doesn't exist, then the month_data[] entry for it
30
will be zero (thus, in the Gregorian and Julian and Islamic calendars,
31
month_data[12] is always zero, since these calendars have only 12 months.)
33
The next level up is the get_calendar_data( ) function, which (through
34
the wonders of a switch statement) can get the JD of New Years Day and
35
the array of months for any given year for any calendar. Above this
36
point, all calendars can be treated in a common way; one is shielded
37
from the oddities of individual calendrical systems.
39
Finally, at the top level, we reach the only two functions that are
40
exported for the rest of the world to use: dmy_to_day( ) and day_to_dmy( ).
41
The first takes a day, month, year, and calendar system. It calls
42
get_calendar_data( ) for the given year, adds in the days in the months
43
intervening New Years Day and the desired month, and adds in the day
44
of the month, and returns the resulting Julian Day.
46
day_to_dmy( ) reverses this process. It finds an "approximate" year
47
corresponding to an input JD, and calls get_calendar_data( ) for
48
that year. By adding all the month_data[] values for that year, it
49
can also find the JD for the _end_ of that year; if the input JD is
50
outside that range, it may have to back up a year or add in a year.
51
Once it finds "JD of New Years Day < JD < JD of New Years Eve", it's
52
a simple matter to track down which month and day of the month corresponds
56
const char* monthNames[12] = {
57
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
58
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
62
* Begin: Gregorian and Julian calendars (combined for simplicity)
64
* It's common to implement Gregorian/Julian calendar code with the
65
* aid of cryptic formulae, rather than through simple lookup tables.
66
* For example, consider this formula from Fliegel and Van Flandern,
67
* to convert Gregorian (D)ay, (M)onth, (Y)ear to JD:
69
* JD = (1461*(Y+4800+(M-14)/12))/4+(367*(M-2-12*((M-14)/12)))/12
70
* -(3*((Y+4900+(M-14)/12)/100))/4+D-32075
72
* The only way to verify that they work is to feed through all possible
73
* cases. Personally, I like to be able to look at a chunk of code and
74
* see what it means. It should resemble the Reformation view of the
75
* Bible: anyone can read it and witness the truth thereof.
78
//----------------------------------------------------------------------------
79
void DateOps::getJulGregYearData(
80
long year, long& daysInYear, MonthDays& md, bool julian )
82
static const MonthDays months =
83
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 };
86
daysInYear = year * 365L + year / 4L;
88
daysInYear += -year / 100L + year / 400L;
91
daysInYear = (year * 365L) + (year - 3L) / 4L;
93
daysInYear += -(year - 99L) / 100L + (year - 399L) / 400L;
99
memcpy( &md, months, sizeof(MonthDays) );
102
if( (year % 100L) || !(year % 400L) || julian ) {
107
daysInYear += E_JULIAN_GREGORIAN + 1;
110
//----------------------------------------------------------------------------
111
int DateOps::getCalendarData(
112
long year, YearEndDays& days, MonthDays& md, int calendar)
116
memset( &md, 0, sizeof(MonthDays) );
121
getJulGregYearData( year, days[0], md, (T_JULIAN == calendar) );
123
#if defined( CALENDARS_OF_THE_WORLD )
125
getIslamicYearData( year, days[0], md );
128
getHebrewYearData( year, days, md );
130
case T_REVOLUTIONARY:
131
getRevolutionaryYearData( year, days, md );
134
if( year >= LOWER_PERSIAN_YEAR && year <= UPPER_PERSIAN_YEAR)
135
getJalaliYearData( year, days, md );
146
// days[1] = JD of "New Years Eve" + 1; that is, New Years Day of the
147
// following year. If you have days[0] <= JD < days[1], JD is in the
151
for( int i=0; i<13; i++ )
160
* just gets calendar data for the current year, including the JD of New Years
161
* Day for that year. After that, all it has to do is add up the days in
162
* intervening months, plus the day of the month, and it's done:
165
// calendar: 0 = gregorian, 1 = julian
167
long DateOps::dmyToDay( int day, int month, long year, int calendar )
173
if( 0 == getCalendarData( year, yed, md, calendar ) ) {
175
for( int i=0; i<(month-1); i++ ) {
186
* Estimates the year corresponding to an input JD and calls get_calendar_data();
187
* for that year. Occasionally, it will find that the guesstimate was off;
188
* in such cases, it moves ahead or back a year and tries again. Once it's
189
* done, jd - year_ends[0] gives the number of days since New Years Day;
190
* by subtracting month_data[] values, we quickly determine which month and
191
* day of month we're in.
194
// calendar: 0 = gregorian, 1 = julian
196
void DateOps::dayToDmy( long jd, int& day, int& month, long& year, int calendar )
198
day = -1; /* to signal an error */
202
year = (jd - E_JULIAN_GREGORIAN) / 365L;
204
#if defined( CALENDARS_OF_THE_WORLD )
206
year = (jd - E_HEBREW) / 365L;
209
year = (jd - E_ISLAMIC) / 354L;
211
case T_REVOLUTIONARY:
212
year = (jd - E_REVOLUTIONARY) / 365L;
215
year = (jd - JALALI_ZERO) / 365L;
216
if( year < LOWER_PERSIAN_YEAR)
217
year = LOWER_PERSIAN_YEAR;
218
if( year > UPPER_PERSIAN_YEAR)
219
year = UPPER_PERSIAN_YEAR;
222
default: /* undefined calendar */
230
if( 0 != getCalendarData( year, yed, md, calendar ) )
239
} while( yed[0] > jd || yed[1] <= jd );
241
long currJd = yed[0];
243
for( int i = 0; i < 13; i++) {
244
day = int( jd - currJd );
250
currJd += long( md[i] );
256
//----------------------------------------------------------------------------
257
// Determine DST start JD (first Sunday in April)
259
long DateOps::dstStart(int year)
261
long jdStart = dmyToDay( 1, 4, year, T_GREGORIAN );
262
while ( 6 != (jdStart % 7 ) ) // Sunday
268
//----------------------------------------------------------------------------
269
// Determine DST end JD (last Sunday in October)
271
long DateOps::dstEnd(int year)
273
long jdEnd = dmyToDay( 31, 10, year, T_GREGORIAN );
274
while ( 6 != (jdEnd % 7 ) ) // Sunday
280
//----------------------------------------------------------------------------