PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
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 
33 static int have_initted=0;
34 static 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 */
41 static const double sec_rounding_value = 1.e-8;
42 
43 
44 /* Internal to this file only */
45 static void initialize( ut_system *units_system );
46 static void get_origin( ut_unit *dataunits, int *y0, int *mon0, int *d0, int *h0, int *min0, double *s0 );
47 static cv_converter *get_day_to_user_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 );
48 static cv_converter *get_user_to_day_converter( ut_unit *uu, int y0, int mon0, int d0, int h0, int min0, double s0 );
49 static calcalcs_cal *getcal( const char *name );
50 static void unknown_cal_emit_warning( const char *calendar_name );
51 
52 /* Stores previuosly initialized calendars and their names */
53 static const int maxcals_known=100;
54 static int ncals_known=0;
55 static calcalcs_cal **known_cal; /* ptr to array of calcals_cal ptrs */
56 static char **known_cal_name;
57 
58 /* Stores previously emitted "unknown calendar" warnings */
59 #define UTC2_MAX_UNKCAL_WARNS 1000
61 static int n_unkcal=0;
62 
63 /*========================================================================================
64  * Turns the passed value into a Y/M/D date
65  */
66 int 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  */
210 int 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  */
309 static 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  */
338 static 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  */
369 static 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 /*========================================================================================*/
420 static 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  */
449 static 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  */
503 static 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 ));
523  if( unknown_cal_emitted_warning_for[ n_unkcal ] == NULL ) {
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 }
calcalcs_cal * ccs_init_calendar(const char *calname)
Definition: calcalcs.c:103
char * ccs_err_str(int error_code)
Definition: calcalcs.c:907
int ccs_jday2date(calcalcs_cal *calendar, int jday, int *year, int *month, int *day)
Definition: calcalcs.c:413
void ccs_free_calendar(calcalcs_cal *cc)
Definition: calcalcs.c:1337
int ccs_date2jday(calcalcs_cal *calendar, int year, int month, int day, int *jday)
Definition: calcalcs.c:441
static const double h0
Definition: exactTestP.cc:49
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