utCalendar2_cal.c

00001 /*
00002 This routines provide the equivalent functionality of 'utCalendar' and 'utInvCalendar'
00003 for the udunits-2 library, which does not directly have equivalent calls.  
00004 
00005 Furthermore, they extend these to add two more new routines, utCalendar_cal and
00006 utInvCalendar_cal, which take a "calendar" argument indicating which calendar to use
00007 for the calculations.  The calendar string can be "standard", "noleap", "365_day", 
00008 "360", or "360_day" at the moment.  If the calendar string is set to NULL, a standard 
00009 calendar is used.
00010 
00011 Note that the change to the udunits-2 library requires that the argument list of
00012 utCalendar and utInvCalendar be different from the argument list that these routines
00013 had in the udunits-1 library.
00014 
00015 Note that these routines call the udunits-2 routines, so they have to be installed to use this!
00016 
00017 Version 3.01    (versions < 2 are for the udunits-1 library, >=3 are for udunits-2 library)
00018 David W. Pierce
00019 dpierce@ucsd.edu
00020 2010-01-13
00021 
00022 Thanks to Christian Page' of CERFACS, France, for bug fixes!
00023 
00024 PLEASE READ: IMPORTANT DISCUSSION OF THE "YEAR 0" PROBLEM
00025 ---------------------------------------------------------
00026 
00027 In our actual calendars there is no year 0, because the calendar
00028 goes from year 1 BC to year 1 AD, with no "year 0" in between.  Nevertheless,
00029 people often do mistakenly start their calendars from "year 0".  In particular,
00030 the NCAR CAM atmospheric model (and consequently, the CCSM3 coupled earth
00031 system model) bases dates on "year 0".
00032 
00033 The standard udunits library treats a units spec of the form "... since 0000-01-01"
00034 as if it instead specified ".... since 0001-01-01".  Ie., it treats a reference
00035 to a origin of year 0 with a reference to an origin of year 1.  This can be confusing 
00036 if the user expects, for example, that the dates "0 days since 0001-01-01" and 
00037 "0 days since 0000-01-01" are different.  To the udunits library, these are the same date.
00038 
00039 Particular headaches are caused when trying to understand CAM/CCSM3 model output
00040 based on ".... since 0000-01-01".  For the *model*, "23 days since 0000-01-01"
00041 is intended to be the date 0000-01-24.  However, for the *udunits library*, 
00042 "23 days since 0000-01-01" is the date 0001-01-24.  So, in essence, the udunits
00043 library adds 1 to the year of a CAM/CCSM3 file that uses ".... since 0000-01-01"
00044 as its date.
00045 
00046 This code is intended to be similar to a "drop in" replacement of the standard
00047 udunits-1 calls that works in udunits-2, and so DOES NOT MODIFY THE YEAR 0 
00048 BEHAVIOR in any way.  All origins that start at "year 0000" (which does not 
00049 exist) are treated as if they had specified "year 0001".  Again, this is exactly 
00050 what the standard udunits library does.  NOTE: the udunits-2 library treats
00051 the "year 0" problem exactly the same as does the udunits-1 library.
00052 */
00053 
00054 /* LICENSE BEGIN
00055 
00056 Copyright Cerfacs (Christian Page) (2015)
00057 
00058 christian.page@cerfacs.fr
00059 
00060 This software is a computer program whose purpose is to downscale climate
00061 scenarios using a statistical methodology based on weather regimes.
00062 
00063 This software is governed by the CeCILL license under French law and
00064 abiding by the rules of distribution of free software. You can use, 
00065 modify and/ or redistribute the software under the terms of the CeCILL
00066 license as circulated by CEA, CNRS and INRIA at the following URL
00067 "http://www.cecill.info". 
00068 
00069 As a counterpart to the access to the source code and rights to copy,
00070 modify and redistribute granted by the license, users are provided only
00071 with a limited warranty and the software's author, the holder of the
00072 economic rights, and the successive licensors have only limited
00073 liability. 
00074 
00075 In this respect, the user's attention is drawn to the risks associated
00076 with loading, using, modifying and/or developing or reproducing the
00077 software by the user in light of its specific status of free software,
00078 that may mean that it is complicated to manipulate, and that also
00079 therefore means that it is reserved for developers and experienced
00080 professionals having in-depth computer knowledge. Users are therefore
00081 encouraged to load and test the software's suitability as regards their
00082 requirements in conditions enabling the security of their systems and/or 
00083 data to be ensured and, more generally, to use and operate it in the 
00084 same conditions as regards security. 
00085 
00086 The fact that you are presently reading this means that you have had
00087 knowledge of the CeCILL license and that you accept its terms.
00088 
00089 LICENSE END */
00090 
00091 
00092 
00093 
00094 
00095 
00096 #include <stdio.h>
00097 #include <math.h>
00098 #include <stdlib.h>
00099 #include <udunits.h>
00100 #include <string.h>
00101 #include <strings.h>
00102 #include <utCalendar2_cal.h>
00103 
00104 /* define DEBUG */
00105 
00106 /*                                         J   F   M   A   M   J   J   A   S   O   N   D    */
00107 static long days_per_month_reg_year[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
00108 static long days_per_month_360[]      = { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 };
00109 
00110 static ut_unit *udu_origin_zero=NULL;
00111 
00112 static void make_udu_origin_zero( ut_unit *units );
00113 static int udu_sec_since_ref_date( double val, ut_unit *dataunits, int *yr0, int *mon0, 
00114                 int *day0, int *hr0, int *min0, double *sec0, double *ssrd );   /* ssrd = "seconds since reference date */
00115 static int utCalendar2_noleap_inner( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00116                                 int *minute, double *second, int days_per_year, long *days_per_month );
00117 static int utCalendar2_360( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00118                                 int *minute, double *second );
00119 static int utCalendar2_noleap( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00120                                 int *minute, double *second );
00121 static int utInvCalendar2_noleap_inner( int year, int month, int day, int hour, int minute,
00122                 double second, ut_unit *unit, double *value, long *days_per_month );
00123 static void err_mess_not_t_convertible( ut_unit *unit );
00124 
00125 static int shown_proleptic_warning = 0;
00126 static int shown_julian_warning    = 0;
00127 
00128 /*
00129  * Mary removed the have_initted flag, because we want to initialize
00130  * every time. 
00131  * static int have_initted                 = 0;
00132  */
00133 
00134 #if DEBUG > 7
00135 static void dateify( int year, int month, int day, int hour, int minute, double second, char *buf );
00136 /******************************************************************************/
00137 static void dateify( int year, int month, int day, int hour, int minute, 
00138                 double second, char *buf )
00139 {
00140         sprintf( buf, "%04d-%02d-%02d %02d:%02d:%.2f", year, month, day, hour, minute, second );
00141 }
00142 #endif
00143 
00144 /******************************************************************************
00145  * Creates a time unit that has zero offset relative to the time unit that 
00146  * the udunits-2 library uses.
00147  */
00148 static void make_udu_origin_zero( ut_unit *units )
00149 {
00150         double  zero;
00151         int     y0, m0, d0, h0, min0;
00152         double  s0, rez;
00153         char    u_str[1024];
00154         ut_unit *tmpu;
00155         ut_system *unitSystem;
00156 
00157         unitSystem = ut_get_system( units );
00158 
00159         /* Work around a bug in the udunits-2 library that resets the 
00160          * library's internal time origin value upon the first
00161          * ut_parse( timestamp ) call, but not upon the first
00162          * ut_decode_time call
00163          */
00164         if( (tmpu = ut_parse( unitSystem, "days since 1901-01-01", UT_ASCII )) == NULL ) {
00165                 fprintf( stderr, "Internal error in utCalendar2_cal routine make_udu_origin_zero: could not decode test timestamp string\n" );
00166                 exit(-1);
00167                 }
00168         ut_free( tmpu );        /* We just do the parse for its side effect */
00169 
00170         zero = 0.0;
00171         ut_decode_time( zero, &y0, &m0, &d0, &h0, &min0, &s0, &rez );
00172 
00173         sprintf( u_str, "seconds since %04d-%02d-%02d %02d:%02d:%.12lf",
00174                 y0, m0, d0, h0, min0, s0 );
00175 
00176         if (udu_origin_zero) 
00177                 ut_free(udu_origin_zero);
00178 
00179         udu_origin_zero = ut_parse( unitSystem, u_str, UT_ASCII );      /* NOTE: sets a global (udu_origin_zero) */
00180         if( udu_origin_zero == NULL ) {
00181                 fprintf( stderr, "Internal error in utCalendar2_cal routine make_udu_origin_zero: could not decode string \"%s\"\n",
00182                         u_str );
00183                 exit(-1);
00184                 }
00185 }
00186 
00187 /******************************************************************************/
00188 /* This extends the standard utCalendar call by recognizing CF-1.0 compliant
00189  * calendar names.
00190  * Here are some check values:
00191  *
00192  *   Units string               calendar        input val        Output date    note
00193  * ---------------              --------        ----------       -----------    ----
00194  * days since 0001-01-01        standard        146000 (400x365) 0400-09-23     400*365 days leaves us 100 leap days short of 1 Jan
00195  * days since 1001-01-01        standard        146000 (400x365) 1400-09-23     Advance by 1000 yrs leaves same deficit as above
00196  * days since 1601-01-01        standard        146000 (400x365) 2000-09-26     NOW have 3 less leap days than prev, since udunits 
00197  *                                                                              switches to Gregorian calendar after 1582
00198  * days since 1001-01-01        standard        -146000          0601-04-11     works with neg vals too; 400*365 leaves us in day #101
00199  * days since 2001-01-01        standard        -146000          1601-04-08     Gregorian calendar, vs. Julian in prev line
00200  *
00201  * days since 1001-07-15        standard        146000           1401-04-06     Offset can be other than first day of year; fall 100 
00202  *                                                                              days short of going exactly 400 yrs, due to lack of leap days
00203  *
00204  * days since 0001-01-01        noleap          146000           0401-01-01     No leap days, 400*365 = 400 yrs exactly
00205  * days since 1601-01-01        noleap          146000           2001-01-01     "noleap" calendar doesn't change behavior around 1582
00206  * days since 2001-01-01        noleap          -146000          1601-01-01     works with neg values too
00207  * days since 1001-01-01        noleap          -146000          0601-01-01     neg values don't care before/after 1582 either
00208  *
00209  * Returns 0 on success, UT_ENOINIT if the package hasn't been initialized yet, and UT_EINVALID if the
00210  * passed unit structure is not a temporal one.
00211  */
00212 /************************************************************************************************************************/
00213 int utCalendar2_cal( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00214                                 int *minute, double *second, const char *calendar ) 
00215 {
00216         static  int have_shown_warning = 0;
00217 
00218 #if DEBUG > 7
00219         printf( "entering utCalendar2_cal\n" );
00220         printf( "Input value: %lf  Input calendar: %s\n", val, calendar );
00221 #endif
00222 
00223 /*
00224         if( have_initted == 0 ) {
00225                 make_udu_origin_zero( dataunits );
00226                 have_initted = 1;
00227                 }
00228  */
00229           make_udu_origin_zero( dataunits );
00230 
00231         if( (calendar == NULL) || (strlen(calendar)==0) || (strncasecmp(calendar,"standard",8)==0) || (strncasecmp(calendar,"gregorian",9)==0) ) {
00232 #if DEBUG > 7
00233                 printf( "utCalendar_cal: using standard calendar\n" );
00234 #endif
00235                 return( utCalendar2( val, dataunits, year, month, day, hour, minute, second ));
00236                 }
00237 
00238         else if( (strcmp(calendar,"365")==0) || (strncasecmp(calendar,"365_day",7)==0) || (strncasecmp(calendar,"noleap",6)==0) ) {
00239 #if DEBUG > 7
00240                 printf( "utCalendar_cal: using 365-day calendar\n" );
00241 #endif
00242                 return( utCalendar2_noleap( val, dataunits, year, month, day, hour, minute, second ));
00243                 }
00244 
00245         else if( (strcmp(calendar,"360")==0) || (strncasecmp(calendar,"360_day",7)==0) ) {
00246 #if DEBUG > 7
00247                 printf( "utCalendar_cal: using 360-day calendar\n" );
00248 #endif
00249                 return( utCalendar2_360( val, dataunits, year, month, day, hour, minute, second ));
00250                 }
00251 
00252         else if( strncasecmp(calendar,"proleptic_gregorian",19)==0) {
00253                 if( shown_proleptic_warning == 0 ) {
00254                         fprintf( stderr, "********************************************************************************\n" );
00255                         fprintf( stderr, "Sorry, proleptic_gregorian calendar not implemented yet; using standard calendar\n" );
00256                         fprintf( stderr, "********************************************************************************\n" );
00257                         shown_proleptic_warning = 1;
00258                         }
00259                 return( utCalendar2( val, dataunits, year, month, day, hour, minute, second ));
00260                 }
00261 
00262         else if( strncasecmp(calendar,"julian",6)==0) {
00263                 if( shown_julian_warning == 0 ) {
00264                         fprintf( stderr, "Sorry, julian calendar not implemented yet; using standard calendar\n" );
00265                         shown_julian_warning = 1;
00266                         }
00267                 return( utCalendar2( val, dataunits, year, month, day, hour, minute, second ));
00268                 }
00269 
00270         else
00271                 {
00272                 if( ! have_shown_warning ) {
00273                         fprintf( stderr, "WARNING: unknown calendar: \"%s\". Using standard calendar instead!\n", calendar );
00274                         have_shown_warning = 1;
00275                         }
00276                 return( utCalendar2( val, dataunits, year, month, day, hour, minute, second ));
00277                 }
00278 }
00279 
00280 /*******************************************************************************************************************
00281  * Print an error message that the passed unit is not convertible to a timestamp unit 
00282  * (i.e., is temporal with an origin)
00283  */
00284 static void err_mess_not_t_convertible( ut_unit *unit )
00285 {
00286         static  ut_unit *last_erroneous_unit=NULL;
00287         char    tunit[1024];
00288 
00289         if( (last_erroneous_unit == NULL) || (ut_compare(unit, last_erroneous_unit) != 0)) {
00290                 if( ut_format( unit, tunit, 1020, UT_ASCII|UT_NAMES) != -1 )
00291                         fprintf( stderr, "utCalendar2: error, can't convert units \"%s\" to a date; did you pass a correctly formatted timestamp unit (for example, \"days since 1901-01-01 00:00\"?\n",
00292                                 tunit );
00293                 }
00294 
00295         if( (last_erroneous_unit != NULL) && (ut_compare(unit, last_erroneous_unit) != 0))
00296                 /* We have a previously erroneous unit, but it's different from current one */
00297                 ut_free( last_erroneous_unit );
00298 
00299         last_erroneous_unit = ut_clone( unit );
00300 }
00301 
00302 /*******************************************************************************************************************
00303  * For some reason udunits-2 doesn't seem to directly include this function, so here it is. Note that
00304  * the API differs from the udunits-1 version in that the "second" argument is double instead of float.
00305  */
00306 int utCalendar2( double value, ut_unit *unit, int *year, int *month, int *day, int *hour, int *minute, double *second )
00307 {
00308         double  resolution, value_conv;
00309 
00310         /* Following values are saved between invocations */
00311         static ut_unit  *last_unit=NULL;
00312         static cv_converter *conv_user_to_lib=NULL;
00313 
00314         if( unit == NULL )
00315                 return( UT_ENOINIT );
00316 
00317 /*
00318         if( ! have_initted ) {
00319                 make_udu_origin_zero( unit );
00320                 have_initted = 1;
00321                 }
00322  */
00323         make_udu_origin_zero( unit );
00324 
00325         if( (last_unit == NULL) || (ut_compare(unit, last_unit) != 0)) {
00326 
00327                 if( last_unit != NULL )
00328                         ut_free( last_unit );
00329 
00330                 if( conv_user_to_lib != NULL )
00331                         cv_free( conv_user_to_lib );
00332 
00333                 /* Make a converter FROM given units TO library units */
00334                 conv_user_to_lib = ut_get_converter( unit, udu_origin_zero );
00335                 if( conv_user_to_lib == NULL ) {
00336                         err_mess_not_t_convertible( unit );
00337                         return( UT_EINVALID );
00338                         }
00339 
00340                 last_unit = ut_clone( unit );
00341                 }
00342 
00343         /* Do the conversion */
00344         value_conv = cv_convert_double( conv_user_to_lib, value );
00345 
00346         /* Change into a date */
00347         ut_decode_time( value_conv, year, month, day, hour, minute, second, &resolution );
00348 
00349         return(0);
00350 }
00351 
00352 /*******************************************************************************************************************
00353  * For some reason udunits-2 doesn't seem to directly include this function, so here it is. Note that
00354  * the API differs from the udunits-1 version in that the "second" argument is double instead of float.
00355  */
00356 int utInvCalendar2( int year, int month, int day, int hour, int minute, double second, ut_unit *unit, double *value )
00357 {
00358         double  lib_tval;
00359 
00360         static ut_unit  *last_unit=NULL;
00361         static cv_converter *conv_lib_to_user=NULL;
00362 
00363         if( unit == NULL )
00364                 return( UT_ENOINIT );
00365 
00366 /*
00367         if( have_initted == 0 ) {
00368                 make_udu_origin_zero( unit );
00369                 have_initted = 1;
00370                 }
00371  */
00372         make_udu_origin_zero( unit );
00373 
00374         /* First turn the date into a time w.r.t. the udunits library's internal 
00375          * reference date and units (which as of this writing is seconds since
00376          * 2001-01-01, but it is irrelevant what they are, the code doesn't care)
00377          */
00378         lib_tval = ut_encode_time( year, month, day, hour, minute, second );
00379 
00380         if( (last_unit == NULL) || (ut_compare(unit, last_unit) != 0)) {
00381 
00382                 if( last_unit != NULL )
00383                         ut_free( last_unit );
00384 
00385                 if( conv_lib_to_user != NULL )
00386                         cv_free( conv_lib_to_user );
00387 
00388                 /* Make a converter FROM library units TO user units */
00389                 conv_lib_to_user = ut_get_converter( udu_origin_zero, unit );
00390                 if( conv_lib_to_user == NULL ) {
00391                         err_mess_not_t_convertible( unit );
00392                         return( UT_EINVALID );
00393                         }
00394 
00395                 last_unit = ut_clone( unit );
00396                 }
00397 
00398         /* Do the conversion */
00399         *value = cv_convert_double( conv_lib_to_user, lib_tval );
00400 
00401         return(0);
00402 }
00403 
00404 /*************************************************************************************************
00405  * This returns 2 things.  1) the "since" time encoded in the "dataunits" object
00406  * (yr0, mon0, day0, etc) and 2) the number of seconds since that since time.  I.e.,
00407  * if the units string is "days since 1979-01-01", then the "since time" is 1 Jan 1979, which
00408  * is returned in yr0, mon0, day0, etc.  The returned value of this routine is 
00409  * the number of seconds that the "val, dataunits" combination indicates since that reference date.
00410  * So if val=2 and dataunits=days, then the returned value will be 2*86400.
00411  *
00412  * Returns 0 on success, <0 on error.
00413  */
00414 static int udu_sec_since_ref_date( double val, ut_unit *dataunits, int *yr0, int *mon0, 
00415                 int *day0, int *hr0, int *min0, double *sec0, double *ssrd )    /* ssrd = "seconds since reference date */
00416 {
00417         double          tval0, tval_conv, rez;
00418         char            tunits[1024];
00419 #if DEBUG > 7
00420         char            buf[1024];
00421 #endif
00422         ut_unit         *users_units_in_seconds;
00423         int             same_units_as_before;
00424         cv_converter    *conv_user_date_to_ref_date;
00425 
00426         /* Following are saved between invocations */
00427         static ut_unit  *last_dataunits=NULL;
00428         static cv_converter *conv_user_units_to_sec=NULL;
00429         static int p_yr0, p_mon0, p_day0, p_hr0, p_min0;        /* "previous" values from prior call */
00430         static double p_sec0;                                   /* "previous" value from prior call */
00431 
00432         if( udu_origin_zero == NULL )   /* Should never happen, but be safe anyway */
00433                 make_udu_origin_zero( dataunits );
00434 
00435         /* If we are doing the same units as previously, use cached values */
00436         same_units_as_before = ((last_dataunits != NULL) && (ut_compare(dataunits, last_dataunits) == 0));
00437 
00438         /* Happily, udunits-2 makes it much easier to figure out the "since date".  All
00439          * you need to do is convert a quantiy of zero units since the since date
00440          * into the library's time system, then decode that value.  Example: if the
00441          * original string was "days since 2005-01-01", make a converter that turns
00442          * "days since 2005-01-01" into the library's reference time unit.  Then,
00443          * convert the value of "0 days since 2005-01-01" and the result will be the number
00444          * of seconds (since that is the time unit the ref library uses) between the
00445          * reference library's since date and the user's since date.  Decode that
00446          * time, and viola! You have the originally specified since date.
00447          */
00448         if( same_units_as_before ) {
00449                 *yr0=p_yr0; *mon0=p_mon0; *day0=p_day0; *hr0=p_hr0; *min0=p_min0; *sec0=p_sec0; 
00450                 }
00451         else
00452                 {
00453                 conv_user_date_to_ref_date = ut_get_converter( dataunits, udu_origin_zero );
00454                 if( conv_user_date_to_ref_date == NULL ) {
00455                         err_mess_not_t_convertible( dataunits );
00456                         return( UT_EINVALID );
00457                         }
00458                 tval0 = 0.0;
00459                 tval_conv = cv_convert_double( conv_user_date_to_ref_date, tval0 );
00460 
00461                 ut_decode_time( tval_conv, yr0, mon0, day0, hr0, min0, sec0, &rez );
00462                 p_yr0 = *yr0; p_mon0 = *mon0; p_day0 = *day0; p_hr0 = *hr0; p_min0 = *min0; p_sec0 = *sec0;
00463 
00464 #if DEBUG > 7
00465                 dateify( *yr0, *mon0, *day0, *hr0, *min0, *sec0, buf );
00466                 printf( "udu_sec_since_ref_date: here is the <<since date>> that the user specified: %s\n", buf );
00467 #endif
00468 
00469                 /* Now convert the value from whatever units it might be in to seconds.
00470                  * For example, if the user specified "days since 1901-01-01", and the
00471                  * passed argument "val" is 1, then we want to return 86400.
00472                  */
00473                 sprintf( tunits, "seconds since %04d-%02d-%02d %02d:%02d:%.12lf", 
00474                         *yr0, *mon0, *day0, *hr0, *min0, *sec0 );
00475 
00476                 users_units_in_seconds = ut_parse( ut_get_system(dataunits), tunits, UT_ASCII );
00477                 if( users_units_in_seconds == NULL ) {
00478                         fprintf( stderr, "Internal error in routine udu_sec_since_ref_date: cannot parse string \"%s\"\n",
00479                                 tunits );
00480                         return( UT_EINVALID );
00481                         }
00482 
00483                 /* Get the new converter */
00484                 if( conv_user_units_to_sec != NULL )
00485                         cv_free( conv_user_units_to_sec );
00486                 conv_user_units_to_sec = ut_get_converter( dataunits, users_units_in_seconds );
00487                 if( conv_user_units_to_sec == NULL ) {
00488                         err_mess_not_t_convertible( dataunits );
00489                         return( UT_EINVALID );
00490                         }
00491 
00492                 ut_free( users_units_in_seconds );
00493                 
00494                 if( last_dataunits != NULL )
00495                         ut_free( last_dataunits );
00496                 last_dataunits = ut_clone( dataunits );
00497                 }
00498                 
00499         *ssrd = cv_convert_double( conv_user_units_to_sec, val );
00500 
00501 #if DEBUG > 7
00502         printf( "udu_sec_since_ref_date: passed VAL in user units was %lf, converted that to %lf seconds\n", 
00503                 val, *ssrd );
00504 #endif
00505         return( 0 );
00506 }
00507 
00508 /*************************************************************************************/
00509 /* A calendar with no leap days; days per month and year are passed in, not assumed!  
00510  * Can pass in days_per_year=365 and days_per_month={30,28,31,30, etc} for a "noleap" calendar,
00511  * or days_per_year=360 and days_per_month={30,30,30,...} for a "360 day" calendar
00512  */
00513 static int utCalendar2_noleap_inner( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00514                                 int *minute, double *second, int days_per_year, long *days_per_month )
00515 {
00516         int yr0, mon0, day0, hr0, min0, ierr;
00517         double sec0;
00518         double ss, ss_extra;
00519         long dy, ds, sec_per_day, sec_per_hour, sec_per_min, nny;
00520         long nhrs, nmin;
00521 
00522         sec_per_day   = 86400;
00523         sec_per_hour  = 3600;
00524         sec_per_min   = 60;
00525 
00526         /* -------------------------------------------------------------------------------------
00527          * Get both the REFERENCE TIME that the netCDF file specifies for the units string
00528          * (yr0, mon0, day0, etc) and the number of seconds since that reference date.  I.e.,
00529          * if the units string is "days since 1979-01-01", then the reference date is 1 Jan 1979
00530          * and 'ss' is the number of seconds since that date that we want to turn into a calendar
00531          * date, given the specified calendar.
00532          *--------------------------------------------------------------------------------------*/
00533         if( (ierr = udu_sec_since_ref_date( val, dataunits, &yr0, &mon0, &day0, &hr0, &min0, &sec0, &ss )) != 0 )
00534                 return( ierr );
00535 #if DEBUG > 7
00536         printf( "utCalendar2_noleap_inner: converting time %lf seconds since %04d-%02d-%02d %02d:%02d:%06.3lf\n", ss, yr0, mon0, day0, hr0, min0, sec0 ); 
00537 #endif
00538 
00539         /*--------------------------------------------------------------------------
00540          * If we have a date before our reference date (indicated by a negative ss),
00541          * then wind back the reference date to to be before the target
00542          * date.  This avoids having to muck around with negative offsets.
00543          *------------------------------------------------------------------------*/
00544         if( ss < 0 ) {
00545                 nny = -ss / (sec_per_day*days_per_year) + 1;
00546                 yr0 -= nny;
00547                 ss  += nny * sec_per_day*days_per_year;
00548                 }
00549 
00550         /*-------------------------------------------------------------------
00551          * We now have seconds since (ss) yr0, mon0, day0, hr0, min0, sec0.
00552          * Try to turn this into integer days since reference date and seconds
00553          * extra, avoiding problems with roundoff and limited precision.  Not
00554          * an exact science.  We use days since (ds) instead of sticking 
00555          * strictly with seconds since (ss) becuase we can overflow longs in
00556          * fairly routine circumstances, if tring to put a century or so of
00557          * seconds into a long.
00558          *-------------------------------------------------------------------*/
00559         ds = (long)((ss + .01)/(double)sec_per_day);
00560         ss_extra = ss - ((double)ds)*((double)sec_per_day);  /* need to be careful of overflow here */
00561         if( ss_extra < 0. )
00562                 ss_extra = 0;
00563 
00564 #if DEBUG > 7
00565         printf( "utCalendar2_noleap_inner: # of days since ref date: %ld   # of extra seconds after days taken out: %lf\n", ds, ss_extra );
00566 #endif
00567 
00568         /*------------------------------------------------
00569          * Easier to do things relative to 1 Jan  00:00:00
00570          *-----------------------------------------------*/
00571         if( (sec0 != 0) || (min0 != 0) || (hr0 != 0) || (day0 != 1) || (mon0 != 1)) {
00572 
00573                 ss_extra += sec0;
00574                 sec0 = 0;
00575 
00576                 ss_extra += min0 * sec_per_min;
00577                 min0 = 0;
00578 
00579                 ss_extra += hr0 * sec_per_hour;
00580                 hr0 = 0;
00581 
00582                 ds += (day0-1);
00583                 day0 = 1;
00584 
00585                 while( mon0 > 1 ) {
00586                         ds += days_per_month[ mon0-2 ]; /*  -2 cuz -1 for prev month, -1 for 0 offset */
00587                         mon0--;
00588                         }
00589                 }
00590 
00591         /* After adjustments immediately above, could have more seconds
00592          * than there are in a day
00593          */
00594         while( ss_extra > (double)sec_per_day ) {
00595                 ds++;
00596                 ss_extra -= (double)sec_per_day;
00597                 }
00598 
00599         dy = ds / days_per_year;
00600         *year = yr0 + dy;
00601         ds = ds - dy*days_per_year;
00602 
00603         *month = 1;
00604         while( ds > days_per_month[(*month) - 1] - 1 ) {
00605                 ds -= days_per_month[(*month) - 1];
00606                 (*month)++;
00607                 }
00608 
00609         *day = ds + 1;
00610 
00611         nhrs = ss_extra / sec_per_hour;
00612         *hour = nhrs;
00613         ss_extra -= nhrs * sec_per_hour;
00614 
00615         nmin = ss_extra / sec_per_min;
00616         *minute = nmin;
00617         ss_extra -= nmin * sec_per_min;
00618 
00619         *second = ss_extra;
00620 
00621         return(0);
00622 }
00623 
00624 /******************************************************************************/
00625 static int utCalendar2_360( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00626                                 int *minute, double *second )
00627 {
00628         long days_per_year;
00629 
00630         days_per_year = 360L;
00631 
00632         return( utCalendar2_noleap_inner( val, dataunits, year, month, day, hour, minute, second,
00633                 days_per_year, days_per_month_360 ));
00634 }
00635 
00636 /******************************************************************************/
00637 static int utCalendar2_noleap( double val, ut_unit *dataunits, int *year, int *month, int *day, int *hour, 
00638                                 int *minute, double *second )
00639 {
00640         long days_per_year;
00641 
00642         days_per_year = 365L;
00643 
00644         return( utCalendar2_noleap_inner( val, dataunits, year, month, day, hour, minute, second,
00645                 days_per_year, days_per_month_reg_year ));
00646 }
00647 
00648 
00649 /*****************************************************************************************************
00650  *****************************************************************************************************
00651  *****************************************************************************************************
00652  *****************************************************************************************************
00653  *
00654  *  Inverse Calendar routines 
00655  *
00656  *****************************************************************************************************
00657  *****************************************************************************************************
00658  *****************************************************************************************************
00659  *
00660 Similar to utInvCalendar, but takes an extra 'cal' argument that can be one of the
00661 following:
00662         'noleap':       A calendar with no leap years, so just 365 days every year
00663         '365_day':      A synonym for noleap
00664         '360_day':      A day with 360 days per year, arranged in 12 months of 30 days each
00665         'standard':     An ordinary, Gregorian calendar
00666 
00667 Check values:
00668 
00669 Units string            calendar        Input: yr,mo,dy,hr,min,sec      Output val      Notes
00670 ------------            --------        --------------------------      ----------      -----
00671 days since 2000-02-25   standard        2000, 3, 1, 12, 0, 0.0          5.5             2000 was a leap year
00672 days since 2000-02-25   noleap          2000, 3, 1, 12, 0, 0.0          4.5             2000 was a leap year
00673 */
00674 
00675 /* LICENSE BEGIN
00676 
00677 Copyright Cerfacs (Christian Page) (2015)
00678 
00679 christian.page@cerfacs.fr
00680 
00681 This software is a computer program whose purpose is to downscale climate
00682 scenarios using a statistical methodology based on weather regimes.
00683 
00684 This software is governed by the CeCILL license under French law and
00685 abiding by the rules of distribution of free software. You can use, 
00686 modify and/ or redistribute the software under the terms of the CeCILL
00687 license as circulated by CEA, CNRS and INRIA at the following URL
00688 "http://www.cecill.info". 
00689 
00690 As a counterpart to the access to the source code and rights to copy,
00691 modify and redistribute granted by the license, users are provided only
00692 with a limited warranty and the software's author, the holder of the
00693 economic rights, and the successive licensors have only limited
00694 liability. 
00695 
00696 In this respect, the user's attention is drawn to the risks associated
00697 with loading, using, modifying and/or developing or reproducing the
00698 software by the user in light of its specific status of free software,
00699 that may mean that it is complicated to manipulate, and that also
00700 therefore means that it is reserved for developers and experienced
00701 professionals having in-depth computer knowledge. Users are therefore
00702 encouraged to load and test the software's suitability as regards their
00703 requirements in conditions enabling the security of their systems and/or 
00704 data to be ensured and, more generally, to use and operate it in the 
00705 same conditions as regards security. 
00706 
00707 The fact that you are presently reading this means that you have had
00708 knowledge of the CeCILL license and that you accept its terms.
00709 
00710 LICENSE END */
00711 
00712 
00713 
00714 
00715 
00716 
00717 /********************************************************************************************************/
00718 int utInvCalendar2_cal( int year, int month, int day, int hour, int minute, 
00719                 double second, ut_unit *unit, double *value, const char *calendar )
00720 {
00721 #if DEBUG > 7
00722         char            buf[1024];
00723 #endif
00724 
00725 #if DEBUG > 7
00726         dateify( year, month, day, hour, minute, second, buf );
00727         printf( "called utInvCalendar_cal with date to convert=%s\n", buf );
00728 #endif
00729 
00730 /*
00731         if( have_initted == 0 ) {
00732                 make_udu_origin_zero( unit );
00733                 have_initted = 1;
00734                 }
00735  */
00736         make_udu_origin_zero( unit );
00737 
00738         if( (calendar == NULL) || (strlen(calendar) == 0) || (strncasecmp(calendar,"standard",8)==0) || (strncasecmp(calendar,"gregorian",9)==0) ) {
00739 #if DEBUG > 7
00740                 printf( "called utInvCalendar_cal with a standard calendar\n" );
00741 #endif
00742                 return( utInvCalendar2( year, month, day, hour, minute, second, unit, value ));
00743                 }
00744 
00745         else if( (strncasecmp(calendar,"365_day",7)==0) || (strncasecmp(calendar,"noleap",6)==0) 
00746                 || (strncasecmp(calendar,"365",3) == 0) || (strncasecmp(calendar,"no_leap",7)==0)) {
00747 #if DEBUG > 7
00748                 printf( "called utInvCalendar_cal with a noleap calendar\n" );
00749 #endif
00750                 return( utInvCalendar2_noleap_inner( year, month, day, hour, minute, second, unit, value,
00751                         days_per_month_reg_year ));
00752                 }
00753 
00754         else if((strncasecmp(calendar,"360_day",7)==0) || (strncasecmp(calendar,"360",3)==0)) {
00755 #if DEBUG > 7
00756                 printf( "called utInvCalendar_cal with a 360_day calendar\n" );
00757 #endif
00758                 return( utInvCalendar2_noleap_inner( year, month, day, hour, minute, second, unit, value,
00759                         days_per_month_360 ));
00760                 }
00761         else
00762                 {
00763                 printf( "Sorry, %s calendar not implemented yet; using standard calendar\n", calendar );
00764                 return( utInvCalendar2( year, month, day, hour, minute, second, unit, value ));
00765                 }
00766 }
00767 
00768 /********************************************************************************************************/
00769 static int utInvCalendar2_noleap_inner( int year, int month, int day, int hour, int minute,
00770                 double second, ut_unit *user_unit, double *value, long *days_per_month )
00771 { 
00772         int     u_year, u_month, u_day, u_hour, u_minute, i, err,
00773                 yr0, yr1, mo0, mo1, dy0, dy1, hr0, hr1, mn0, mn1;
00774         double  u_second, tmp_tval, sec0, sec1;
00775         long    units_earlier, sep_seconds_i, ss_extra_i, sep_days, days_per_year, dd_extra;
00776         double  sep_seconds_f, ss_extra_f, sec_per_min, sec_per_hour, sec_per_day, unused;
00777         char    tmp_unit_str[1024];
00778         ut_unit *tmp_unit;
00779 #if DEBUG > 7
00780         char            buf[1024];
00781 #endif
00782 
00783         static ut_unit          *last_user_unit;
00784         static cv_converter     *conv_seconds_to_user_units=NULL;
00785         static cv_converter     *conv_days_to_user_units=NULL;
00786 
00787         sec_per_min = 60;
00788         sec_per_hour = sec_per_min * 60;
00789         sec_per_day  = sec_per_hour * 24;
00790 
00791         /* Get, into u_year, u_month, etc., the date passed in the units string */
00792         if( (err = udu_sec_since_ref_date( 0.0, user_unit, &u_year, &u_month, &u_day, &u_hour, &u_minute, &u_second, &unused )) != 0 )
00793                 return( err );
00794 #if DEBUG > 7
00795         dateify( u_year, u_month, u_day, u_hour, u_minute, u_second, buf );
00796         printf( "utInvCalendar_cal, date in passed units string: %s\n", buf );
00797 #endif
00798 
00799         /*--------------------------------------------------------------------
00800          * Find out which is earlier, the passed date or the unit's base date.
00801          * If they are the same, just return zero.
00802          *-------------------------------------------------------------------*/
00803         if( (u_year==year) && (u_month==month) && (u_day==day) && (u_hour==hour) && 
00804                         (u_minute==minute) && (u_second==second)) {
00805                 *value = 0.0;
00806                 return(0);
00807                 }
00808                 
00809         units_earlier = 1;
00810         if( u_year > year ) 
00811                 units_earlier = 0;
00812         else if( u_year == year ) {
00813                 if( u_month > month )
00814                         units_earlier = 0;
00815                 else if( u_month == month ) {
00816                         if( u_day > day )
00817                                 units_earlier = 0;
00818                         else if( u_day == day ) {
00819                                 if( u_hour > hour )
00820                                         units_earlier = 0;
00821                                 else if( u_hour == hour ) {
00822                                         if( u_minute > minute ) 
00823                                                 units_earlier = 0;
00824                                         else if( u_minute == minute ) {
00825                                                 if( u_second > second )
00826                                                         units_earlier = 0;
00827                                                 }
00828                                         }
00829                                 }
00830                         }
00831                 }
00832                 
00833         /* Put things in the early and late dates for ease, apply 
00834          * proper sign at the end */
00835         if( units_earlier ) {
00836                 yr0 = u_year;
00837                 yr1 = year;
00838                 mo0 = u_month;
00839                 mo1 = month;
00840                 dy0 = u_day;
00841                 dy1 = day;
00842                 hr0 = u_hour;
00843                 hr1 = hour;
00844                 mn0 = u_minute;
00845                 mn1 = minute;
00846                 sec0 = u_second;
00847                 sec1 = second;
00848                 }
00849         else
00850                 {
00851                 yr1 = u_year;
00852                 yr0 = year;
00853                 mo1 = u_month;
00854                 mo0 = month;
00855                 dy1 = u_day;
00856                 dy0 = day;
00857                 hr1 = u_hour;
00858                 hr0 = hour;
00859                 mn1 = u_minute;
00860                 mn0 = minute;
00861                 sec1 = u_second;
00862                 sec0 = second;
00863                 }
00864 
00865         /* This is the total separation between the early and
00866          * late dates, in seconds plus days.  We split out
00867          * integer seconds and fracitonal seconds to better 
00868          * address rounding issues.
00869          */
00870         sep_seconds_i = 0;
00871         sep_seconds_f = 0.0;
00872         sep_days      = 0;
00873 
00874         /* Wind the early date back to Jan 1 of that year for ease of computation */
00875         ss_extra_i = 0;
00876         ss_extra_f = 0.0;
00877         dd_extra   = 0;
00878         if( (mo0 != 1) || (dy0 != 1) || (hr0 != 0) || (mn0 != 0) || (sec0 != 0.0)) {
00879                 
00880                 ss_extra_i = floor( sec0 );
00881                 ss_extra_f = sec0 - floor( sec0 );
00882                 sec0 = 0.0;
00883 
00884                 ss_extra_i += mn0 * sec_per_min;
00885                 mn0 = 0;
00886 
00887                 ss_extra_i += hr0 * sec_per_hour;
00888                 hr0 = 0;
00889 
00890                 dd_extra += dy0 - 1;
00891                 dy0 = 1;
00892 
00893                 while( mo0 > 1 ) {
00894                         dd_extra += days_per_month[ mo0-2 ];  /* -2 cuz -1 for prev month, -1 for 0 offset */
00895                         mo0--;
00896                         }
00897                 }
00898 
00899         days_per_year = 0;
00900         for( i=0; i<12; i++ )
00901                 days_per_year += days_per_month[i];
00902 
00903         /* Add up all the years that separate our early and late dates.  We
00904          * use days rather than seconds because we can overflow seconds in
00905          * fairly routine calculations involving century timescales. 
00906          */
00907         sep_days += (yr1-yr0) * days_per_year;
00908 
00909         /* Now just add up days from beginning of the year to our
00910          * later target date...
00911          */
00912         sep_days += dy1 - 1;
00913         while( mo1 > 1 ) {
00914                 sep_days += days_per_month[ mo1-2 ];    /* -2 cuz -1 for prev month, -1 for 0 offset */
00915                 mo1--;
00916                 }
00917 
00918         sep_seconds_i += floor( sec1 );
00919         sep_seconds_f =  sec1 - floor(sec1);
00920         sep_seconds_i += mn1*sec_per_min + hr1*sec_per_hour;    /* note: days taken care of just above! */
00921 
00922         /* Correct for the unwinding of the base ref date to Jan 1st */
00923         sep_seconds_f -= ss_extra_f;
00924         sep_seconds_i -= ss_extra_i;
00925         sep_days      -= dd_extra;
00926 
00927         /* We now have the correct number of days and seconds separating
00928          * our early and late dates: sep_days and (sep_seconds_i + sep_seconds_f)
00929          */
00930 #if DEBUG > 7
00931         printf( "utInvCalendar2_noleap_inner: dates differ by %ld days, and (%ld + %lf) seconds\n", sep_days, sep_seconds_i, sep_seconds_f );
00932 #endif
00933 
00934         /* Now convert our days/seconds since the user's reference date to the units required 
00935          * by the user. Note that we go through this whole days/seconds rigormarole so
00936          * that we don't overflow seconds when doing century-scale conversions.
00937          */
00938         if( (last_user_unit == NULL) || (ut_compare(last_user_unit, user_unit) != 0)) {
00939 
00940                 /* Get converter, seconds to user units */
00941                 sprintf( tmp_unit_str, "seconds since %04d-%02d-%02d %02d:%02d:%.12lf", 
00942                         u_year, u_month, u_day, u_hour, u_minute, u_second );
00943                 if( (tmp_unit = ut_parse( ut_get_system(user_unit), tmp_unit_str, UT_ASCII )) == NULL ) {
00944                         fprintf( stderr, "Internal error in routine utInvCalendar2_noleap_inner: could not parse string \"%s\"\n",
00945                                 tmp_unit_str );
00946                         exit(-1);
00947                         }
00948                 if( conv_seconds_to_user_units != NULL )
00949                         cv_free( conv_seconds_to_user_units );
00950                 if( (conv_seconds_to_user_units = ut_get_converter( tmp_unit, user_unit )) == NULL ) {
00951                         err_mess_not_t_convertible( user_unit );
00952                         return( UT_EINVALID );
00953                         }
00954                 ut_free( tmp_unit );
00955 
00956                 /* Get converter, days to user units */
00957                 sprintf( tmp_unit_str, "days since %04d-%02d-%02d %02d:%02d:%.12lf", 
00958                         u_year, u_month, u_day, u_hour, u_minute, u_second );
00959                 if( (tmp_unit = ut_parse( ut_get_system(user_unit), tmp_unit_str, UT_ASCII )) == NULL ) {
00960                         fprintf( stderr, "Internal error in routine utInvCalendar2_noleap_inner: could not parse string \"%s\"\n",
00961                                 tmp_unit_str );
00962                         exit(-1);
00963                         }
00964                 if( conv_days_to_user_units != NULL )
00965                         cv_free( conv_days_to_user_units );
00966                 if( (conv_days_to_user_units = ut_get_converter( tmp_unit, user_unit )) == NULL ) {
00967                         err_mess_not_t_convertible( user_unit );
00968                         return( UT_EINVALID );
00969                         }
00970                 ut_free( tmp_unit );
00971 
00972                 if( last_user_unit != NULL )
00973                         ut_free( last_user_unit );
00974                 last_user_unit = ut_clone( user_unit );
00975                 }
00976 
00977         tmp_tval = (double)sep_seconds_i + sep_seconds_f;
00978         *value = cv_convert_double( conv_seconds_to_user_units, tmp_tval );
00979 
00980         tmp_tval = sep_days;
00981         *value += cv_convert_double( conv_days_to_user_units, tmp_tval );
00982 
00983         /* Apply sign */
00984         if( ! units_earlier )
00985                 *value = -(*value);
00986 
00987 #if DEBUG > 7
00988         printf( "utInvCalendar2_noleap_inner: that separation in users units is %lf\n", *value );
00989 #endif
00990         return(0);
00991 }
00992 

Generated on 12 May 2016 for DSCLIM by  doxygen 1.6.1