Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
utCalendar2_cal.c
Go to the documentation of this file.
1/*
2 The CalCalcs routines, a set of C-language routines to perform
3 calendar calculations.
4
5 Version 1.01, released 9 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 <unistd.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include "udunits2.h"
29#include "calcalcs.h"
30
31#include "utCalendar2_cal.h"
32
33static int have_initted=0;
34static calcalcs_cal *cal_std=NULL;
35
36/* Following controls the rounding precision of the routines. I.e., if we end up with a value
37 such as 59.99999999 seconds, we are going to round it to 60 seconds. The value is given
38 in seconds, so, for example, 1.e-3 means round up to 1 second if the value is 0.999 seconds or greater,
39 and 1.e-6 means round up to 1 second if the value is 0.999999 seconds or greater.
40*/
41static const double sec_rounding_value = 1.e-8;
42
43
44/* Internal to this file only */
45static void initialize( ut_system *units_system );
46static void get_origin( ut_unit *dataunits, int *y0, int *mon0, int *d0, int *h0, int *min0, double *s0 );
47static cv_converter *get_day_to_user_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 );
48static cv_converter *get_user_to_day_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 );
49static calcalcs_cal *getcal( const char *name );
50static void unknown_cal_emit_warning( const char *calendar_name );
51
52/* Stores previuosly initialized calendars and their names */
53static const int maxcals_known=100;
54static int ncals_known=0;
55static calcalcs_cal **known_cal; /* ptr to array of calcals_cal ptrs */
56static char **known_cal_name;
57
58/* Stores previously emitted "unknown calendar" warnings */
59#define UTC2_MAX_UNKCAL_WARNS 1000
61static int n_unkcal=0;
62
63/*========================================================================================
64 * Turns the passed value into a Y/M/D date
65 */
66int utCalendar2_cal( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour,
67 int *minute, double *second, const char *calendar_name )
68{
69
70 int jdnew, ndinc, ierr, iorig, iround;
71 double fdays, extra_seconds, tot_extra_seconds;
72 int ndays;
73 calcalcs_cal *cal2use;
74
75 /* Following vars are saved between invocations and reused if the
76 * passed units are the same as last time.
77 */
78 static ut_unit *prev_units=NULL;
79 static cv_converter *conv_user_units_to_days=NULL;
80 static int y0, mon0, d0, h0, min0, jday0;
81 static double s0, extra_seconds0;
82 static char *prev_calendar=NULL;
83
84 if( dataunits == NULL ) {
85 fprintf( stderr, "Error, utCalendar2 passed a NULL units\n" );
86 return( UT_ENOINIT );
87 }
88
89 if( have_initted == 0 )
90 initialize( ut_get_system(dataunits) );
91
92 /* Get the calendar we will be using, based on the passed name
93 */
94 cal2use = getcal( calendar_name );
95 if( cal2use == NULL ) {
96 unknown_cal_emit_warning( calendar_name );
97 cal2use = getcal( "Standard" );
98 }
99
100 /* See if we are being passed the same units and calendar as last time. If so,
101 * we can optimize by not recomputing all this junk
102 */
103 if( (prev_units != NULL) && (prev_calendar != NULL)
104 && (strcmp(prev_calendar,cal2use->name)==0)
105 && (ut_compare( prev_units, dataunits ) == 0)) {
106 /* Units and calendar are same as used last call */
107 }
108 else
109 {
110 /* Units/calendar combo are different from previously saved units, must redo calcs */
111
112 if( prev_units != NULL )
113 ut_free( prev_units );
114
115 if( prev_calendar != NULL )
116 free( prev_calendar );
117
118 /* Get origin day of the data units */
119 get_origin( dataunits, &y0, &mon0, &d0, &h0, &min0, &s0 ); /* Note: static vars */
120
121 /* Number of seconds into the specified origin day */
122 extra_seconds0 = h0*3600.0 + min0*60.0 + s0; /* Note: static vars */
123
124 /* Convert the origin day to Julian Day number in the specified calendar */
125 if( (ierr = ccs_date2jday( cal2use, y0, mon0, d0, &jday0 )) != 0 ) {
126 fprintf( stderr, "Error in utCalendar2: %s\n", ccs_err_str(ierr) );
127 return( UT_EINVALID );
128 }
129
130 /* Get converter from user-specified units to "days" */
131 if( conv_user_units_to_days != NULL )
132 cv_free( conv_user_units_to_days );
133 conv_user_units_to_days = get_user_to_day_converter( dataunits, y0, mon0, d0, h0, min0, s0 );
134
135 /* Save these units so we can reuse our time-consuming
136 * calculations next time if they are the same units
137 */
138 prev_units = ut_clone( dataunits );
139 if( ut_compare( prev_units, dataunits ) != 0 ) {
140 fprintf( stderr, "error, internal error in udunits2 library found in routine utCalendar2: a clone of the user's units does not equal the original units!\n" );
141 exit(-1);
142 }
143
144 prev_calendar = (char *)malloc( sizeof(char) * (strlen(cal2use->name)+1 ));
145 strcpy( prev_calendar, cal2use->name );
146 }
147
148 /* Convert user value of offset to floating point days */
149 fdays = cv_convert_double( conv_user_units_to_days, val );
150
151 /* Get integer number of days and seconds past that */
152 ndays = fdays;
153 extra_seconds = (fdays - ndays)*86400.0;
154
155 /* Get new Julian day */
156 jdnew = jday0 + ndays;
157
158 /* Handle the sub-day part */
159 tot_extra_seconds = extra_seconds0 + extra_seconds;
160 ndinc = tot_extra_seconds / 86400.0;
161 jdnew += ndinc;
162 tot_extra_seconds -= ndinc*86400.0;
163 if( tot_extra_seconds < 0.0 ) {
164 tot_extra_seconds += 86400.0;
165 jdnew--;
166 }
167
168 /* Convert to a date */
169 if( (ierr = ccs_jday2date( cal2use, jdnew, year, month, day )) != 0 ) {
170 fprintf( stderr, "Error in utCalendar2: %s\n", ccs_err_str(ierr) );
171 return( UT_EINVALID );
172 }
173
174 *hour = tot_extra_seconds / 3600.0;
175 tot_extra_seconds -= *hour * 3600.0;
176 *minute = tot_extra_seconds / 60.0;
177 tot_extra_seconds -= *minute * 60.0;
178 *second = tot_extra_seconds;
179
180 /* Handle the rouding issues */
181 iorig = *second; /* Integer conversion */
182 iround = *second + sec_rounding_value;
183 if( iround > iorig ) {
184 /* printf( "rounding alg invoked, orig date: %04d-%02d-%02d %02d:%02d:%.20lf\n", *year, *month, *day, *hour, *minute, *second ); */
185 *second = (double)iround;
186 if( *second >= 60.0 ) {
187 *second -= 60.0;
188 *minute += 1.0;
189 if( *minute >= 60.0 ) {
190 *minute -= 60.0;
191 *hour += 1.0;
192 if( *hour >= 24.0 ) {
193 *hour -= 24.0;
194 if( (ierr = ccs_jday2date( cal2use, jdnew+1, year, month, day )) != 0 ) {
195 fprintf( stderr, "Error in utCalendar2: %s\n", ccs_err_str(ierr) );
196 return( UT_EINVALID );
197 }
198 }
199 }
200 }
201 /* printf( "after rounding alg here is new date: %04d-%02d-%02d %02d:%02d:%02.20lf\n", *year, *month, *day, *hour, *minute, *second ); */
202 }
203
204 return(0);
205}
206
207/*========================================================================================
208 * Turn the passed Y/M/D date into a value in the user's units
209 */
210int utInvCalendar2_cal( int year, int month, int day, int hour, int minute, double second,
211 ut_unit *user_unit, double *value, const char *calendar_name )
212{
213 int jday, ierr, diff_in_days;
214 double fdiff_in_days, val_days, val_partdays, fdiff_in_partdays, fpartday;
215 calcalcs_cal *cal2use;
216
217 /* Following vars are static and retained between invocations for efficiency */
218 static ut_unit *prev_units=NULL;
219 static int y0, mon0, d0, h0, min0, jday0;
220 static double s0, fpartday0;
221 static cv_converter *conv_days_to_user_units=NULL;
222 static char *prev_calendar=NULL;
223
224 if( have_initted == 0 )
225 initialize( ut_get_system(user_unit) );
226
227 /* Get the calendar we will be using, based on the passed name
228 */
229 cal2use = getcal( calendar_name );
230 if( cal2use == NULL ) {
231 unknown_cal_emit_warning( calendar_name );
232 cal2use = getcal( "Standard" );
233 }
234
235 if( (prev_units != NULL) && (prev_calendar != NULL)
236 && (strcmp(prev_calendar,cal2use->name)==0)
237 && (ut_compare( prev_units, user_unit ) == 0)) {
238 /* Units are same as used last call */
239 }
240 else
241 {
242 if( prev_units != NULL )
243 ut_free( prev_units );
244
245 if( prev_calendar != NULL )
246 free( prev_calendar );
247
248 if( conv_days_to_user_units != NULL )
249 cv_free( conv_days_to_user_units );
250
251 /* Get origin day of the data units */
252 get_origin( user_unit, &y0, &mon0, &d0, &h0, &min0, &s0 ); /* Note: static vars */
253
254 /* Convert the origin day to Julian Day number in the specified calendar */
255 if( (ierr = ccs_date2jday( cal2use, y0, mon0, d0, &jday0 )) != 0 ) {
256 fprintf( stderr, "Error in utCalendar2: %s\n", ccs_err_str(ierr) );
257 return( UT_EINVALID );
258 }
259
260 /* Get the origin's HMS in fractional (floating point) part of a Julian day */
261 fpartday0 = (double)h0/24.0 + (double)min0/1440.0 + s0/86400.0;
262
263 /* Get converter for turning days into user's units */
264 conv_days_to_user_units = get_day_to_user_converter( user_unit, y0, mon0, d0, h0, min0, s0 );
265
266 /* Save these units so we can reuse our time-consuming
267 * calculations next time if they are the same units
268 */
269 prev_units = ut_clone( user_unit );
270 if( ut_compare( prev_units, user_unit ) != 0 ) {
271 fprintf( stderr, "error, internal error in udunits2 library found in routine utInvCalendar2: a clone of the user's units does not equal the original units!\n" );
272 exit(-1);
273 }
274
275 prev_calendar = (char *)malloc( sizeof(char) * (strlen(cal2use->name)+1 ));
276 strcpy( prev_calendar, cal2use->name );
277 }
278
279 /* Turn passed date into a Julian day */
280 if( (ierr = ccs_date2jday( cal2use, year, month, day, &jday )) != 0 ) {
281 fprintf( stderr, "Error in utInvCalendar2: %s\n", ccs_err_str(ierr) );
282 return( UT_EINVALID );
283 }
284
285 /* jday and jday0 can be very large and nearly equal, so we difference
286 * them first to keep the precision high
287 */
288 diff_in_days = jday - jday0;
289 fdiff_in_days = (double)diff_in_days;
290
291 /* Get the fractional (floating point) part of a Julian day difference
292 */
293 fpartday = (double)hour/24.0 + (double)minute/1440.0 + second/86400.0;
294 fdiff_in_partdays = fpartday - fpartday0;
295
296 /* Convert days and partial days to user's units */
297 val_days = cv_convert_double( conv_days_to_user_units, fdiff_in_days );
298 val_partdays = cv_convert_double( conv_days_to_user_units, fdiff_in_partdays );
299
300 /* Hopefully this will minimize the roundoff errors */
301 *value = val_days + val_partdays;
302
303 return(0);
304}
305
306/*==============================================================================================
307 * Get a converter that turns the user's units into days
308 */
309static cv_converter *get_user_to_day_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 )
310{
311 char daystr[1024];
312 ut_unit *udu_days;
313 cv_converter *conv_user_units_to_days;
314
315 sprintf( daystr, "days since %04d-%02d-%02d %02d:%02d:%f",
316 y0, mon0, d0, h0, min0, s0 );
317
318 udu_days = ut_parse( ut_get_system(uu), daystr, UT_ASCII );
319 if( udu_days == NULL ) {
320 fprintf( stderr, "internal error in utCalendar2/conv_to_days: failed to parse following string: \"%s\"\n",
321 daystr );
322 exit(-1);
323 }
324 conv_user_units_to_days = ut_get_converter( uu, udu_days );
325 if( conv_user_units_to_days == NULL ) {
326 fprintf( stderr, "internal error in utCalendar2/conv_to_days: cannot convert from \"%s\" to user units\n",
327 daystr );
328 exit(-1);
329 }
330
331 ut_free( udu_days );
332 return( conv_user_units_to_days );
333}
334
335/*==============================================================================================
336 * Get a converter that turns days into the user's units
337 */
338static cv_converter *get_day_to_user_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 )
339{
340 char daystr[1024];
341 ut_unit *udu_days;
342 cv_converter *conv_days_to_user_units;
343
344 sprintf( daystr, "days since %04d-%02d-%02d %02d:%02d:%f",
345 y0, mon0, d0, h0, min0, s0 );
346
347 udu_days = ut_parse( ut_get_system(uu), daystr, UT_ASCII );
348 if( udu_days == NULL ) {
349 fprintf( stderr, "internal error in utCalendar2/conv_to_user_units: failed to parse following string: \"%s\"\n",
350 daystr );
351 exit(-1);
352 }
353 conv_days_to_user_units = ut_get_converter( udu_days, uu );
354 if( conv_days_to_user_units == NULL ) {
355 fprintf( stderr, "internal error in utCalendar2/conv_to_user_units: cannot convert from user units to \"%s\"\n",
356 daystr );
357 exit(-1);
358 }
359
360 ut_free( udu_days );
361 return( conv_days_to_user_units );
362}
363
364/*==========================================================================================
365 * The user specified some origin to the time units. For example, if the units string
366 * were "days since 2005-10-15", then the origin date is 2005-10-15. This routine
367 * deduces the specified origin date from the passed units structure
368 */
369static void get_origin( ut_unit *dataunits, int *y0, int *mon0, int *d0, int *h0, int *min0, double *s0 )
370{
371 double s0lib, rez, tval, tval_conv, resolution;
372 cv_converter *conv_user_date_to_ref_date;
373 ut_unit *tmpu;
374 int y0lib, mon0lib, d0lib, h0lib, min0lib;
375 char ustr[1024];
376
377 static ut_unit *udu_ref_date=NULL; /* Saved between invocations */
378
379 if( udu_ref_date == NULL ) {
380
381 /* Make a timestamp units that refers to the udunits2 library's intrinsic
382 * time origin. The first thing we do is parse a timestampe unit
383 * (any old timestamp unit) and immediately discard the result, to account
384 * for a bug in the udunits-2 library that fails to set the library's
385 * time origin unless this step is done first. Without this, a call to
386 * ut_decode_time with tval==0 returns year=-4713 rather than 2001.
387 */
388 if( (tmpu = ut_parse( ut_get_system(dataunits), "days since 2010-01-09", UT_ASCII )) == NULL ) {
389 fprintf( stderr, "Internal error in routnie utCalendar2/get_origin: failed to parse temp timestamp string\n" );
390 exit(-1);
391 }
392 ut_free( tmpu );
393
394 tval = 0.0;
395 ut_decode_time( tval, &y0lib, &mon0lib, &d0lib, &h0lib, &min0lib, &s0lib, &rez );
396 sprintf( ustr, "seconds since %04d-%02d-%02d %02d:%02d:%f",
397 y0lib, mon0lib, d0lib, h0lib, min0lib, s0lib );
398 udu_ref_date = ut_parse( ut_get_system(dataunits), ustr, UT_ASCII );
399 if( udu_ref_date == NULL ) {
400 fprintf( stderr, "internal error in routine utCalendar2/get_origin: could not parse origin string \"%s\"\n",
401 ustr );
402 exit(-1);
403 }
404 }
405
406 /* Get converter from passed units to the library's intrinsic time units */
407 conv_user_date_to_ref_date = ut_get_converter( dataunits, udu_ref_date );
408
409 /* convert tval=0 to ref date */
410 tval = 0.0;
411 tval_conv = cv_convert_double( conv_user_date_to_ref_date, tval );
412
413 /* Now decode the converted value */
414 ut_decode_time( tval_conv, y0, mon0, d0, h0, min0, s0, &resolution );
415
416 cv_free( conv_user_date_to_ref_date );
417}
418
419/*========================================================================================*/
420static void initialize( ut_system *units_system )
421{
422 int i;
423
424 /* Make space for our saved calendars */
425 known_cal = (calcalcs_cal **)malloc( sizeof( calcalcs_cal * ) * maxcals_known );
426 if( known_cal == NULL ) {
427 fprintf( stderr, "Error in utCalendar2 routines, could not allocate internal storage\n" );
428 exit(-1);
429 }
430 for( i=0; i<maxcals_known; i++ )
431 known_cal[i] = NULL;
432 known_cal_name = (char **)malloc( sizeof( char * ) * maxcals_known );
433 if( known_cal_name == NULL ) {
434 fprintf( stderr, "Error in utCalendar2 routines, could not allocate internal storage for calendar names\n" );
435 exit(-1);
436 }
437 for( i=0; i<maxcals_known; i++ )
438 known_cal_name[i] = NULL;
439
440 /* Make a standard calendar */
441 cal_std = ccs_init_calendar( "Standard" );
442
443 have_initted = 1; /* a global */
444}
445
446/*========================================================================================
447 * Returns NULL if the passed calendar name is both not found and not creatable
448 */
449static calcalcs_cal *getcal( const char *name )
450{
451 int i, new_index;
452 calcalcs_cal *new_cal;
453
454 if( cal_std == NULL ) {
455 fprintf( stderr, "Coding error in utCalendar2_cal routines, cal_std is null!\n" );
456 exit(-1);
457 }
458
459 if( strcasecmp( name, "standard" ) == 0 )
460 return( cal_std );
461
462 /* See if it is one of the previously known calendars */
463 for( i=0; i<ncals_known; i++ ) {
464 if( strcmp( known_cal_name[i], name ) == 0 )
465 return( known_cal[i] );
466 }
467
468 /* If we get here, the cal is not known, so create it if possible */
469 new_cal = ccs_init_calendar( name );
470 if( new_cal == NULL )
471 return( NULL ); /* unknown name */
472
473 /* We now know a new calendar */
474 new_index = ncals_known;
475 ncals_known++;
476
477 /* new_index is where we will be storing the new calendar */
478 if( ncals_known > maxcals_known ) {
480 new_index = strlen(name); /* some arbitrary value */
481 if( new_index >= maxcals_known )
482 new_index = 10;
483 }
484
485 /* If there was already a calendar stored in this slot
486 * (because we might be reusing slots) then clear it out
487 */
488 if( known_cal[new_index] != NULL )
489 ccs_free_calendar( known_cal[new_index] );
490
491 if( known_cal_name[new_index] != NULL )
492 free( known_cal_name[new_index] );
493
494 known_cal[new_index] = new_cal;
495 known_cal_name[new_index] = (char *)malloc( sizeof(char) * (strlen(name)+1 ));
496 strcpy( known_cal_name[new_index], name );
497
498 return( new_cal );
499}
500
501/*=============================================================================================
502 */
503static void unknown_cal_emit_warning( const char *calendar_name )
504{
505 int i;
506
507 for( i=0; i<n_unkcal; i++ ) {
508 if( strcmp( calendar_name, unknown_cal_emitted_warning_for[i] ) == 0 )
509 /* Already emitted a warning for this calendar */
510 return;
511 }
512
514 /* Too many warnings already given, give up */
515 return;
516
517 /* OK, emit the warning now */
518 fprintf( stderr, "Error in utCalendar2_cal/utInvCalendar2_cal: unknown calendar \"%s\". Using Standard calendar instead\n",
519 calendar_name );
520
521 /* Save the fact that we have warned for this calendar */
522 unknown_cal_emitted_warning_for[ n_unkcal ] = (char *)malloc( sizeof(char) * (strlen(calendar_name)+1 ));
524 fprintf( stderr, "Error in utCalendar_cal: could not allocate internal memory to store string \"%s\"\n",
525 calendar_name );
526 return;
527 }
528
529 strcpy( unknown_cal_emitted_warning_for[ n_unkcal ], calendar_name );
530 n_unkcal++;
531}
char * ccs_err_str(int error_code)
Definition calcalcs.c:908
int ccs_jday2date(calcalcs_cal *calendar, int jday, int *year, int *month, int *day)
Definition calcalcs.c:414
void ccs_free_calendar(calcalcs_cal *cc)
Definition calcalcs.c:1338
int ccs_date2jday(calcalcs_cal *calendar, int year, int month, int day, int *jday)
Definition calcalcs.c:442
calcalcs_cal * ccs_init_calendar(const char *calname)
Definition calcalcs.c:104
char * name
Definition calcalcs.h:31
int utCalendar2_cal(double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, int *minute, double *second, const char *calendar_name)
static int have_initted
static void initialize(ut_system *units_system)
static char ** known_cal_name
static cv_converter * get_day_to_user_converter(ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0)
static int n_unkcal
int utInvCalendar2_cal(int year, int month, int day, int hour, int minute, double second, ut_unit *user_unit, double *value, const char *calendar_name)
static const double sec_rounding_value
#define UTC2_MAX_UNKCAL_WARNS
static void unknown_cal_emit_warning(const char *calendar_name)
static void get_origin(ut_unit *dataunits, int *y0, int *mon0, int *d0, int *h0, int *min0, double *s0)
static int ncals_known
static calcalcs_cal * getcal(const char *name)
static char * unknown_cal_emitted_warning_for[UTC2_MAX_UNKCAL_WARNS]
static cv_converter * get_user_to_day_converter(ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0)
static calcalcs_cal ** known_cal
static const int maxcals_known
static calcalcs_cal * cal_std
#define UT_ENOINIT
#define UT_EINVALID