tcalcalcs.c - pism - [fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
HTML git clone git://src.adamsgaard.dk/pism
DIR Log
DIR Files
DIR Refs
DIR LICENSE
---
tcalcalcs.c (55991B)
---
1 /*
2 The CalCalcs routines, a set of C-language routines to perform
3 calendar calculations.
4
5 Version 1.0, released 7 January 2010
6
7 Copyright (C) 2010 David W. Pierce, dpierce@ucsd.edu
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <ctype.h>
28
29 #include "calcalcs.h"
30
31 static int c_isleap_gregorian ( int year, int *leap );
32 static int c_isleap_gregorian_y0( int year, int *leap );
33 static int c_isleap_julian ( int year, int *leap );
34 static int c_isleap_never ( int year, int *leap );
35
36 static int c_date2jday_julian ( int year, int month, int day, int *jday );
37 static int c_date2jday_gregorian ( int year, int month, int day, int *jday );
38 static int c_date2jday_gregorian_y0( int year, int month, int day, int *jday );
39 static int c_date2jday_noleap ( int year, int month, int day, int *jday );
40 static int c_date2jday_360_day ( int year, int month, int day, int *jday );
41
42 static int c_jday2date_julian ( int jday, int *year, int *month, int *day );
43 static int c_jday2date_gregorian ( int jday, int *year, int *month, int *day );
44 static int c_jday2date_gregorian_y0( int jday, int *year, int *month, int *day );
45 static int c_jday2date_noleap ( int jday, int *year, int *month, int *day );
46 static int c_jday2date_360_day ( int jday, int *year, int *month, int *day );
47
48 static int c_dpm_julian ( int year, int month, int *dpm );
49 static int c_dpm_gregorian ( int year, int month, int *dpm );
50 static int c_dpm_gregorian_y0( int year, int month, int *dpm );
51 static int c_dpm_noleap ( int year, int month, int *dpm );
52 static int c_dpm_360_day ( int year, int month, int *dpm );
53
54 #define CCS_ERROR_MESSAGE_LEN 8192
55 static char error_message[CCS_ERROR_MESSAGE_LEN];
56
57 /* Following are number of Days Per Month (dpm). They are called 'idx1' to
58 * emphasize that they are intended to be index by a month starting at 1
59 * rather than at 0.
60 * na, jan feb mar apr may jun jul aug sep oct nov dec */
61 static int dpm_idx1[] = {-99, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
62 static int dpm_leap_idx1[] = {-99, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
63
64 /* Same as above, but SUM of previous months. Indexing starts at 1 for January */
65 static int spm_idx1[] = {-99, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
66 /* static int spm_leap_idx1[] = {-99, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; note: spm is only used for calendars with no leap years */
67
68 static int date_ge( int year, int month, int day, int y2, int m2, int d2 );
69 static int date_le( int year, int month, int day, int y2, int m2, int d2 );
70 static int date_lt( int year, int month, int day, int y2, int m2, int d2 );
71 static int date_gt( int year, int month, int day, int y2, int m2, int d2 );
72 static int set_xition_extra_info( calcalcs_cal *cal );
73 static void ccs_dump_xition_dates( void );
74 static void ccs_gxd_add_country( char *code, char *longname, int year, int month, int day );
75 static void ccs_init_country_database( void );
76
77 /* Some arbitrary number that is unlikely to be encounterd in a string of random digits */
78 #define CCS_VALID_SIG 89132412
79
80 /* These implement the database that associates a two-letter country code, such as "UK",
81 * with the transition date that the country switched from the Julian to Gregorian calendar
82 */
83 #define CCS_MAX_N_COUNTRY_CODES 5000
84 static int ccs_n_country_codes = 0;
85 static ccs_country_code *ccs_xition_dates[ CCS_MAX_N_COUNTRY_CODES ];
86 static int have_initted_country_codes = 0;
87
88 /**********************************************************************************************
89 * Initialize a calendar. The passed argument is the name of the calendar, and may be
90 * one of the following character strings:
91 * "standard"
92 * "proleptic_Julian"
93 * "proleptic_Gregorian"
94 * "noleap" (aka "365_day" and "no_leap")
95 * "360_day"
96 *
97 * As a special hack, a calendar can be named "standard_XX" where XX is a two-letter
98 * date code recognized by ccs_get_xition_date, in which case a standard calendar with
99 * the specified transition date will be used.
100 *
101 * Returns a pointer to the new calendar, or NULL on error.
102 */
103 calcalcs_cal *ccs_init_calendar( const char *calname )
104 {
105 calcalcs_cal *retval;
106 int use_specified_xition_date, spec_year_x, spec_month_x, spec_day_x;
107
108 error_message[0] = '\0';
109
110 if( strncasecmp( calname, "standard", 8 ) == 0 ) {
111
112 if( ! have_initted_country_codes )
113 ccs_init_country_database();
114
115 /* See if this is a name of the form "Standard_XX" */
116 use_specified_xition_date = 0;
117 if( (strlen(calname) >= 11) && (calname[8] == '_')) {
118 if( ccs_get_xition_date( calname+9, &spec_year_x, &spec_month_x, &spec_day_x ) != 0 ) {
119 fprintf( stderr, "Error, unknown calendar passed to ccs_init_calendar: \"%s\". Returning NULL\n",
120 calname );
121 return(NULL);
122 }
123 use_specified_xition_date = 1;
124 }
125
126 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
127 if( retval == NULL ) {
128 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
129 return( NULL );
130 }
131 retval->sig = CCS_VALID_SIG;
132 retval->name = (char *)malloc( sizeof(char) * (strlen(calname)+1) );
133 /* new code by CK */
134 if ( retval->name == NULL ) {
135 free(retval);
136 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
137 return( NULL );
138 }
139 /* end of new code by CK */
140 strcpy( retval->name, calname );
141
142 retval->mixed = 1;
143 retval->early_cal = ccs_init_calendar( "proleptic_julian" );
144 retval->late_cal = ccs_init_calendar( "proleptic_gregorian" );
145
146 /* Following are FIRST DAY the "later" calendar should be used */
147 if( use_specified_xition_date == 1 ) {
148 retval->year_x = spec_year_x;
149 retval->month_x = spec_month_x;
150 retval->day_x = spec_day_x;
151 }
152 else
153 {
154 retval->year_x = 1582;
155 retval->month_x = 10;
156 retval->day_x = 15;
157 }
158
159 /* Set the last date the earlier cal was used, and the transition day's Julian date */
160 if( set_xition_extra_info( retval ) != 0 ) {
161 fprintf( stderr, "calcalcs_init_cal: Error trying to initialize calendar \"%s\": %s. Returning NULL\n",
162 calname, error_message );
163 /* new code by CK */
164 free(retval->name);
165 free(retval);
166 /* end of new code by CK */
167 return(NULL);
168 }
169 }
170
171 else if( (strcasecmp( calname, "gregorian" ) == 0) ||
172 (strcasecmp( calname, "proleptic_gregorian" ) == 0)) {
173
174 /* This is a "regular" Gregorian calendar, which does not include "year 0".
175 * See also calendar gregorian_y0, which does include a year 0, below
176 */
177 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
178 if( retval == NULL ) {
179 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar\n" );
180 return( NULL );
181 }
182 retval->sig = CCS_VALID_SIG;
183 retval->name = (char *)malloc( sizeof(char) * (strlen(calname)+1) );
184 /* new code by CK */
185 if ( retval->name == NULL ) {
186 free(retval);
187 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
188 return( NULL );
189 }
190 /* end of new code by CK */
191 strcpy( retval->name, calname );
192 retval->ndays_reg = 365;
193 retval->ndays_leap = 366;
194
195 retval->mixed = 0;
196
197 retval->c_isleap = &c_isleap_gregorian;
198 retval->c_date2jday = &c_date2jday_gregorian;
199 retval->c_jday2date = &c_jday2date_gregorian;
200 retval->c_dpm = &c_dpm_gregorian;
201 }
202
203 else if( (strcasecmp( calname, "gregorian_y0" ) == 0) ||
204 (strcasecmp( calname, "proleptic_gregorian_y0" ) == 0)) {
205
206 /* This is a Gregorian calendar that includes "year 0".
207 */
208 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
209 if( retval == NULL ) {
210 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar\n" );
211 return( NULL );
212 }
213 retval->sig = CCS_VALID_SIG;
214 retval->name = (char *)malloc( sizeof(char) * (strlen(calname)+1) );
215 /* new code by CK */
216 if ( retval->name == NULL ) {
217 free(retval);
218 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
219 return( NULL );
220 }
221 /* end of new code by CK */
222 strcpy( retval->name, calname );
223 retval->ndays_reg = 365;
224 retval->ndays_leap = 366;
225
226 retval->mixed = 0;
227
228 retval->c_isleap = &c_isleap_gregorian_y0;
229 retval->c_date2jday = &c_date2jday_gregorian_y0;
230 retval->c_jday2date = &c_jday2date_gregorian_y0;
231 retval->c_dpm = &c_dpm_gregorian_y0;
232 }
233
234 else if( (strcasecmp( calname, "julian" ) == 0 ) ||
235 (strcasecmp( calname, "proleptic_julian" ) == 0 )) {
236 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
237 if( retval == NULL ) {
238 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar\n" );
239 return( NULL );
240 }
241 retval->sig = CCS_VALID_SIG;
242 retval->name = (char *)malloc( sizeof(char) * (strlen(calname)+1) );
243 /* new code by CK */
244 if ( retval->name == NULL ) {
245 free(retval);
246 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
247 return( NULL );
248 }
249 /* end of new code by CK */
250 strcpy( retval->name, calname );
251 retval->ndays_reg = 365;
252 retval->ndays_leap = 366;
253
254 retval->mixed = 0;
255
256 retval->c_isleap = &c_isleap_julian;
257 retval->c_date2jday = &c_date2jday_julian;
258 retval->c_jday2date = &c_jday2date_julian;
259 retval->c_dpm = &c_dpm_julian;
260 }
261
262 else if( (strcasecmp(calname,"noleap")==0) ||
263 (strcasecmp(calname,"no_leap")==0) ||
264 (strcasecmp(calname,"365_day")==0)) {
265 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
266 if( retval == NULL ) {
267 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar\n" );
268 return( NULL );
269 }
270 retval->sig = CCS_VALID_SIG;
271 retval->name = (char *)malloc( sizeof(char) * (strlen("noleap")+1) );
272 /* new code by CK */
273 if ( retval->name == NULL ) {
274 free(retval);
275 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
276 return( NULL );
277 }
278 /* end of new code by CK */
279 strcpy( retval->name, "noleap" );
280 retval->ndays_reg = 365;
281 retval->ndays_leap = 365;
282
283 retval->mixed = 0;
284
285 retval->c_isleap = &c_isleap_never;
286 retval->c_date2jday = &c_date2jday_noleap;
287 retval->c_jday2date = &c_jday2date_noleap;
288 retval->c_dpm = &c_dpm_noleap;
289 }
290
291 else if( strcasecmp(calname,"360_day")==0) {
292 retval = (calcalcs_cal *)malloc( sizeof(calcalcs_cal) );
293 if( retval == NULL ) {
294 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar\n" );
295 return( NULL );
296 }
297 retval->sig = CCS_VALID_SIG;
298 retval->name = (char *)malloc( sizeof(char) * (strlen(calname)+1) );
299 /* new code by CK */
300 if ( retval->name == NULL ) {
301 free(retval);
302 fprintf( stderr, "Error, cannot allocate space for the calcalcs calendar. Returning NULL\n" );
303 return( NULL );
304 }
305 /* end of new code by CK */
306 strcpy( retval->name, calname );
307 retval->ndays_reg = 360;
308 retval->ndays_leap = 360;
309
310 retval->mixed = 0;
311
312 retval->c_isleap = &c_isleap_never;
313 retval->c_date2jday = &c_date2jday_360_day;
314 retval->c_jday2date = &c_jday2date_360_day;
315 retval->c_dpm = &c_dpm_360_day;
316 }
317
318 else
319 return( NULL );
320
321 return( retval );
322 }
323
324 /**********************************************************************************************
325 *
326 * Determine if the passed year is a leap year in the specified calendar.
327 * The passed parameter leap is set to '1' if the year is a leap year, and '0' if it is not.
328 *
329 * Returns 0 on success, and a negative value on error.
330 * Errors include the passed year being invalid (before 4713 B.C.) or not existing
331 * in the specified calendar (i.e., there is no year 0 in either the Gregorian or
332 * Julian calendars).
333 *
334 */
335 int ccs_isleap( calcalcs_cal *calendar, int year, int *leap )
336 {
337 int ierr;
338 calcalcs_cal *c2use;
339
340 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
341 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
342
343 if( year < -4714 ) {
344 sprintf( error_message, "ccs_isleap: year %d is out of range for the %s calendar; dates must not be before 4713 B.C.", year, calendar->name );
345 return( CALCALCS_ERR_OUT_OF_RANGE );
346 }
347
348 if( calendar->mixed ) {
349 if( year >= calendar->year_x ) /* Q: did any countries transition during a year that had different leap status before and after??? Let's hope not! */
350 c2use = calendar->late_cal;
351 else
352 c2use = calendar->early_cal;
353 }
354 else
355 c2use = calendar;
356
357 ierr = c2use->c_isleap( year, leap );
358 return( ierr );
359 }
360
361 /**********************************************************************************************
362 * ccs_dpm: returns the number of days per month for the passed year/month/calendar combo
363 *
364 * Returns 0 on success, and a negative number on error (for example, an illegal month number)
365 */
366 int ccs_dpm( calcalcs_cal *calendar, int year, int month, int *dpm )
367 {
368 int ndays_reg, ierr;
369 int overlap_px_month, overlap_x_month;
370 calcalcs_cal *c2use;
371
372 if( calendar->mixed ) {
373 /* A calendar transition potentially affects two months -- the month containing the
374 * last day of the old calendar, and the month containing the first day of the
375 * new calendar. If we are in either of those months, things get much harder.
376 * (Note that these can easily be the same month)
377 */
378 overlap_px_month = ((year == calendar->year_px) && (month == calendar->month_px));
379 overlap_x_month = ((year == calendar->year_x ) && (month == calendar->month_x ));
380 if( overlap_px_month || overlap_x_month ) {
381 if( overlap_px_month && (!overlap_x_month)) {
382 /* Last day of the month must have been last day the early calendar was used */
383 *dpm = calendar->day_px;
384 return(0);
385 }
386 else if( overlap_x_month && (!overlap_px_month)) {
387 if( (ierr = ccs_dpm( calendar->late_cal, year, month, &ndays_reg )) != 0 )
388 return( ierr );
389 *dpm = ndays_reg - calendar->day_x + 1;
390 return(0);
391 }
392
393 else /* overlap_px_month && overlap_x_month */
394 {
395 if( (ierr = ccs_dpm( calendar->late_cal, year, month, &ndays_reg )) != 0 )
396 return( ierr );
397 *dpm = calendar->day_px + (ndays_reg - calendar->day_x + 1);
398 return(0);
399 }
400 }
401 else if( date_ge( year, month, 1, calendar->year_x, calendar->month_x, calendar->day_x ))
402 c2use = calendar->late_cal;
403 else
404 c2use = calendar->early_cal;
405 }
406 else
407 c2use = calendar;
408
409 return( c2use->c_dpm( year, month, dpm ));
410 }
411
412 /**********************************************************************************************
413 * ccs_jday2date: give a Julian day number, return the corresponding date in the
414 * selected calendar
415 *
416 * Returns 0 on success, <0 on error and fills string error_message
417 */
418 int ccs_jday2date( calcalcs_cal *calendar, int jday, int *year, int *month, int *day )
419 {
420 calcalcs_cal *c2use;
421
422 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
423 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
424
425 if( calendar->mixed ) {
426 if( jday >= calendar->jday_x )
427 c2use = calendar->late_cal;
428 else
429 c2use = calendar->early_cal;
430 }
431 else
432 c2use = calendar;
433
434 return( c2use->c_jday2date( jday, year, month, day ));
435 }
436
437 /**********************************************************************************************
438 * ccs_date2jday: given a date, return the (true) Julian day number
439 *
440 * Note that "Julian day number" is not the day number of the year, but rather the
441 * day number starting on Jan 1st 4713 BC (in the proleptic Julian calendar) and
442 * counting consecutively.
443 *
444 * Returns 0 on success, <0 on error and fills string error_message
445 */
446 int ccs_date2jday( calcalcs_cal *calendar, int year, int month, int day, int *jday )
447 {
448 int dpm, ierr;
449 calcalcs_cal *c2use;
450
451 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
452 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
453
454 if( calendar->mixed ) {
455 if( date_ge( year, month, day, calendar->year_x, calendar->month_x, calendar->day_x ))
456 c2use = calendar->late_cal;
457 else if( date_le( year, month, day, calendar->year_px, calendar->month_px, calendar->day_px ))
458 c2use = calendar->early_cal;
459 else
460 {
461 sprintf( error_message, "ccs_date2jday: date %04d-%02d-%02d is not a valid date in the %s calendar; it falls between the last date the %s calendar was used (%04d-%02d-%02d) and the first date the %s calendar was used (%04d-%02d-%02d)",
462 year, month, day, calendar->name,
463 calendar->early_cal->name,
464 calendar->year_px, calendar->month_px, calendar->day_px,
465 calendar->late_cal->name,
466 calendar->year_x, calendar->month_x, calendar->day_x );
467 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
468 }
469 }
470 else
471 c2use = calendar;
472
473 if( (ierr = ccs_dpm( c2use, year, month, &dpm )) != 0 )
474 return( ierr );
475
476 if( (month < 1) || (month > 12) || (day < 1) || (day > dpm)) {
477 sprintf( error_message, "date2jday passed an date that is invalid in the %s calendar: %04d-%02d-%02d",
478 c2use->name, year, month, day );
479 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
480 }
481
482 return( c2use->c_date2jday( year, month, day, jday ));
483 }
484
485 /********************************************************************************************
486 *
487 * ccs_date2doy: given a Y/M/D date, calculates the day number of the year, starting at 1 for
488 * January 1st.
489 *
490 * Returns 0 on success, and a negative value on error (for example, an illegal date [one
491 * that does not exist in the specified calendar])
492 */
493 int ccs_date2doy( calcalcs_cal *calendar, int year, int month, int day, int *doy )
494 {
495 int ierr, jd0, jd1, doy_px, jd_args, xition_date_first_day_of_year,
496 ndays_elapsed;
497 calcalcs_cal *c2use;
498
499 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
500 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
501
502 if( calendar->mixed ) {
503
504 /* If we fall in the twilight zone after the old calendar was stopped but before
505 * the new calendar was used, it's an error
506 */
507 if( date_gt( year, month, day, calendar->year_px, calendar->month_px, calendar->day_px ) &&
508 date_lt( year, month, day, calendar->year_x, calendar->month_x, calendar->day_x )) {
509 sprintf( error_message, "ccs_date2doy: date %04d-%02d-%02d is not a valid date in the %s calendar; it falls between the last date the %s calendar was used (%04d-%02d-%02d) and the first date the %s calendar was used (%04d-%02d-%02d)",
510 year, month, day, calendar->name,
511 calendar->early_cal->name,
512 calendar->year_px, calendar->month_px, calendar->day_px,
513 calendar->late_cal->name,
514 calendar->year_x, calendar->month_x, calendar->day_x );
515 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
516 }
517
518 xition_date_first_day_of_year = ((year == calendar->year_x) && (calendar->month_x == 1) && (calendar->day_x == 1));
519 if( (year > calendar->year_x) || xition_date_first_day_of_year )
520 c2use = calendar->late_cal;
521 else if( date_le( year, month, day, calendar->year_px, calendar->month_px, calendar->day_px ))
522 c2use = calendar->early_cal;
523 else
524 {
525 /* Complicated if we are asking for the day of the year during
526 * the transition year and after the transition date. I'm choosing
527 * to define the day numbering during a transition year as
528 * consecutive, which means that the doy for dates
529 * after the transition date equals the doy of the last
530 * day of the earlier calendar plus the number of days
531 * that have elapsed since the transition day.
532 */
533
534 /* Get the doy of the day BEFORE the transition day
535 * in the earlier calendar
536 */
537 if( (ierr = ccs_date2doy( calendar->early_cal, calendar->year_px, calendar->month_px, calendar->day_px, &doy_px )) != 0 )
538 return( ierr );
539
540 /* Get number of days that have elapsed between the transition day
541 * and the requested date
542 */
543 if( (ierr = ccs_date2jday( calendar->late_cal, year, month, day, &jd_args )) != 0 )
544 return( ierr );
545 ndays_elapsed = jd_args - calendar->jday_x + 1; /* if this IS the transition day, ndays_elapsed==1 */
546
547 /* Finally, the day of the year is the day number of the day BEFORE the
548 * transition day plus the number of elapsed days since the
549 * transition day.
550 */
551 *doy = doy_px + ndays_elapsed;
552
553 return(0);
554 }
555 }
556 else
557 c2use = calendar;
558
559 /* Get Julian day number of Jan 1st of the specified year */
560 if( (ierr = c2use->c_date2jday( year, 1, 1, &jd0 )) != 0 )
561 return( ierr );
562
563 /* Get Julian day number of the specified date */
564 if( (ierr = c2use->c_date2jday( year, month, day, &jd1 )) != 0 )
565 return( ierr );
566
567 *doy = jd1 - jd0 + 1; /* Add 1 because numbering starts at 1 */
568
569 return(0);
570 }
571
572 /********************************************************************************************
573 *
574 * ccs_doy2date: given a year and a day number in that year (with counting starting at 1 for
575 * Jan 1st), this returns the month and day of the month that the doy refers to.
576 *
577 * Returns 0 on success, and a negative value on error (for example, a day of the year
578 * that is less than 1 or greater than 366).
579 */
580 int ccs_doy2date( calcalcs_cal *calendar, int year, int doy, int *month, int *day )
581 {
582 int ierr, leap, jd0, jd1, tyear, doy_px, jd_want,
583 xition_date_first_day_of_year, ndays_max;
584 calcalcs_cal *c2use;
585
586 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
587 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
588
589 if( calendar->mixed ) {
590 xition_date_first_day_of_year = ((year == calendar->year_x) && (calendar->month_x == 1) && (calendar->day_x == 1));
591 if( year < calendar->year_x )
592 c2use = calendar->early_cal;
593 else if( (year > calendar->year_x) || xition_date_first_day_of_year )
594 c2use = calendar->late_cal;
595 else
596 {
597 /* Get the doy of the day BEFORE the transition day
598 * in the earlier calendar
599 */
600 if( (ierr = ccs_date2doy( calendar->early_cal, calendar->year_px, calendar->month_px, calendar->day_px, &doy_px )) != 0 )
601 return( ierr );
602
603 /* If our requested doy is before the transition doy, we
604 * can just easily calculate it with the early calendar
605 */
606 if( doy <= doy_px )
607 return( ccs_doy2date( calendar->early_cal, year, doy, month, day ));
608
609 /* Finally calculate the Julian day we want, and convert it to a date */
610 jd_want = calendar->jday_x + (doy - doy_px - 1);
611 if( (ierr = ccs_jday2date( calendar->late_cal, jd_want, &tyear, month, day)) != 0 )
612 return(ierr);
613
614 /* If the year we got from that Julian day is different from the original
615 * year specified, it means we have gone off the end of the transition year,
616 * probably because that year has less days than regular years. In that
617 * event, return an error.
618 */
619 if( tyear != year ) {
620 sprintf( error_message, "year %d in the %s calendar (with transition date %04d-%02d-%02d) has less than %d days, but that was the day-of-year number requested in a call to ccs_doy2date\n",
621 year, calendar->name, calendar->year_x, calendar->month_x, calendar->day_x, doy );
622 return( CALCALCS_ERR_INVALID_DAY_OF_YEAR );
623 }
624
625 return(0);
626 }
627 }
628 else
629 c2use = calendar;
630
631 /* Check to make sure we are not asking for a doy that does not exist,
632 * esp. as regards to the number of days in leap vs. non-leap years
633 */
634 if( (ierr = c2use->c_isleap( year, &leap )) != 0 )
635 return( ierr );
636 if( leap == 1 )
637 ndays_max = c2use->ndays_leap;
638 else
639 ndays_max = c2use->ndays_reg;
640
641 if( (doy < 1) || (doy > ndays_max)) {
642 sprintf( error_message, "routine ccs_doy2date was passed a day-of-year=%d, but for year %d in the %s calendar, the value must be between 1 and %d",
643 doy, year, c2use->name, ndays_max );
644 return( CALCALCS_ERR_INVALID_DAY_OF_YEAR );
645 }
646
647 /* Get Julian day number of Jan 1st of the specified year */
648 if( (ierr = c2use->c_date2jday( year, 1, 1, &jd0 )) != 0 )
649 return( ierr );
650
651 /* Calculate new Julian day */
652 jd1 = jd0 + doy - 1;
653
654 /* Get date for new Julian day */
655 if( (ierr = c2use->c_jday2date( jd1, &tyear, month, day )) != 0 )
656 return( ierr );
657
658 return(0);
659 }
660
661 /********************************************************************************************
662 * ccs_dayssince: Given a Y/M/D date in a specified calendar, and the number of days since
663 * that date, this returns the new Y/M/D date in a (possibly different) calendar.
664 *
665 * Note that specifying "zero" days since, and giving different calendars as the original
666 * and new calendars, essentially converts dates between calendars.
667 *
668 * Returns 0 on success, and a negative value on error.
669 */
670 int ccs_dayssince( calcalcs_cal *calendar_orig, int year_orig, int month_orig, int day_orig,
671 int ndays_since, calcalcs_cal *calendar_new, int *year_new, int *month_new, int *day_new )
672 {
673 int ierr, jd0, jd1;
674 calcalcs_cal *c2use_orig, *c2use_new;
675
676 if( calendar_orig == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
677 if( calendar_orig->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
678
679 if( calendar_new == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
680 if( calendar_new->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
681
682 /* Figure out which calendar of the ORIGINAL calendar to use if it's a mixed calendar
683 */
684 if( calendar_orig->mixed ) {
685 if( date_ge( year_orig, month_orig, day_orig,
686 calendar_orig->year_x, calendar_orig->month_x, calendar_orig->day_x ))
687 c2use_orig = calendar_orig->late_cal;
688 else if( date_le( year_orig, month_orig, day_orig,
689 calendar_orig->year_px, calendar_orig->month_px, calendar_orig->day_px ))
690 c2use_orig = calendar_orig->early_cal;
691 else
692 {
693 sprintf( error_message, "ccs_dayssince: date %04d-%02d-%02d is not a valid date in the %s calendar; it falls between the last date the %s calendar was used (%04d-%02d-%02d) and the first date the %s calendar was used (%04d-%02d-%02d)",
694 year_orig, month_orig, day_orig, calendar_orig->name,
695 calendar_orig->early_cal->name,
696 calendar_orig->year_px, calendar_orig->month_px, calendar_orig->day_px,
697 calendar_orig->late_cal->name,
698 calendar_orig->year_x, calendar_orig->month_x, calendar_orig->day_x );
699 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
700 }
701 }
702 else
703 c2use_orig = calendar_orig;
704
705 /* Get Julian day in the original calendar and date combo */
706 if( (ierr = c2use_orig->c_date2jday( year_orig, month_orig, day_orig, &jd0 )) != 0 )
707 return(ierr);
708
709 /* Get new Julian day */
710 jd1 = jd0 + ndays_since;
711
712 if( calendar_new->mixed ) {
713 /* Figure out which calendar of the NEW calendar to use if it's a mixed calendar.
714 */
715 if( jd1 >= calendar_new->jday_x )
716 c2use_new = calendar_new->late_cal;
717 else
718 c2use_new = calendar_new->early_cal;
719 }
720 else
721 c2use_new = calendar_new;
722
723 /* Convert the new Julian day to a date in the new calendar */
724 if( (ierr = c2use_new->c_jday2date( jd1, year_new, month_new, day_new )) != 0 )
725 return( ierr );
726
727 return(0);
728 }
729
730 /********************************************************************************************/
731 static void ccs_gxd_add_country( char *code, char *longname, int year, int month, int day )
732 {
733 if( ccs_n_country_codes >= CCS_MAX_N_COUNTRY_CODES ) {
734 fprintf( stderr, "Error, the calcalcs library is attempting to store more country codes than is possible; max is %d\n",
735 CCS_MAX_N_COUNTRY_CODES );
736 fprintf( stderr, "To fix, recompile with a larger number for CCS_MAX_N_COUNTRY_CODES\n" );
737 exit( -1 );
738 }
739
740 ccs_xition_dates[ccs_n_country_codes] = (ccs_country_code *)malloc( sizeof( ccs_country_code ));
741 if( ccs_xition_dates[ccs_n_country_codes] == NULL ) {
742 fprintf( stderr, "calcalcs routine ccs_gxd_add_country: Error trying to allocate space for country code %s\n",
743 code );
744 exit(-1);
745 }
746
747 ccs_xition_dates[ccs_n_country_codes]->code = (char *)malloc( sizeof(char) * (strlen(code)+1) );
748 if( ccs_xition_dates[ccs_n_country_codes]->code == NULL ) {
749 fprintf( stderr, "calcalcs routine ccs_gxd_add_country: Error trying to allocate space for country code named %s\n",
750 code );
751 exit(-1);
752 }
753 strcpy( ccs_xition_dates[ccs_n_country_codes]->code, code );
754
755 ccs_xition_dates[ccs_n_country_codes]->longname = (char *)malloc( sizeof(char) * (strlen(longname)+1) );
756 if( ccs_xition_dates[ccs_n_country_codes]->longname == NULL ) {
757 fprintf( stderr, "calcalcs routine ccs_gxd_add_country: Error trying to allocate space for country code long name %s\n",
758 longname );
759 exit(-1);
760 }
761 strcpy( ccs_xition_dates[ccs_n_country_codes]->longname, longname );
762
763 ccs_xition_dates[ccs_n_country_codes]->year = year;
764 ccs_xition_dates[ccs_n_country_codes]->month = month;
765 ccs_xition_dates[ccs_n_country_codes]->day = day;
766
767 ccs_n_country_codes++;
768 }
769
770 /********************************************************************************************/
771 static void ccs_init_country_database()
772 {
773 ccs_gxd_add_country( "AK", "Alaska", 1867, 10, 18 );
774 ccs_gxd_add_country( "AL", "Albania", 1912, 12, 1 );
775 ccs_gxd_add_country( "AT", "Austria", 1583, 10, 16 );
776 ccs_gxd_add_country( "BE", "Belgium", 1582, 12, 25 );
777 ccs_gxd_add_country( "BG", "Bulgaria", 1916, 4, 1 );
778 ccs_gxd_add_country( "CN", "China", 1929, 1, 1 );
779 ccs_gxd_add_country( "CZ", "Czechoslovakia", 1584, 1, 17 );
780 ccs_gxd_add_country( "DK", "Denmark", 1700, 3, 1 );
781 ccs_gxd_add_country( "NO", "Norway", 1700, 3, 1 );
782 ccs_gxd_add_country( "EG", "Egypt", 1875, 1, 1 );
783 ccs_gxd_add_country( "EE", "Estonia", 1918, 1, 1 );
784 ccs_gxd_add_country( "FI", "Finland", 1753, 3, 1 );
785 ccs_gxd_add_country( "FR", "France", 1582, 12, 20 );
786 ccs_gxd_add_country( "DE", "Germany", 1583, 11, 22 );
787 ccs_gxd_add_country( "UK", "United Kingdom", 1752, 9, 14 );
788 ccs_gxd_add_country( "GR", "Greece", 1924, 3, 23 );
789 ccs_gxd_add_country( "HU", "Hungary", 1587, 11, 1 );
790 ccs_gxd_add_country( "IT", "Italy", 1582, 10, 15 );
791 ccs_gxd_add_country( "JP", "Japan", 1918, 1, 1 );
792 ccs_gxd_add_country( "LV", "Latvia", 1915, 1, 1 );
793 ccs_gxd_add_country( "LT", "Lithuania", 1915, 1, 1 );
794 ccs_gxd_add_country( "LU", "Luxemburg", 1582, 12, 15 );
795 ccs_gxd_add_country( "NL", "Netherlands", 1582, 10, 15 );
796 ccs_gxd_add_country( "PL", "Poland", 1582, 10, 15 );
797 ccs_gxd_add_country( "PT", "Portugal", 1582, 10, 15 );
798 ccs_gxd_add_country( "RO", "Romania", 1919, 4, 14 );
799 ccs_gxd_add_country( "ES", "Spain", 1582, 10, 15 );
800 ccs_gxd_add_country( "SE", "Sweden", 1753, 3, 1 );
801 ccs_gxd_add_country( "CH", "Switzerland", 1584, 1, 22 );
802 ccs_gxd_add_country( "TR", "Turkey", 1927, 1, 1 );
803 ccs_gxd_add_country( "YU", "Yugoslavia", 1919, 1, 1 );
804 ccs_gxd_add_country( "US", "United States", 1752, 9, 14 );
805 ccs_gxd_add_country( "SU", "Soviet Union", 1918, 2, 1 );
806 ccs_gxd_add_country( "RU", "Russia", 1918, 2, 1 );
807
808 have_initted_country_codes = 1;
809 }
810
811 /********************************************************************************************/
812 int ccs_get_xition_date( const char *country_code, int *year, int *month, int *day )
813 {
814 int i;
815
816 if( ! have_initted_country_codes )
817 ccs_init_country_database();
818
819 if( strcmp( country_code, "??" ) == 0 ) {
820 ccs_dump_xition_dates();
821 *year = 0;
822 *month = 0;
823 *day = 0;
824 return(0);
825 }
826
827 /* Find the passed country code in our list */
828 for( i=0; i<ccs_n_country_codes; i++ ) {
829 if( strcmp( country_code, ccs_xition_dates[i]->code ) == 0 ) {
830 *year = ccs_xition_dates[i]->year;
831 *month = ccs_xition_dates[i]->month;
832 *day = ccs_xition_dates[i]->day;
833 return(0);
834 }
835 }
836
837 /* Maybe they passed a longname? */
838 for( i=0; i<ccs_n_country_codes; i++ ) {
839 if( strcmp( country_code, ccs_xition_dates[i]->longname ) == 0 ) {
840 *year = ccs_xition_dates[i]->year;
841 *month = ccs_xition_dates[i]->month;
842 *day = ccs_xition_dates[i]->day;
843 return(0);
844 }
845 }
846
847 sprintf( error_message, "ccs_get_xition_date: unknown calendar country/region code: \"%s\". Known codes: ", country_code );
848 for( i=0; i<ccs_n_country_codes; i++ ) {
849 if( (strlen(error_message) + strlen(ccs_xition_dates[i]->code) + strlen(ccs_xition_dates[i]->longname) + 10) < CCS_ERROR_MESSAGE_LEN ) {
850 strcat( error_message, ccs_xition_dates[i]->code );
851 strcat( error_message, " (" );
852 strcat( error_message, ccs_xition_dates[i]->longname );
853 strcat( error_message, ") " );
854 }
855 }
856
857 return(CALCALCS_ERR_UNKNOWN_COUNTRY_CODE);
858 }
859
860 /********************************************************************************************/
861 static void ccs_dump_xition_dates( void )
862 {
863 int i;
864
865 printf( "Calcalcs library known country codes:\n" );
866 for( i=0; i<ccs_n_country_codes; i++ ) {
867 printf( "Code: %s Transition date: %04d-%02d-%02d Country/Region: %s\n",
868 ccs_xition_dates[i]->code,
869 ccs_xition_dates[i]->year,
870 ccs_xition_dates[i]->month,
871 ccs_xition_dates[i]->day,
872 ccs_xition_dates[i]->longname );
873 if( i%3 == 2 )
874 printf( "\n" );
875 }
876 }
877
878 /********************************************************************************************/
879 int ccs_set_xition_date( calcalcs_cal *calendar, int year, int month, int day )
880 {
881 int ierr, dpm;
882
883 if( calendar == NULL ) return(CALCALCS_ERR_NULL_CALENDAR);
884 if( calendar->sig != CCS_VALID_SIG ) return(CALCALCS_ERR_INVALID_CALENDAR);
885
886 if( calendar->mixed == 0 )
887 return( CALCALCS_ERR_NOT_A_MIXED_CALENDAR );
888
889 /* Check to make sure the specified date is a valid date in the
890 * LATE calendar (since the transition date is the first date
891 * that the late calendar is used)
892 */
893 if( (ierr = ccs_dpm( calendar->late_cal, year, month, &dpm )) != 0 )
894 return( ierr );
895
896 if( (month < 1) || (month > 12) || (day < 1) || (day > dpm)) {
897 fprintf( stderr, "Error in routine set_cal_xition_date: trying to set the calendar Julian/Gregorian transition date to an illegal date: %04d-%02d-%02d\n", year, month, day );
898 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
899 }
900
901 calendar->year_x = year;
902 calendar->month_x = month;
903 calendar->day_x = day;
904
905 if( (ierr = set_xition_extra_info( calendar )) != 0 )
906 return( ierr );
907
908 return(0);
909 }
910
911 /********************************************************************************************/
912 char *ccs_err_str( int errno )
913 {
914 if( errno == 0 )
915 sprintf( error_message, "no error from calcalcs routines version %f", CALCALCS_VERSION_NUMBER );
916
917 else if( errno == CALCALCS_ERR_NULL_CALENDAR )
918 sprintf( error_message, "a NULL calendar was passed to the calcalcs routine" );
919
920 else if( errno == CALCALCS_ERR_INVALID_CALENDAR )
921 sprintf( error_message, "an invalid, malformed, previously-freed, or uninitialized calendar was passed to the calcalcs routine" );
922
923 return( error_message );
924 }
925
926 /*==================================================================================================
927 * Returns 0 on success, <0 on error.
928 */
929 int c_isleap_julian( int year, int *leap )
930 {
931 int tyear;
932
933 if( year == 0 ) {
934 sprintf( error_message, "the Julian calendar has no year 0" );
935 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
936 }
937
938 /* Because there is no year 0 in the Julian calendar, years -1, -5, -9, etc
939 * are leap years.
940 */
941 if( year < 0 )
942 tyear = year + 1;
943 else
944 tyear = year;
945
946 *leap = ((tyear % 4) == 0);
947
948 return(0);
949 }
950
951 /*==================================================================================================
952 * Returns 0 on success, <0 on error.
953 */
954 int c_isleap_gregorian( int year, int *leap )
955 {
956 int tyear;
957
958 if( year == 0 ) {
959 sprintf( error_message, "the Gregorian calendar has no year 0. Use the \"Gregorian_y0\" calendar if you want to include year 0." );
960 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
961 }
962
963 /* Because there is no year 0 in the gregorian calendar, years -1, -5, -9, etc
964 * are leap years.
965 */
966 if( year < 0 )
967 tyear = year + 1;
968 else
969 tyear = year;
970
971 *leap = (((tyear % 4) == 0) && ((tyear % 100) != 0)) || ((tyear % 400) == 0);
972
973 return(0);
974 }
975
976 /*==================================================================================================
977 * Returns 0 on success, <0 on error.
978 */
979 int c_isleap_gregorian_y0( int year, int *leap )
980 {
981 *leap = (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0);
982
983 return(0);
984 }
985
986 /*==================================================================================================
987 * Given a Y/M/D in the Gregorian calendar, this computes the (true) Julian day number of the
988 * specified date. Julian days are counted starting at 0 on 1 Jan 4713 BC using a proleptic Julian
989 * calendar. The algorithm is based on the "counting" algorithm in the C++ code I obtained from
990 * Edward M. Reingold's web site at http://emr.cs.uiuc.edu/~reingold/calendar.C.
991 * In that file, the work is declared to be in the public domain. I modified it by
992 * extending it to negative years (years BC) in addition to positive years, and to use
993 * actual Julian Days as the counter. Otherwise, the spirit of the algorithm is similar.
994 *
995 * Returns 0 on success, <0 on error.
996 */
997 int c_date2jday_gregorian( int year, int month, int day, int *jday )
998 {
999 int m, leap, *dpm2use, err;
1000
1001 if( (month < 1) || (month > 12) || (day < 1) || (day > 31)) {
1002 sprintf( error_message, "date %04d-%02d-%02d does not exist in the Gregorian calendar",
1003 year, month, day );
1004 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1005 }
1006
1007 if( year == 0 ) {
1008 sprintf( error_message, "year 0 does not exist in the Gregorian calendar. Use the \"Gregorian_y0\" calendar if you want to include year 0" );
1009 return( CALCALCS_ERR_NO_YEAR_ZERO );
1010 }
1011
1012 /* Limit ourselves to positive Julian Days */
1013 if( year < -4714 ) {
1014 sprintf( error_message, "year %d is out of range of the Gregorian calendar routines; must have year >= -4714", year );
1015 return( CALCALCS_ERR_OUT_OF_RANGE );
1016 }
1017
1018 /* Following is necessary because Gregorian calendar skips year 0, so the
1019 * offst for negative years is different than offset for positive years
1020 */
1021 if( year < 0 )
1022 year += 4801;
1023 else
1024 year += 4800;
1025
1026 if( (err = c_isleap_gregorian( year, &leap )) != 0 )
1027 return( err );
1028
1029 if( leap )
1030 dpm2use = dpm_leap_idx1;
1031 else
1032 dpm2use = dpm_idx1;
1033
1034 *jday = day;
1035 for( m=month-1; m>0; m-- )
1036 *jday += dpm2use[m];
1037
1038 *jday += 365*(year-1) + (year-1)/4 - (year-1)/100 + (year-1)/400;
1039
1040 /* Ajust to "true" Julian days. This constant is how many days difference there is
1041 * between the Julian Day origin date of 4713 BC and our offset date of 4800 BC
1042 */
1043 *jday -= 31739;
1044
1045 return(0);
1046 }
1047
1048 /*==================================================================================================
1049 * Given a Y/M/D in the Gregorian calendar, this computes the (true) Julian day number of the
1050 * specified date. Julian days are counted starting at 0 on 1 Jan 4713 BC using a proleptic Julian
1051 * calendar. The algorithm is based on the "counting" algorithm in the C++ code I obtained from
1052 * Edward M. Reingold's web site at http://emr.cs.uiuc.edu/~reingold/calendar.C.
1053 * In that file, the work is declared to be in the public domain. I modified it by
1054 * extending it to negative years (years BC) in addition to positive years, and to use
1055 * actual Julian Days as the counter. Otherwise, the spirit of the algorithm is similar.
1056 *
1057 * Returns 0 on success, <0 on error.
1058 */
1059 int c_date2jday_gregorian_y0( int year, int month, int day, int *jday )
1060 {
1061 int m, leap, *dpm2use, err;
1062
1063 if( (month < 1) || (month > 12) || (day < 1) || (day > 31)) {
1064 sprintf( error_message, "date %04d-%02d-%02d does not exist in the Gregorian calendar",
1065 year, month, day );
1066 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1067 }
1068
1069 /* Limit ourselves to positive Julian Days */
1070 if( year < -4714 ) {
1071 sprintf( error_message, "year %d is out of range of the Gregorian calendar routines; must have year >= -4714", year );
1072 return( CALCALCS_ERR_OUT_OF_RANGE );
1073 }
1074
1075 year += 4800;
1076
1077 if( (err = c_isleap_gregorian_y0( year, &leap )) != 0 )
1078 return( err );
1079
1080 if( leap )
1081 dpm2use = dpm_leap_idx1;
1082 else
1083 dpm2use = dpm_idx1;
1084
1085 *jday = day;
1086 for( m=month-1; m>0; m-- )
1087 *jday += dpm2use[m];
1088
1089 *jday += 365*(year-1) + (year-1)/4 - (year-1)/100 + (year-1)/400;
1090
1091 /* Ajust to "true" Julian days. This constant is how many days difference there is
1092 * between the Julian Day origin date of 4713 BC and our offset date of 4800 BC
1093 */
1094 *jday -= 31739;
1095
1096 return(0);
1097 }
1098
1099 /*==========================================================================================
1100 * Given a (true) Julian Day, this converts to a date in the Gregorian calendar.
1101 * Technically, in the proleptic Gregorian calendar, since this works for dates
1102 * back to 4713 BC. Again based on the same public domain code from Edward Reingold's
1103 * web site as the date2jday routine, extended by me to apply to negative years (years BC).
1104 *
1105 * Returns 0 on success, <0 on error.
1106 */
1107 int c_jday2date_gregorian( int jday, int *year, int *month, int *day )
1108 {
1109 int tjday, leap, *dpm2use, ierr, yp1;
1110
1111 /* Make first estimate for year. We subtract 4714 because Julian Day number
1112 * 0 occurs in year 4714 BC in the Gregorian calendar (recall that it occurs
1113 * in year 4713 BC in the JULIAN calendar
1114 */
1115 *year = jday/366 - 4714;
1116
1117 /* Advance years until we find the right one */
1118 yp1 = *year + 1;
1119 if( yp1 == 0 ) yp1 = 1; /* no year 0 in the Gregorian calendar */
1120 if( (ierr = c_date2jday_gregorian( yp1, 1, 1, &tjday )) != 0 )
1121 return( ierr );
1122 while( jday >= tjday ) {
1123 (*year)++;
1124 if( *year == 0 )
1125 *year = 1; /* no year 0 in the Gregorian calendar */
1126 yp1 = *year + 1;
1127 if( yp1 == 0 ) yp1 = 1; /* no year 0 in the Gregorian calendar */
1128 if( (ierr = c_date2jday_gregorian( yp1, 1, 1, &tjday )) != 0 )
1129 return( ierr );
1130 }
1131
1132 if( (ierr = c_isleap_gregorian( *year, &leap )) != 0 )
1133 return( ierr );
1134 if( leap )
1135 dpm2use = dpm_leap_idx1;
1136 else
1137 dpm2use = dpm_idx1;
1138
1139 *month = 1;
1140 if( (ierr = c_date2jday_gregorian( *year, *month, dpm2use[*month], &tjday)) != 0)
1141 return( ierr );
1142 while( jday > tjday ) {
1143 (*month)++;
1144 if( (ierr = c_date2jday_gregorian( *year, *month, dpm2use[*month], &tjday) != 0))
1145 return( ierr );
1146 }
1147
1148 if( (ierr = c_date2jday_gregorian( *year, *month, 1, &tjday)) != 0 )
1149 return( ierr );
1150 *day = jday - tjday + 1;
1151
1152 return(0);
1153 }
1154
1155 /*==========================================================================================
1156 * Given a (true) Julian Day, this converts to a date in the Gregorian calendar.
1157 * Technically, in the proleptic Gregorian calendar, since this works for dates
1158 * back to 4713 BC. Again based on the same public domain code from Edward Reingold's
1159 * web site as the date2jday routine, extended by me to apply to negative years (years BC).
1160 *
1161 * Returns 0 on success, <0 on error.
1162 */
1163 int c_jday2date_gregorian_y0( int jday, int *year, int *month, int *day )
1164 {
1165 int tjday, leap, *dpm2use, ierr, yp1;
1166
1167 /* Make first estimate for year. We subtract 4714 because Julian Day number
1168 * 0 occurs in year 4714 BC in the Gregorian calendar (recall that it occurs
1169 * in year 4713 BC in the JULIAN calendar
1170 */
1171 *year = jday/366 - 4715;
1172
1173 /* Advance years until we find the right one */
1174 yp1 = *year + 1;
1175 if( (ierr = c_date2jday_gregorian_y0( yp1, 1, 1, &tjday )) != 0 )
1176 return( ierr );
1177 while( jday >= tjday ) {
1178 (*year)++;
1179 yp1 = *year + 1;
1180 if( (ierr = c_date2jday_gregorian_y0( yp1, 1, 1, &tjday )) != 0 )
1181 return( ierr );
1182 }
1183
1184 if( (ierr = c_isleap_gregorian_y0( *year, &leap )) != 0 )
1185 return( ierr );
1186 if( leap )
1187 dpm2use = dpm_leap_idx1;
1188 else
1189 dpm2use = dpm_idx1;
1190
1191 *month = 1;
1192 if( (ierr = c_date2jday_gregorian_y0( *year, *month, dpm2use[*month], &tjday)) != 0)
1193 return( ierr );
1194 while( jday > tjday ) {
1195 (*month)++;
1196 if( (ierr = c_date2jday_gregorian_y0( *year, *month, dpm2use[*month], &tjday) != 0))
1197 return( ierr );
1198 }
1199
1200 if( (ierr = c_date2jday_gregorian_y0( *year, *month, 1, &tjday)) != 0 )
1201 return( ierr );
1202 *day = jday - tjday + 1;
1203
1204 return(0);
1205 }
1206
1207 /*==================================================================================================
1208 * Given a Y/M/D in the Julian calendar, this computes the (true) Julian day number of the
1209 * specified date. Julian days are counted starting at 0 on 1 Jan 4713 BC using a proleptic Julian
1210 * calendar. The algorithm is based on the "counting" algorithm in the C++ code I obtained from
1211 * Edward M. Reingold's web site at http://emr.cs.uiuc.edu/~reingold/calendar.C.
1212 * In that file, the work is declared to be in the public domain. I modified it by
1213 * extending it to negative years (years BC) in addition to positive years, and to use
1214 * actual Julian Days as the counter. Otherwise, the spirit of the algorithm is similar.
1215 *
1216 * Returns 0 on success, <0 on error.
1217 */
1218 int c_date2jday_julian( int year, int month, int day, int *jday )
1219 {
1220 int m, leap, *dpm2use, err;
1221
1222 if( (month < 1) || (month > 12) || (day < 1) || (day > 31)) {
1223 sprintf( error_message, "date %04d-%02d-%02d does not exist in the Julian calendar",
1224 year, month, day );
1225 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1226 }
1227
1228 if( year == 0 ) {
1229 sprintf( error_message, "year 0 does not exist in the Julian calendar" );
1230 return( CALCALCS_ERR_NO_YEAR_ZERO );
1231 }
1232
1233 /* Limit ourselves to positive Julian Days */
1234 if( year < -4713 ) {
1235 sprintf( error_message, "year %d is out of range of the Julian calendar routines; must have year >= -4713", year );
1236 return( CALCALCS_ERR_OUT_OF_RANGE );
1237 }
1238
1239 /* Following is necessary because Julian calendar skips year 0, so the
1240 * offst for negative years is different than offset for positive years
1241 */
1242 if( year < 0 )
1243 year += 4801;
1244 else
1245 year += 4800;
1246
1247 if( (err = c_isleap_julian( year, &leap )) != 0 )
1248 return( err );
1249
1250 if( leap )
1251 dpm2use = dpm_leap_idx1;
1252 else
1253 dpm2use = dpm_idx1;
1254
1255 *jday = day;
1256 for( m=month-1; m>0; m-- )
1257 *jday += dpm2use[m];
1258
1259 *jday += 365*(year-1) + (year-1)/4;
1260
1261 /* Ajust to "true" Julian days. This constant is how many days difference there is
1262 * between the Julian Day origin date of 4713 BC and our offset date of 4800 BC
1263 */
1264 *jday -= 31777;
1265
1266 return(0);
1267 }
1268
1269 /*==========================================================================================
1270 * Given a (true) Julian Day, this converts to a date in the Julian calendar.
1271 * Technically, in the proleptic Julian calendar, since this works for dates
1272 * back to 4713 BC. Again based on the same public domain code from Edward Reingold's
1273 * web site as the date2jday routine, extended by me to apply to negative years (years BC).
1274 *
1275 * Returns 0 on success, <0 on error.
1276 */
1277 int c_jday2date_julian( int jday, int *year, int *month, int *day )
1278 {
1279 int tjday, leap, *dpm2use, ierr, yp1;
1280
1281 /* Make first estimate for year. We subtract 4713 because Julian Day number
1282 * 0 occurs in year 4713 BC in the Julian calendar
1283 */
1284 *year = jday/366 - 4713;
1285
1286 /* Advance years until we find the right one */
1287 yp1 = *year + 1;
1288 if( yp1 == 0 ) yp1 = 1; /* no year 0 in the Julian calendar */
1289 if( (ierr = c_date2jday_julian( yp1, 1, 1, &tjday )) != 0 )
1290 return( ierr );
1291 while( jday >= tjday ) {
1292 (*year)++;
1293 if( *year == 0 )
1294 *year = 1; /* no year 0 in the Julian calendar */
1295 yp1 = *year + 1;
1296 if( yp1 == 0 ) yp1 = 1; /* no year 0 in the Julian calendar */
1297 if( (ierr = c_date2jday_julian( yp1, 1, 1, &tjday )) != 0 )
1298 return( ierr );
1299 }
1300
1301 if( (ierr = c_isleap_julian( *year, &leap )) != 0 )
1302 return( ierr );
1303 if( leap )
1304 dpm2use = dpm_leap_idx1;
1305 else
1306 dpm2use = dpm_idx1;
1307
1308 *month = 1;
1309 if( (ierr = c_date2jday_julian( *year, *month, dpm2use[*month], &tjday)) != 0)
1310 return( ierr );
1311 while( jday > tjday ) {
1312 (*month)++;
1313 if( (ierr = c_date2jday_julian( *year, *month, dpm2use[*month], &tjday) != 0))
1314 return( ierr );
1315 }
1316
1317 if( (ierr = c_date2jday_julian( *year, *month, 1, &tjday)) != 0 )
1318 return( ierr );
1319 *day = jday - tjday + 1;
1320
1321 return(0);
1322 }
1323
1324 /*==================================================================================================
1325 * Free the storage associated with a calendar
1326 */
1327 void ccs_free_calendar( calcalcs_cal *cc )
1328 {
1329 if( cc == NULL )
1330 return;
1331
1332 if( cc->mixed == 1 ) {
1333 ccs_free_calendar( cc->early_cal );
1334 ccs_free_calendar( cc->late_cal );
1335 }
1336
1337 if( cc->sig != CCS_VALID_SIG ) {
1338 fprintf( stderr, "Warning: invalid calendar passed to routine ccs_free_calendar!\n" );
1339 return;
1340 }
1341
1342 cc->sig = 0;
1343
1344 if( cc->name != NULL )
1345 free( cc->name );
1346
1347 free( cc );
1348 }
1349
1350 /**********************************************************************************************/
1351 static int date_gt( int year, int month, int day, int y2, int m2, int d2 )
1352 {
1353 return( ! date_le( year, month, day, y2, m2, d2 ));
1354 }
1355
1356 /**********************************************************************************************/
1357 static int date_lt( int year, int month, int day, int y2, int m2, int d2 )
1358 {
1359 return( ! date_ge( year, month, day, y2, m2, d2 ));
1360 }
1361
1362 /**********************************************************************************************/
1363 static int date_le( int year, int month, int day, int y2, int m2, int d2 ) {
1364
1365 if( year > y2 )
1366 return(0);
1367
1368 if( year < y2 )
1369 return(1);
1370
1371 /* If get here, must be same year */
1372 if( month > m2 )
1373 return(0);
1374 if( month < m2)
1375 return(1);
1376
1377 /* If get here, must be same month */
1378 if( day > d2 )
1379 return(0);
1380
1381 return(1);
1382 }
1383
1384 /**********************************************************************************************/
1385 static int date_ge( int year, int month, int day, int y2, int m2, int d2 ) {
1386
1387 if( year < y2 )
1388 return(0);
1389
1390 if( year > y2 )
1391 return(1);
1392
1393 /* If get here, must be same year */
1394 if( month < m2 )
1395 return(0);
1396 if( month > m2)
1397 return(1);
1398
1399 /* If get here, must be same month */
1400 if( day < d2 )
1401 return(0);
1402
1403 return(1);
1404 }
1405
1406 /**********************************************************************************************/
1407 int c_isleap_never( int year, int *leap )
1408 {
1409 *leap = 0;
1410 return( 0 );
1411 }
1412
1413 /**********************************************************************************************/
1414 int c_date2jday_360_day( int year, int month, int day, int *jday )
1415 {
1416 int spm;
1417
1418 if( day > 30) {
1419 sprintf( error_message, "date %04d-%02d-%02d does not exist in the 360_day calendar",
1420 year, month, day );
1421 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1422 }
1423
1424 spm = (month-1)*30; /* sum of days in the previous months */
1425
1426 *jday = year*360 + spm + (day-1);
1427
1428 return( 0 );
1429 }
1430
1431 /**********************************************************************************************/
1432 int c_date2jday_noleap( int year, int month, int day, int *jday )
1433 {
1434 if( (month == 2) && (day == 29)) {
1435 sprintf( error_message, "date %04d-%02d-%02d does not exist in the noleap calendar",
1436 year, month, day );
1437 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1438 }
1439
1440 *jday = year*365 + spm_idx1[month] + (day-1);
1441
1442 return( 0 );
1443 }
1444
1445 /**********************************************************************************************/
1446 int c_jday2date_360_day( int jday, int *year, int *month, int *day )
1447 {
1448 int nextra, yr_offset, doy;
1449
1450 yr_offset = 0;
1451 if( jday < 0 ) {
1452 yr_offset = (-jday)/360+1;
1453 jday += 360*yr_offset;
1454 }
1455
1456 *year = jday/360;
1457
1458 nextra = jday - *year*360;
1459 doy = nextra + 1; /* Julday numbering starts at 0, doy starts at 1 */
1460 *month = nextra/30 + 1;
1461 *day = doy - (*month-1)*30;
1462
1463 *year -= yr_offset;
1464
1465 return(0);
1466 }
1467
1468 /**********************************************************************************************/
1469 int c_jday2date_noleap( int jday, int *year, int *month, int *day )
1470 {
1471 int nextra, yr_offset, doy;
1472
1473 yr_offset = 0;
1474 if( jday < 0 ) {
1475 yr_offset = (-jday)/365+1;
1476 jday += 365*yr_offset;
1477 }
1478
1479 *year = jday/365;
1480
1481 nextra = jday - *year*365;
1482 doy = nextra + 1; /* Julday numbering starts at 0, doy starts at 1 */
1483 *month = 1;
1484 while( doy > spm_idx1[*month + 1] )
1485 *month += 1;
1486
1487 *day = doy - spm_idx1[*month];
1488
1489 *year -= yr_offset;
1490
1491 return(0);
1492 }
1493
1494 /**********************************************************************************************/
1495 int c_dpm_gregorian( int year, int month, int *dpm )
1496 {
1497 int ierr, leap;
1498
1499 if( (month<1) || (month>12)) {
1500 sprintf( error_message, "month %d does not exist in the Gregorian calendar", month );
1501 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1502 }
1503
1504 if( (ierr = c_isleap_gregorian( year, &leap )) != 0 )
1505 return( ierr );
1506
1507 if( leap )
1508 *dpm = dpm_leap_idx1[month];
1509 else
1510 *dpm = dpm_idx1[month];
1511
1512 return(0);
1513 }
1514
1515
1516 /**********************************************************************************************/
1517 int c_dpm_gregorian_y0( int year, int month, int *dpm )
1518 {
1519 int ierr, leap;
1520
1521 if( (month<1) || (month>12)) {
1522 sprintf( error_message, "month %d does not exist in the Gregorian calendar", month );
1523 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1524 }
1525
1526 if( (ierr = c_isleap_gregorian_y0( year, &leap )) != 0 )
1527 return( ierr );
1528
1529 if( leap )
1530 *dpm = dpm_leap_idx1[month];
1531 else
1532 *dpm = dpm_idx1[month];
1533
1534 return(0);
1535 }
1536
1537 /**********************************************************************************************/
1538 int c_dpm_julian( int year, int month, int *dpm )
1539 {
1540 int ierr, leap;
1541
1542 if( (month<1) || (month>12)) {
1543 sprintf( error_message, "month %d does not exist in the Julian calendar", month );
1544 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1545 }
1546
1547 if( (ierr = c_isleap_julian( year, &leap )) != 0 )
1548 return( ierr );
1549
1550 if( leap )
1551 *dpm = dpm_leap_idx1[month];
1552 else
1553 *dpm = dpm_idx1[month];
1554
1555 return(0);
1556 }
1557
1558 /**********************************************************************************************/
1559 int c_dpm_360_day( int year, int month, int *dpm )
1560 {
1561 if( (month<1) || (month>12)) {
1562 sprintf( error_message, "month %d does not exist in the 360_day calendar", month );
1563 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1564 }
1565
1566 *dpm = 30;
1567
1568 return(0);
1569 }
1570
1571 /**********************************************************************************************/
1572 int c_dpm_noleap( int year, int month, int *dpm )
1573 {
1574 if( (month<1) || (month>12)) {
1575 sprintf( error_message, "month %d does not exist in the noleap calendar", month );
1576 return( CALCALCS_ERR_DATE_NOT_IN_CALENDAR );
1577 }
1578
1579 *dpm = dpm_idx1[month];
1580
1581 return(0);
1582 }
1583
1584 /*******************************************************************************************/
1585 static int set_xition_extra_info( calcalcs_cal *cal )
1586 {
1587 int ierr;
1588
1589 /* This is the Julian Day of the transition date */
1590 ierr = ccs_date2jday( cal->late_cal, cal->year_x, cal->month_x, cal->day_x, &(cal->jday_x) );
1591 if( ierr != 0 ) {
1592 sprintf( error_message, "Failed to turn the mixed calendar transition day %04d-%02d-%02d in the %s calendar into a Julian day!\n",
1593 cal->year_x, cal->month_x, cal->day_x, cal->name );
1594 return(ierr);
1595 }
1596
1597 /* This is the date of the day BEFORE the transition day,
1598 * i.e., the last day that the early calendar was used
1599 */
1600 ierr = ccs_jday2date( cal->early_cal, (cal->jday_x-1), &(cal->year_px), &(cal->month_px), &(cal->day_px));
1601 if( ierr != 0 ) {
1602 sprintf( error_message, "Failed to turn the day before the mixed calendar transition day into a date! %s\n",
1603 ccs_err_str(ierr) );
1604 return(ierr);
1605 }
1606
1607 return(0);
1608 }
1609