/* ioreadrec_routines WJS Jul 96 */ /* Contains externally referenced routines: */ /* void calc_latlon(valarr,valstrlen,level,outlon,inlon,outlat,inlat) */ /* void calc_timedate(valarr,valstrlen,level,in,out,fragbuflist) */ /* void standardize_missing_values (val,missings) */ /* void coordinate_missing_values */ /* (varnum,valarr,valstrlen,dependencies) */ /* called by ioreadrec_ (& only by ioreadrec_) */ /* */ /* This routine logically uses the tabvalues array from defgb */ /* This is an array of character strings. Since the size of each */ /* string is method-dependent, if we used that constant here we */ /* would need to produce a version of this per method. Therefore, */ /* we do our own array arithmetic here. Better soln would be to */ /* replace tabvalues with an array of pointers... */ #define IOREADREC_ROUTINES_VERSION \ "ioreadrec_routines version 2.1 22 Jun 2017" /* 22 Jun 17. 2.1 */ /* For ISO time output, don't put out decimal seconds unless */ /* input has decimal seconds. Issue is "really" about con- */ /* verting seconds to decimal minutes and back again, but this */ /* mod should deal w/that and be cleaner in general */ /* Bug fix: .00 frac of sec was being treated as whole sec from */ /* a precision perspective */ /* [Begin 2.1] */ /* 5 Jun 13. 2.0 */ /* Allow some vars to be missing if other vars are missing */ /* 4 Jun 13. 2.0 */ /* Convert user spec'd values into JGOFS missing values */ /* [Begin 2.0] */ /* 15 Feb 13. 1.8f */ /* Bug fix: don't calc ISO time if any of its pieces are missing */ /* [Begin 1.8f] */ /* 18 Oct 12. 1.8e */ /* Don't quit if input precision exceeds max output precision */ /* Round instead */ /* [Begin 1.8e] */ /* 9 Oct 12. 1.8d */ /* ISO 8601 output format */ /* [Needs defgb.h 5.5b] */ /* [Begin 1.8d] */ /* 21 Sep 11. 1.8c */ /* Bug fix: td_carryover did not honor the "special" borrowval */ /* flag in one "direction" - just used it as a datum instead! */ /* Symptom is date of max+2; eg, 33 May */ /* [Begin 1.8c] */ /* 8 Feb 11. 1.8b */ /* Bug fix: 1.8a wrong - units should be set to "units - 10" */ /* if we "roll over a 10" */ /* [Begin 1.8b] */ /* 6 Mar 09. 1.8a */ /* Bug fix: td_carryover should set "units" to their min when */ /* rolling forward, not unconditionally to 0 */ /* [Begin 1.8a] */ /* 23 Apr 07. 1.8 */ /* Looks like we want BOTH cum seconds and cum days... and a */ /* yyyymmhh option, too */ /* ok_to_calc; ok_to_use moved back here from defgb.c */ /* Bug: julian output should NOT incorporate DST. Not addressed */ /* except to pass DST_FRAG around, since fix will need that */ /* [Begin 1.8] */ /* 23 Aug 05. 1.7 */ /* Decided new "Cumulative seconds" should be "Cumulative days" */ /* 10 Aug 05. 1.7 */ /* Time/date option for Cumulative seconds since 1 Jan 1970 */ /* [Begin 1.7] */ /* */ /* [Comments about versions 1.0->1.6 in defgb_revision.doc as of ] */ /* [ 4 Oct 12 WJS ] */ /* 1 Aug 1996 [version 1.0 in use w/test ver of defgb 2.5] */ /* */ #include "defgb.h" /* Uses no global variables. Globals defined here only used here */ /* Needs following functions */ void err(),errn(); int iovarlevel__(); Logical missing(),get_logical_from_string(); Logical is_a_leap_year(); Logical legal_varnum(); Logical legal_level(); char *strdupl(),*buildstring(),*nxttok(),*lookup_wjstbl(); double fabs(),pow(),modf(); char *version = IOREADREC_ROUTINES_VERSION; static int cumdays[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; static char *monthnames[12] = {"january","february","march","april","may","june", "july","august","september","october","november","december"}; /*************** */ Logical comparison (comp_switch,val1,val2) int comp_switch,val1,val2; { switch (comp_switch) { case IDMISSING_DATUM_LT_VAL: return (val1 < val2); case IDMISSING_DATUM_LE_VAL: return (val1 <= val2); case IDMISSING_DATUM_EQ_VAL: return (val1 == val2); case IDMISSING_DATUM_GE_VAL: return (val1 >= val2); case IDMISSING_DATUM_GT_VAL: return (val1 > val2); default: errn ("Internal error: unknown idmissing comparison ",comp_switch); } } Logical fcomparison (comp_switch,val1,val2,tolerance) int comp_switch; double val1,val2,tolerance; { switch (comp_switch) { case IDMISSING_DATUM_LT_VAL: return (val1 < val2); case IDMISSING_DATUM_LE_VAL: return (val1 <= val2); case IDMISSING_DATUM_EQ_VAL: return (fabs(val1-val2) < tolerance); case IDMISSING_DATUM_GE_VAL: return (val1 >= val2); case IDMISSING_DATUM_GT_VAL: return (val1 > val2); default: errn ("Internal error: unknown idmissing comparison ",comp_switch); } } void standardize_missing_values (datum,missings) /* See if datum is "in" missings, and, if so, change it to nd */ char *datum; struct idmissing *missings[]; { struct idmissing *next; struct idmissing **list; char errbuf[2] = {'\0','\0'}; int idatum,strcmp_result; double fdatum; Logical replace,ok; list = missings; while (*list != NULL) { /* If datum is already missing, no point in doing any work */ /* Must be inside loop in case, in string of conversions, an */ /* early one creates an "nd" */ if (strcmp(datum,MISSING_VALUE_STRING) == 0) return; replace = FALSE; next = *list; switch (next->datatype) { case IDMISSING_DATATYPE_CHAR: strcmp_result = (next->caseblindcomparison) ? strcasecmp_wjs(datum,next->val_as_string) : strcmp(datum,next->val_as_string); replace = comparison ( next->comparison, strcmp_result, 0 ); break; case IDMISSING_DATATYPE_LONG: GET_INTEGER_FROM_STRING(idatum,datum,ok); if (ok) { if (next->do_comparison_test) replace=comparison( next->comparison, idatum, next->val_as_long ); } else { if (next->notdecodable_action == IDMISSING_NOTDECODABLE_ABORT) err ("Datum cannot be decoded as integer to see if it should \ be considered missing. Datum = ",datum); replace = (next->notdecodable_action == IDMISSING_NOTDECODABLE_MISSING); } break; case IDMISSING_DATATYPE_FLOAT: GET_NUMBER_FROM_STRING(fdatum,datum,ok); if (ok) { if (next->do_comparison_test) replace = fcomparison (next->comparison, fdatum, next->val_as_double, next->tolerance); } else { if (next->notdecodable_action == IDMISSING_NOTDECODABLE_ABORT) err ("Datum cannot be decoded as floating point to see if it \ should be considered missing. Datum = ",datum); replace = (next->notdecodable_action == IDMISSING_NOTDECODABLE_MISSING); } break; default: errbuf[0] = next->datatype; err ("Internal error: unknown idmissing datatype ",errbuf); } /* Next must be inside loop so that we break out after ANY rule */ /* sets a datum to missing. (Break out is at top of loop) */ if (replace) strcpy(datum,MISSING_VALUE_STRING); list++; } return; } void coordinate_missing_values (dependent_varnum,data,size_datum,dependencies) int dependent_varnum,size_datum; char *data; int *dependencies; /* Independent vars must be read in at or before level that holds */ /* dependent var. Rely on initialization stuff to guarantee that */ { int i; int independent_varnum; char *independent_datum,*dependent_datum; /* NVAR should really be MAX_DEPENDENCIES and coordinated w/ val */ /* in ioopen_routines. On the other hand, doing this as a fixed */ /* len loop instead of just looking for ILLEGAL_VARNUM is just to */ /* get an extra test of the list integrity. Since we're being so */ /* nice, why make us do more work? */ for (i=0; i 3 fields. */ /* Overwrites str (& returns pointer to somewhere in it (if not NULL) */ char *str_orig,*sepstr; { char *start_deg,*start_min,*start_cardinal,*decpt,*end; char leading_zeros[3] = {'\0','\0','\0'}; static char errbuf[80]; /* Too lazy to count */ static int str_copy_len = 0; static char *str_copy = NULL; if (*sepstr == '\0') return strdupl(&end,str_orig,"latlon copy1"); /* We never free this buffer - since input is really fixed length */ /* it's logically something we could have statically allocated */ /* anyway. Even if input was dynamic, seems better to just keep */ /* expanding rather than reallocating every time... */ if (strlen(str_orig) + 2 > str_copy_len) { str_copy_len = strlen(str_orig) + 2; str_copy = (char *)realloc(str_copy,str_copy_len + 1); if (str_copy == NULL) { sprintf (errbuf,"Failed to get %d (0x%X) bytes of memory\n", str_copy_len,str_copy_len); err (errbuf,"attempting to copy latlon input field"); } } strcpy(str_copy,str_orig); if ( (start_deg = nxttok(str_copy,sepstr,&end,NULL,FALSE,FALSE)) == NULL ) /* No string at all (well, no non-seps in string) */ return strdupl(&end,str_orig,"latlon copy2"); /* Haven't learned much - might just have skipped leading seps */ if ( (start_min = nxttok(end,sepstr,&end,NULL,FALSE,FALSE)) == NULL ) /* All one string anyway */ return strdupl(&end,start_deg,"latlon copy3"); start_cardinal = nxttok(end,sepstr,&end,NULL,FALSE,FALSE); if (end != NULL) return NULL; /* Too many fields */ /* Put in 1 or 2 leading 0s or leave at default of none */ if ( (decpt = strchr(start_min,'.')) != NULL ) { if (decpt <= start_min+1) leading_zeros[0] = '0'; if (decpt == start_min) leading_zeros[1] = '0'; } if (start_cardinal != NULL) strcat(start_min,start_cardinal); return buildstring(start_deg,leading_zeros,start_min,"latlon copy4"); } void reformat_latlon (outstr,maxlenoutstr,instr_orig,outstruct,instruct,cardinal) /* Name of this routine getting stretched as functionality added */ /* This routine gets the cardinal, if any, off all lat/lons. In */ /* doing this, it calls newform_latlon_to_old (which is a re-format */ /* of its own). Then this routine handles any minute->decimal deg, */ /* etc reformatting if necessary */ char *instr_orig,*outstr,*cardinal; struct latlonformat *instruct,*outstruct; int maxlenoutstr; { char *ptr,*ptr2,*instr; char deg[5]; /* Sign; up to 3 digs; null */ int inlen,deglen; int sig_min_digs; /* Significant digits in minute portion of inp arg */ double f,g; /* General form of lat/lons: 1 or 2 numeric strings (1st possibly */ /* signed) followed by an optional cardinal compass point (as its */ /* own field or concatenated w/numeric field). We only look at */ /* 1st letter of compass point and allow more chars after */ /* Call routine to remove any embedded separators first (hist- */ /* orical reasons - orig program did not allow embedded seps). */ /* Reformatting may add up to 2 characters - allow for that */ if ( (instr = newform_latlon_to_old(instr_orig,instruct->sep)) == NULL ) err ("Too many fields in input lat/lon string. String = ", instr_orig); /* At this point, field should be number w/opt trailing compass */ /* Remove compass point. Leading separators should be removed */ /* but trailing might not be, so process from left to right. */ ptr = instr + strspn(instr,"0123456789+-."); if (*ptr == '\0') *cardinal = instruct->convention; else if (strchr(instruct->legal_cardinal,*ptr) == NULL) err ("Illegal compass point in lat/lon string. String = ", instr_orig); else { *cardinal = toupper(*ptr); *ptr = '\0'; } if (strcmp(instruct->format,outstruct->format) == 0) return; if ( (strcmp(instruct->format,"degdecmin") == 0) && (strcmp(outstruct->format,"decdeg") == 0) ) { /* degdecmin format assumed to be opt sign, up to 3 digits of */ /* degrees, up to 2 digits of whole minutes, optional decimal pt */ /* w/fractional minute, optional trailing compass point. */ /* Separate degrees from minutes, */ /* turn both into floating pt, add, and reconvert to string. If */ /* any characters found in either portion of string, return nd */ inlen = strlen(instr); if ( (ptr = strchr(instr,'.')) == NULL ) { ptr = instr + inlen; sig_min_digs = 0; } else sig_min_digs = -1; if ( (ptr = ptr - 2) <= instr ) ptr = instr; deglen = ptr-instr; if ( deglen > maxlenoutstr) err ("Output lat/lon not big enough to accept input lat/lon degrees\ \n Input lat/lon = ",instr_orig); if (deglen == 0) f = strtod(ptr,&ptr2) / 60.; else { /* Copy to temp buffer and strtod instead of sscanf w/width */ /* to simplify figuring out if there are characters in string */ /* (requiring an nd return). Don't use outstr because of po- */ /* tential overlap */ strncpy (deg,instr,deglen); deg[deglen] = '\0'; f = strtod(deg,&ptr2); if (*ptr2 == '\0') { g = strtod(ptr,&ptr2) / 60.; (f >= 0) ? (f += g) : (f -= g); } } if (*ptr2 == '\0') { /* Generally, number of significant digits is same in input & */ /* and output (60ths of a degree vs 100ths of a degree */ /* Assume that if no decimal minutes, there are whole minutes */ /* Decimal pt or lack thereof already taken care of */ sig_min_digs += (instr + inlen - ptr); if (sig_min_digs < 2) sig_min_digs = 2; /* +1, -1 for decimal point */ if ((deglen + sig_min_digs + 1) > maxlenoutstr) sig_min_digs = maxlenoutstr - deglen - 1; sprintf (outstr,"%.*f",sig_min_digs,f); } else strncpy(outstr,MISSING_VALUE_STRING,maxlenoutstr); } else err ( buildstring("No code to do ",instruct->format," to ", "building reformat_latlon msg"), buildstring(outstruct->format," latlon conversion",NULL, "building reformat_latlon msg") ); if (instr != NULL) free (instr); return; } void negate_string (outstr,maxlenoutstr,instr) char *outstr,*instr; int maxlenoutstr; /* Negate a (presumably) numeric string w/string operations */ /* Drop digits if input string too long */ { char *out_ptr,*in_ptr,*copy_instr; int outlen; /* Make copy of input string to avoid potential problems w/over- */ /* lapping outstr & instr */ in_ptr = strdupl(©_instr,instr,"in negate_string") - 1; out_ptr = outstr; outlen = maxlenoutstr; while (isspace(*++in_ptr)) ; switch (*in_ptr) { case '\0': err ("Numeric string is all white space",""); case '+': out_ptr++; outlen--; /* Deliberately fall through here */ case '-': while (isspace(*++in_ptr)) ; if (*in_ptr == '\0') err ("Numeric string has no digits after sign. String = ",instr); break; default: out_ptr++; outlen--; break; } strncpy(out_ptr,in_ptr,outlen); if (outstr != out_ptr) *outstr = '-'; free (copy_instr); return; } void calc_latlon(valarr,valstrlen,level,outlon,inlon,outlat,inlat) int level,valstrlen; char *valarr; struct latlonformat *inlon,*outlon,*inlat,*outlat; { int vallen; char *inbuf,*outbuf,cardinal; vallen = valstrlen - 1; if (ok_to_calc(outlat->var,level)) { outbuf = valarr + valstrlen*outlat->var; if (ok_to_use(inlat->var,level)) { inbuf = valarr + valstrlen*inlat->var; reformat_latlon (outbuf,vallen,inbuf,outlat,inlat,&cardinal); if (strcmp (outbuf,MISSING_VALUE_STRING) != 0) { if (cardinal == '\0') err ("Don't know which direction is positive for lat ",inbuf); if (cardinal != outlat->convention) negate_string (outbuf,vallen,inbuf); } } else strcpy (outbuf,MISSING_VALUE_STRING); } if (ok_to_calc(outlon->var,level)) { outbuf = valarr + valstrlen*outlon->var; if (ok_to_use(inlon->var,level)) { inbuf = valarr + valstrlen*inlon->var; reformat_latlon (outbuf,vallen,inbuf,outlon,inlon,&cardinal); if (strcmp (outbuf,MISSING_VALUE_STRING) != 0) { if (cardinal == '\0') err ("Don't know which direction is positive for lon ",inbuf); if (cardinal != outlon->convention) negate_string (outbuf,vallen,inbuf); } } else strcpy (outbuf,MISSING_VALUE_STRING); } return; } void calc_juloutput(outjul,outjulfrac,outjulprecision, year,month,day,hour,minute,fracminute,dst,fracdigits) struct outtime *outjul; double *outjulfrac; int *outjulprecision; struct outtime *year,*month,*day,*hour,*minute,*fracminute,*dst; int fracdigits; /* Make best julian output we can. If integer, return in outjul.val; */ /* otherwise return in *outjulfrac (along w/a precision) */ /* Because we insisted that julian output is at the lowest level of */ /* all time/date stuff, we're missing if any time/date info has been */ /* described as being in object but the value for that info is missing */ /* We further insisted that month/day info (and year for frac year) */ /* are in object, so we don't have to check that */ { #define SECONDS_PER_DAY 86400. double f; int days,secs,days_from_base_year,i; Logical leapyear; /* Vals taken by smallest_input_piece are just flags */ int smallest_input_piece; /* Possible values of outjul.conv. This was tested in ioopen... assume it stayed OK (hope, hope) E days-since-base-year 'v' type g fraction of year 'j' type G fraction of year 'J' type j day of the year 1-366 J day of the year 1-365 (no 29 Feb in any year) K fraction of year 'v' type P fraction of year 'V' type s secs-since-base-year 'v' type v day of the year 0-365 V day of the year 0-364 (no 29 Feb in any year) */ /* As of v 1.8, this routine does not return values in outjul->val */ /* (values are returned in outjulfrac instead). However, ->val is */ /* used by other routines as (at least) a "missing values" flag, so */ /* set it appropriately. Would use ! MISSING_TIME_FLAG if I had */ /* faith regarding negative numbers, etc */ outjul->val = 1; /* Year, if appropriate */ if (year->val == MISSING_TIME_FLAG) { switch (outjul->conv) { case 'g': case 'G': case 'K': case 'P': case 's': case 'E': outjul->val = MISSING_TIME_FLAG; return; } /* Set this so that "no 29 Feb" formats work in "no year" */ /* environment */ leapyear = FALSE; } else { /* We need to recalc leap year in case td_carryover put us */ /* in an earlier year. */ leapyear = is_a_leap_year(year->val); } days_from_base_year = 0; if ((outjul->conv == 'E') || (outjul->conv == 's')) { if (year->val < outjul->baseyear) errn ("Year value before start of epoch. Value: ",year->val); for (i = outjul->baseyear; i < year->val; i++) { days_from_base_year += 365; if (is_a_leap_year(i)) days_from_base_year++; } } if ( (month->val == MISSING_TIME_FLAG) || (day->val == MISSING_TIME_FLAG) ){ outjul->val = MISSING_TIME_FLAG; return; } days = days_from_base_year + cumdays[month->val-1] + day->val; /* "Do" leap year */ switch (outjul->conv) { case 'j': case 'g': case 'v': case 'K': case 'E': case 's': /* Add a day if it's leap year and we're after Feb. */ if ( leapyear && (month->val >= 3) ) ++days; } /* If we're starting from day 0, subtract a day */ switch (outjul->conv) { case 'K': case 'P': case 'v': case 'V': case 'E': case 's': --days; } smallest_input_piece = DAY_FRAG; /* Have days. Now compute fraction of day, if any, in seconds */ secs = 0; /* Things like minute-info-wo-hour-info are already reflected in */ /* an hour value of missing */ if (*hour->source_field_type != '\0') { secs = (hour->val == MISSING_TIME_FLAG) ? MISSING_TIME_FLAG : 60*60 * hour->val; smallest_input_piece = HOUR_FRAG; } if ( (*minute->source_field_type != '\0') && (secs != MISSING_TIME_FLAG) ) { secs += (minute->val == MISSING_TIME_FLAG) ? MISSING_TIME_FLAG : 60 * minute->val; smallest_input_piece = MINUTE_FRAG; } f = 0; /* Don't make Julian missing if fracminute is missing. Turns out */ /* we set it that way if the precision says that we won't be */ /* printing it. That decision probably an error, but not */ /* addressed at this point (defgb 3.8) */ if ( (*fracminute->source_field_type != '\0') && (fracminute->val != MISSING_TIME_FLAG) && (secs != MISSING_TIME_FLAG) ) { /* fracminute is integer valued ... */ /* Too bad fracminute wasn't saved as seconds in the first */ /* place... However NOBODY was interested in seconds until */ /* the seconds-from-epoch stuff, and here we are in the 11th */ /* significant digit and beyond */ /* Seconds are happening yet again w/ISO8601 ... */ f = 60. * (double)fracminute->val/pow(10.,(double)fracdigits); smallest_input_piece = FRACTION_FRAG; } if (secs == MISSING_TIME_FLAG) { outjul->val = MISSING_TIME_FLAG; return; } /* Precisions here mean "digits to right of decimal point" */ /* Values obtained by computing 1 Nth, expressing it as */ /* L.yyy x 10**M where 0 < L < 9, and using -M */ switch (outjul->conv) { case 's': *outjulfrac = f + (double)secs + ((double)days) * SECONDS_PER_DAY; /* Assuming fracdigits was OK, we multiplied its fraction */ /* by 60, so we need to reduce the precision by either -1 or */ /* -2. Empirically, -1 didn't seem to work, so try -2. */ /* (If fooling w/this, see also both case FRACTION_FRAGs, below */ *outjulprecision = (smallest_input_piece == FRACTION_FRAG) ? fracdigits - 2 : 0; break; case 'G': case 'P': case 'g': case 'K': f = (f + (double)secs) / SECONDS_PER_DAY; switch (outjul->conv) { case 'g': case 'K': *outjulfrac = (leapyear) ? (f + (double)days)/366. : (f + (double)days)/365.; break; case 'G': case 'P': *outjulfrac = (f + (double)days)/365.; break; } *outjulfrac += (double)year->val; switch (smallest_input_piece) { case DAY_FRAG: *outjulprecision = 3; break; case HOUR_FRAG: *outjulprecision = 4; break; case MINUTE_FRAG: *outjulprecision = 6; break; case FRACTION_FRAG: /* See case 's' above for info about the -2 */ *outjulprecision = 8 + fracdigits - 2; break; default: errn("Internal defgb problem. Bad flag in calc_juloutput. Flag: ", smallest_input_piece); break; } break; default: switch (smallest_input_piece) { case DAY_FRAG: *outjulfrac = (double)days; *outjulprecision = 0; break; case HOUR_FRAG: *outjulfrac = (double)days + ((double)secs) / SECONDS_PER_DAY; *outjulprecision = 2; break; case MINUTE_FRAG: *outjulfrac = (double)days + ((double)secs) / SECONDS_PER_DAY; *outjulprecision = 4; break; case FRACTION_FRAG: *outjulfrac = (double)days + (f + (double)secs) / SECONDS_PER_DAY; /* See case 's' above for info about the -2 */ *outjulprecision = 5 + fracdigits - 2; break; default: errn("Internal defgb problem. Bad flag in calc_juloutput. Flag: ", smallest_input_piece); break; } break; } return; } void get_timedate_frag (vec,buffer,format,index,expected_in_items,n_out_copies) char *vec[],*buffer,*format; int expected_in_items,index,n_out_copies; /* Do a sscanf from buffer to vec according to format. # items in */ /* format assumed to match length of vec, but we don't know how many. */ /* Hence, set up to decode potentially beyond length of vec, relying */ /* on sscanf to stop using vec once it's decoded everything (and */ /* relying on format to be correct!). Entries in vec are pointers */ /* 14 is number of items in decode list below. In includes an */ /* "extra" item, hence the -1 */ #if NUM_TIMEDATE_FRAGS != 14 - 1 #error "Incorrect number of decode items in get_timedate_frag" #endif { int i; char *tmp,*errtmp; char scratch[2]; if (strcmp(buffer,MISSING_VALUE_STRING) == 0) for (i=0; i 1) if (expected_in_items > 1) err ("defgb internal error: multiple ", "copies of input field can only come from single input field"); else for (i = 1; i < n_out_copies; i++) strcpy(vec[i],vec[0]); return; } Logical set_monthday_from_julian(julday,cum) int julday; struct cumtime *cum; /* leapyear must be correctly set before using this routine */ /* Returns range check on julday */ { if (cum->leapyear) if (julday > 59) julday--; else if (julday == 59) { cum->month = 2; cum->day = 29; return TRUE; } if ( (0 <= julday) && (julday < 365) ) { for (cum->month = 1; cum->month <= 12; cum->month++) if (julday < cumdays[cum->month]) break; cum->day = julday - cumdays[cum->month-1] + 1; return TRUE; } else { cum->month = -1; cum->day = -1; return FALSE; } } signed char check_ampm(s) char *s; /* Check that string is either a or p, am or pm (ignoring case) */ /* Return a, p or -1 */ { char c; c = tolower(*s); if ( (c == 'a') || (c == 'p') ) switch (*(s+1)) { case 'm': case 'M': if (*(s+2) == '\0') return c; break; case '\0': return c; default: ; } return -1; } Logical ok_to_redefine (outtime,level) struct outtime *outtime; int level; { return ( (outtime->lev == level) && (outtime->val != MISSING_TIME_FLAG) ); } void td_carryover(units_struct,min,max,borrowval,tens_struct,level,feb29flag) struct outtime *units_struct,*tens_struct; int min,max,borrowval,level; Logical feb29flag; /* Do carryovers from a time/date field to the next bigger field */ /* if both fields are on this level and not missing. (Deliber- */ /* ately leave illegal fields if carryover not possible) */ /* Note that we assume a carryover of no more than 1; eg, we aren't */ /* asked to handle a situation where we have 2 hours, 150 minutes */ /* If we DID want to handle (or detect) this, the roll-forward */ /* logic would have to handle days-in-month (as the roll-backward */ /* logic is already obliged to do). We would be at a loss to */ /* determine what day in June corresponds to Apr 72 unless we */ /* knew how many days were in May */ /* Analogy to addition/subtraction, hence "units", "tens", etc */ /* min and max are permissible values. negative borrowval is */ /* special case flag for days->months, asking this routine to calc */ /* number of days last month if necessary. This to avoid the calc */ /* except when needed... */ /* Something stinky w/above discussion besides the fact that this */ /* routine kept on coming up w/wrong answers. Observe how routine */ /* is called and note that except for month special case, borrow- */ /* val is just the "range" (max - min +1). In the analogy to */ /* units/tens/etc, borrowval is the "base" of the "ten". Why */ /* are things more complicated than this? (Feb 11) */ { int last_month; if (ok_to_redefine(units_struct,level) && ok_to_redefine(tens_struct,level)) if (units_struct->val > max) { if (borrowval < 0) { /* Set up maximum # days for this month */ if ( (tens_struct->val == 2) && feb29flag ) borrowval = 29; else borrowval = cumdays[tens_struct->val] - cumdays[tens_struct->val - 1]; } units_struct->val -= borrowval; tens_struct->val++; } else if (units_struct->val < min) { if (borrowval < 0) { /* Set up maximum # days for last month */ last_month = (tens_struct->val == 1) ? 12 : tens_struct->val - 1; if ( (last_month == 2) && feb29flag ) borrowval = 29; else borrowval = cumdays[last_month] - cumdays[last_month-1]; } units_struct->val += borrowval; tens_struct->val--; } return; } int get_numeric_data(fragbuf,npieces_this_frag,frac,fracdigits) char *fragbuf; int npieces_this_frag; int *fracdigits; double *frac; /* Input is a character buffer containing numeric data, and a flag */ /* The flag indicates whether the format is integer or floating */ /* point and, if floating point, whether the decoded value is sup- */ /* posed to be integral. */ /* The integral portion of the decoded buffer is returned as the */ /* function value. If appropriate, the fractional portion and its */ /* precision are also returned. */ /* Not sure that negative numbers are appropriate as input, but */ /* assuming they are, always return fraction as positive, decre- */ /* menting the integer if necessary */ { double fabs(),modf(); double fint,fractemp; char *ptr; Logical startcount; int i; /* If npieces_this_frag <=1, fraction is by defn 0, so fracdigits */ /* is meaningless as a precision... */ if (frac != NULL) *frac = 0; if (fracdigits != NULL) *fracdigits = 0; if (npieces_this_frag == 0) return atoi(fragbuf); errno = 0; fractemp = modf(strtod(fragbuf,&ptr),&fint); /* That should be it, but life is seldom simple... */ /* Everything else is validity checking, etc. */ /* To start with, to allow E format input, we had to allow it */ /* (and other chars) in input. Be sure field wasn't EEE. Note */ /* we have to hope that E1 really meant 1.00E01... */ /* Check on errno is another protection on things like E1000000 */ if ( (*ptr != '\0') || (errno != 0) ) err ("Illegal floating input. Datum = ",fragbuf); /* Check that field that's supposed to be integral is. Constant */ /* I chose is a punt. DBL_EPSILON defined in */ if ( (npieces_this_frag == 1) && (fabs(fractemp) > 10.*DBL_EPSILON) ) err ("Time/date format/floating data mismatch.\n Datum = ",fragbuf); /* Make sure fraction is positive */ if (fint < 0.) { fractemp += 1.0; fint -= 1.0; i = fint-.5; } else i = fint+.5; if (frac != NULL) *frac = fractemp; if ( (fracdigits != NULL) && (npieces_this_frag > 1) ) { /* Precision of fractional part. Take a shot... */ /* I hope decimal points stay .s and don't become ,s */ ptr = strchr(fragbuf,'.'); if (ptr == NULL) { ptr = fragbuf; if ( (*ptr == '+') || (*ptr == '-') ) ptr++; } else ptr++; startcount = ( isdigit(*ptr) && (*ptr != '0') ); *fracdigits = 0; while (isdigit(*ptr)) { if ( startcount || (*ptr != '0') ) { startcount = TRUE; (*fracdigits)++; } ptr++; } } return i; } void calc_timedate(valarr,valstrlen,level,in,out,fragbuflist,cum,frac) int level,valstrlen; char *valarr; struct outtime out[]; struct intime in[]; char *fragbuflist[]; struct cumtime *cum; struct timefrac *frac; /* Do required time calculations. First get any fragments that have */ /* become valid at this level. Then compute any output fragments */ /* that have become valid. Then output any output variables */ { char *ptr,*ptr2,*in_ptr,*out_ptr,*output_ptr; static char *casebuf = NULL; /* Buffer for caseblind compares */ int i,j,k; int width,precision; div_t digs; int vallen; Logical data_missing,leapflag,output_is_UTC; Logical tzerr = FALSE; double ftmp; double outjulfrac; int outjulprecision; vallen = valstrlen - 1; if (casebuf == NULL) casebuf = (char *)malloc(valstrlen); if (casebuf == NULL) err ("Could not get memory for casebuf",""); /* Get any time/date fragments that became available because input */ /* variables specifying info are present on this level */ /* Treat fraction stuff special. Sigh */ frac->fractype = *out[FRACTION_FRAG].source_field_type; frac->fracyear = (frac->fractype == 'g') || (frac->fractype == 'G') || (frac->fractype == 'K') || (frac->fractype == 'P'); for (i = 0; i < NUM_TIMEDATE_FRAGS; i++) /* Test below is NOT ok_to_use because a) we don't want to recalc */ /* at levels after variable appears & b) get_timedate_frag needs */ /* missing data input so it can mark all dependent frags missing */ if (ok_to_calc(in[i].var,level)) { get_timedate_frag(&fragbuflist[in[i].fragbuf_index], valarr + valstrlen*in[i].var, in[i].format, i, in[i].nstrings, in[i].nfrags_in_substring); if (i == out[FRACTION_FRAG].intime_index) { /* See if we have a valid fraction fragment. */ /* Because it can be used at many levels below its appear- */ /* ance, we process it from character into binary when read, */ /* and put it and variables related to it in static variables */ /* so they are available when needed. Responsibility for */ /* fraction being present before it's needed is */ /* get_timedateparams' */ /* Since fraction is not a valid output variable, testing */ /* for validity by looking at the .var piece of the out struc- */ /* ture gives us nothing. Look at its source_field_type */ /* instead. */ /* If we have any kind of fraction, get it back to a */ /* real # < 1, and compute the precision of any final */ /* fractions of a minute. This precision can be negative. */ /* We use precision later to round if necessary. Turns out */ /* that the negative precision can be used if from the present */ /* calculations, but if precision is changed here, review the */ /* other places it's used. Bob wants minutes output no matter */ /* what the precision is, so that's why we "overlook" this */ /* issue */ if (frac->fractype != '\0') { if ( ! (frac->frac_missing = missing(in[i].var)) ) { in_ptr = out[FRACTION_FRAG].fragbuf; /* Fragment data */ if (*out[FRACTION_FRAG].source_field_type != '*') j = get_numeric_data (in_ptr, in[i].nfrags_in_substring, &frac->frac, &frac->fracdigits ); /* Data can come from integer or fractional part of input */ /* field. If latter, get_numeric_data has done numeric */ /* work. In fact, frac & fracdigits might have been */ /* calculated when decoding the integer part of the input */ /* field. Could use this, along with source_field_type = *, */ /* to avoid calling get_numeric_data at all, but this */ /* involves ioopen_routines work (see julian day treat- */ /* ment) and some thinking about saving the correct frac & */ /* fracdigits... */ if (in[i].nfrags_in_substring == 0) { /* Fraction is string w/o explicit decimal pt; leading */ /* decimal pt assumed. */ frac->fracdigits = strlen(in_ptr); ftmp = -frac->fracdigits; frac->frac = j * pow(10.,ftmp); } switch (frac->fractype) { case 'o': frac->fracdigits -= 3; /* .001 day = 1.4 min */ break; case 'O': frac->fracdigits -= 2; /* .01 hr = .6 min */ break; case 'q': /* 1 min = 1 min (!) */ break; case 'Q': frac->fracdigits += 2; /* 100 sec = 1.7 min */ break; default: frac->fracdigits -= 5; /* .00001 year = 5.3 min */ break; } } } } } /* Try to calculate any output fragments that are present on this */ /* level. Possible fractional and/or Julian inputs require */ /* processing in "size order". 2nd pass is then done to do rounding */ /* (& zone change?) 2nd pass is needed (and done in reverse "size */ /* order") because roundoff of smallest piece might alter values of */ /* bigger pieces. 3rd pass is then done to do any output. */ /* In general, i &/or j are temp values until i becomes the */ /* integer value of a fragment and j becomes the integer value */ /* of the output. j is also used as "output ok" flag */ /* Report errors when they occur; if errors are not logically */ /* fatal, set output string to missing (although I doubt we'll ever */ /* be able to continue even in the non-fatal case) */ /* Much validity work is done in get_timedateparams. In */ /* particular, we assume */ /* 1) formats for each fragment were correctly set; eg, if year */ /* fragment is not format y, it must be Y. Sometimes we check */ /* here anyway, if convenient */ /* 2) integer data consists only of integers & sign (done by */ /* extracting strings with [0-9,+,-] format specifier instead */ /* of s - bad strings should produce "format mismatch" errors */ /* in get_timedatefrag) */ /* 3) needed input variables exist in the dataset and have been */ /* read in by the time they're needed. Hence, instead of using */ /* ok_to_use, we can get away with just checking for missing */ /* Time displacement */ j = MISSING_TIME_FLAG; if (out[TIME_OFFSET_FRAG].lev == level) { i = out[TIME_OFFSET_FRAG].intime_index; /* Which input gave frag? */ if ( ! missing(in[i].var) ) { in_ptr = out[TIME_OFFSET_FRAG].fragbuf; /* Fragment data */ if (*out[TIME_OFFSET_FRAG].source_field_type == 'Z') { /* Time zone abbrev must be on regular or DST list. If on */ /* DST list, other DST input is a conflict */ /* Do caseblind compare of input. Tables are in upper case */ ptr = casebuf; ptr2 = in_ptr; while (*ptr2 != '\0') *(ptr++) = toupper(*(ptr2++)); *ptr = '\0'; if ( (ptr = lookup_wjstbl(casebuf,TZTBL)) == NULL ) if ( (ptr = lookup_wjstbl(casebuf,TZDSTTBL)) == NULL ) { err ("Time zone abbrev not on defgb's list. Zone = ",in_ptr); tzerr=TRUE; } else if (out[DST_FRAG].var != MISSING_TIME_FLAG) { err("Cannot spec DST input if time zone abbrev spec's DST\n \ Zone = ",in_ptr); tzerr=TRUE; } if (ptr != NULL) i = atoi(ptr); } else { if (*out[TIME_OFFSET_FRAG].source_field_type != '*') i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); if (*out[TIME_OFFSET_FRAG].source_field_type == 'z') i = -i; } if ( ! tzerr ) { if (abs(i) < 100) { /* HH -> HHMM */ /* Save pieces in case time conversion was requested */ /* Set up to add offset to utc to get local */ /* Setting to 0 for completeness only - code never gets here */ /* if NO_TIME_ZONE_CONVERSION */ if (out[HOUR_FRAG].conv == NO_TIME_ZONE_CONVERSION) cum->hours_offset = 0; else if (out[HOUR_FRAG].conv == CONVERT_UTC_TO_LOCAL) cum->hours_offset = i; else if (out[HOUR_FRAG].conv == CONVERT_LOCAL_TO_UTC) cum->hours_offset = -i; else errn ("Internal defgb error in calc_timedate\n Illegal time\ zone conversion flag value ",(long)out[HOUR_FRAG].conv); cum->minutes_offset = 0; i *= 100; } if (abs(i) <= 1200) { if (*out[DST_FRAG].source_field_type == '\0') j = i; else { /* DST processing. Displacement is in minutes */ in_ptr=out[DST_FRAG].fragbuf; if (*out[DST_FRAG].source_field_type == 'f') { k = out[DST_FRAG].intime_index; /* Whence this frag? */ k = get_numeric_data(in_ptr,in[k].nfrags_in_substring,NULL,NULL); } else { k = (get_logical_from_string(in_ptr,"Doing DST processing","")) ? 60 : 0; } if ( (0 <= k) && (k <= 120) ) { /* Do everything in minutes. Seems simpler */ /* Remainder has sign of quotient, simplifying things */ digs = div(i,100); /* Save pieces in case time conversion was requested */ /* Set up to add offset to utc to get local */ /* See comments above re: ->hours_offset about the */ /* setting-to-0 lines */ if (out[HOUR_FRAG].conv == NO_TIME_ZONE_CONVERSION) { cum->hours_offset = 0; cum->minutes_offset = 0; } else if (out[HOUR_FRAG].conv == CONVERT_UTC_TO_LOCAL) { cum->hours_offset = digs.quot; cum->minutes_offset = digs.rem; } else if (out[HOUR_FRAG].conv == CONVERT_LOCAL_TO_UTC) { cum->hours_offset = -digs.quot; cum->minutes_offset = -digs.rem; } else errn ("Internal defgb error in calc_timedate\n Illegal \ timezone conversion flag value ",(long)out[HOUR_FRAG].conv); j = 60*digs.quot + digs.rem + k; /* Minutes displacement */ digs = div(j,60); /* Rebuild */ j = 100*digs.quot + digs.rem; /* 4 dig time */ } else err ("Illegal DST flag or displacement ",in_ptr); } } else err ("Illegal UTC displacement ",in_ptr); } } if (j == MISSING_TIME_FLAG) { cum->hours_offset = 0; cum->minutes_offset = 0; } out[TIME_OFFSET_FRAG].val = j; } /* Calculate year. Y = year w/century; y = w or w/o. */ j = MISSING_TIME_FLAG; /* Not using ok_to_calc allows us to process fragments that have not */ /* been requested for output */ if (out[YEAR_FRAG].lev == level) { i = out[YEAR_FRAG].intime_index; /* Which input gave frag? */ if ( ! missing(in[i].var) ) { in_ptr = out[YEAR_FRAG].fragbuf; /* Fragment data */ i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); if (*out[YEAR_FRAG].source_field_type == 'y') if ( (0 <= i) && (i < 50) ) i += 2000; else if ( (50 <= i) && (i <= 99) ) i += 1900; if ( (YEAR_MIN <= i) && (i <= YEAR_MAX) ) { j = i; if (j != cum->year) /* Test is just to avoid recalc */ cum->leapyear = is_a_leap_year(j); } else err ("Illegal year value ",in_ptr); } out[YEAR_FRAG].val = j; cum->year = j; } /* Calculate month. b, B = month name */ /* m = # */ /* j, J, v, V = Julian day */ /* g, G, K, P = fraction of year */ /* JULIAN_SOURCES = output */ /* previously calculated from Julian */ /* days when processing day output. Do not */ /* confuse with INPUT previously converted */ /* from fragment buffer, which happens when */ /* Julian days are created from fractional yrs*/ j = MISSING_TIME_FLAG; if (out[MONTH_FRAG].lev == level) { /* Get input month data from appropriate string or from */ /* julian day that came from fractional year */ if (frac->fracyear) { if ( ! (data_missing = frac->frac_missing) ) { if (frac->fractype != 'o') { /* Convert fraction of year, if any, to julian day */ ftmp = ( cum->leapyear && ((frac->fractype == 'g') || (frac->fractype == 'K')) ) ? 366. : 365.; frac->frac = modf(ftmp*frac->frac,&ftmp); /* Julian days to julday; */ frac->julday = ftmp + .1; /* rest back to frac */ frac->fractype = 'o'; /* What's left will supply fraction of day */ /* Do not confuse w/ date fragment source */ /* field type, which remains g, G, K, P, etc */ /* fractype indicates what type of data is in */ /* frac at any point */ } i = frac->julday; } } else { i = out[MONTH_FRAG].intime_index; /* Which input gave frag? */ if ( ! (data_missing = missing(in[i].var)) ) { in_ptr = out[MONTH_FRAG].fragbuf; /* Fragment data */ switch (*out[MONTH_FRAG].source_field_type) { case JULIAN_SOURCES: case 'b': case 'B': break; default: i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); break; } } } /* Process input month data to output month data */ if ( ! data_missing ) { switch (*out[MONTH_FRAG].source_field_type) { case 'm': if ( (1 <= i) && (i <= 12) ) j = i; else err ("Illegal month value ",in_ptr); break; case 'j': case 'g': /* Starts from day 1 */ i--; case 'v': case 'K': /* Starts from day 0 */ /* Missing year = missing Julian day */ if (cum->year != MISSING_TIME_FLAG) { if (set_monthday_from_julian(i,cum)) j = cum->month; else err ("Illegal Julian value ",in_ptr); } break; case 'J': case 'G': /* 365 day yr (no 29 Feb); starts from day 1 */ i--; case 'V': case 'P': /* 365 day yr (no 29 Feb); starts from day 0 */ if (cum->year == MISSING_TIME_FLAG) cum->leapyear = FALSE; /* If leapyear, make 29 Feb into 1 Mar, etc */ if ( cum->leapyear && (i >= 59) ) i++; if (set_monthday_from_julian(i,cum)) j = cum->month; else err ("Illegal Julian value ",in_ptr); break; case JULIAN_SOURCES: /* Julian work already done for day */ j = cum->month; break; case 'b': /* 3 letter abbrev */ /* Do caseblind compare of input. Tables are in lower case */ ptr = casebuf; ptr2 = in_ptr; while (*ptr2 != '\0') *(ptr++) = tolower(*(ptr2++)); *ptr = '\0'; if (ptr == (casebuf + 3)) { /* Length check */ for (i = 0; i < 12; i++) if (strncmp(monthnames[i],casebuf,3) == 0) break; if (i < 12) j = i + 1; } if (j == MISSING_TIME_FLAG) err ("Illegal month abbreviation ",in_ptr); break; case 'B': /* Full name */ /* Do caseblind compare of input. Tables are in lower case */ ptr = casebuf; ptr2 = in_ptr; while (*ptr2 != '\0') *(ptr++) = tolower(*(ptr2++)); *ptr = '\0'; for (i = 0; i < 12; i++) if (strcmp(monthnames[i],casebuf) == 0) break; if (i < 12) j = i + 1; if (j == MISSING_TIME_FLAG) err ("Illegal month name ",in_ptr); break; default: err ("Illegal month fragment type ", out[MONTH_FRAG].source_field_type); j = MISSING_TIME_FLAG; break; } } out[MONTH_FRAG].val = j; /* Set up maximum # days for this month */ if (j == MISSING_TIME_FLAG) out[DAY_FRAG].max = INT_MAX; else if ( (j == 2) && cum->leapyear && (cum->year != MISSING_TIME_FLAG) ) out[DAY_FRAG].max = 29; else out[DAY_FRAG].max = cumdays[j] - cumdays[j-1]; } /* Calculate day d = # */ /* j, J, v, V = Julian day */ /* g, G, K, P = fraction of year */ /* * = output previously calculated from Julian */ /* days when processing month output. Do not */ /* confuse with INPUT previously converted */ /* from fragment buffer, which happens when */ /* Julian days are created from fractional yrs */ j = MISSING_TIME_FLAG; if (out[DAY_FRAG].lev == level) { /* Get input day data from appropriate string or from */ /* julian day that came from fractional year */ if (frac->fracyear) { if ( ! (data_missing = frac->frac_missing) ) { if (frac->fractype != 'o') { /* Convert fraction of year, if any, to julian day */ ftmp = ( cum->leapyear && ((frac->fractype == 'g') || (frac->fractype == 'K')) ) ? 366. : 365.; frac->frac = modf(ftmp*frac->frac,&ftmp); /* Julian days to julday; */ frac->julday = ftmp + .1; /* rest back to frac */ frac->fractype = 'o'; /* What's left will supply fraction of day */ /* Do not confuse w/ date fragment source */ /* field type, which remains g, G, K, P, etc */ /* fractype indicates what type of data is in */ /* frac at any point */ } i = frac->julday; } } else { i = out[DAY_FRAG].intime_index; /* Which input gave frag? */ if ( ! (data_missing = missing(in[i].var)) ) if (*out[DAY_FRAG].source_field_type != JULIAN_SOURCES) { in_ptr = out[DAY_FRAG].fragbuf; /* Fragment data */ i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); } } /* Process input day data to output day data */ if ( ! data_missing ) { switch (*out[DAY_FRAG].source_field_type) { case 'd': if ( (1 <= i) && (i <= out[DAY_FRAG].max) ) j = i; else err ("Illegal day value ",in_ptr); break; case 'j': case 'g': /* Honor leap yrs; starts from day 1 */ i--; case 'v': case 'K': /* Honor leap yrs; starts from day 0 */ /* Missing year = missing Julian day */ if (cum->year != MISSING_TIME_FLAG) { if (set_monthday_from_julian(i,cum)) j = cum->day; else err ("Illegal Julian value ",in_ptr); } break; case 'J': case 'G': /* 365 day yr (no 29 Feb); starts from day 1 */ i--; case 'V': case 'P': /* 365 day yr (no 29 Feb); starts from day 0 */ if (cum->year == MISSING_TIME_FLAG) cum->leapyear = FALSE; /* If leapyear, make 29 Feb into 1 Mar, etc */ if ( cum->leapyear && (i >= 59) ) i++; if (set_monthday_from_julian(i,cum)) j = cum->day; else err ("Illegal Julian value ",in_ptr); break; case JULIAN_SOURCES: /* Julian work already done for month */ j = cum->day; break; default: err ("Illegal day fragment type ",out[DAY_FRAG].source_field_type); break; } } out[DAY_FRAG].val = j; } /* Calculate time */ /* Hour from: H (24 hr hour), I & p (12 hour hr & AM/PM flag), */ /* N (24 hr time) or o (fraction of day) */ /* Minute from: M, N, o, or O (fraction of hour) */ /* Fraction of minute from: q, o, O, or S (seconds) & Q */ /* (fraction of second) */ j = MISSING_TIME_FLAG; if (out[HOUR_FRAG].lev == level) { if (frac->fracyear) /* Possible that we have frac year input but no month or day */ /* output, so possibly must get frac days now... */ if ( ! frac->frac_missing) if (frac->fractype != 'o') { /* Convert fraction of year, if any, to julian day */ /* Throw out days; just need fraction... */ ftmp = ( cum->leapyear && ((frac->fractype == 'g') || (frac->fractype == 'K')) ) ? 366. : 365.; frac->frac = modf(ftmp*frac->frac,&ftmp); frac->fractype = 'o'; /* What's left is fraction of day */ } /* Hour */ if (frac->fractype == 'o') { frac->fractype = 'O'; /* What's left will supply fraction of hour */ if ( ! frac->frac_missing) { frac->frac = modf(24.*frac->frac,&ftmp); /* Hours to j; */ j = ftmp + .1; /* rest back to frac */ } } else { i = out[HOUR_FRAG].intime_index; /* Whence this frag? */ if ( ! missing(in[i].var) ) { in_ptr = out[HOUR_FRAG].fragbuf; /* Fragment data */ if (*out[HOUR_FRAG].source_field_type != '*') i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); switch (*out[HOUR_FRAG].source_field_type) { case 'H': /* 24 hr clock */ if ( (0 <= i) && (i <= 23) ) j = i; else err ("Illegal hour value ",in_ptr); break; case 'I': /* 'I' format - 12 hr clock */ if ( (i < 1) || (i > 12) ) err ("Illegal hour value ",in_ptr); else { /* Checked that AMPM_FRAG data was specified while */ /* setting up in get_timedateparams */ if ( ! missing(in[out[AMPM_FRAG].intime_index].var) ) { in_ptr = out[AMPM_FRAG].fragbuf; /* Fragment data */ switch (check_ampm(in_ptr)) { case 'a': j = i; if (i == 12) j = 0; break; case 'p': j = i; if (i < 12) j = i + 12; break; default: err ("Illegal AM/PM flag ",in_ptr); break; } } } break; case 'N': /* 24 hour time */ digs = div(i,100); if ( (0 <= digs.quot) && (digs.quot <= 23) && (0 <= digs.rem) && (digs.rem <= 59) ) { j = digs.quot; cum->minute = digs.rem; /* Save in case minute requested */ } else { err ("Illegal 24 hour time value ",in_ptr); cum->minute = MISSING_TIME_FLAG; } break; case JULIAN_SOURCES: /* HHMM work already done for minute */ if (cum->hour >= 0) j = cum->hour; break; default: err ("Illegal hour fragment type ",out[HOUR_FRAG].source_field_type); break; } } } /* Do time zone conversion if requested. _offset needs to have */ /* been previously set, but no convenient place to set it to 0 if */ /* user didn't want conversion since its non-0 values are set in */ /* this routine */ if ( ! ((j == MISSING_TIME_FLAG) || (out[HOUR_FRAG].conv == NO_TIME_ZONE_CONVERSION)) ) j += cum->hours_offset; out[HOUR_FRAG].val = j; if (j != MISSING_TIME_FLAG) { /* Can't have min w/o hour */ /* Minute */ /* Anticipate ng minute-can't set to nd, so use 0, not */ /* MISSING_TIME_FLAG */ j = 0; if (frac->fractype == 'O') { frac->fractype = 'q'; /* What's left will supply fraction of minute */ if ( ! frac->frac_missing) { /* fracdigits + 2 is number of digits precision for minutes */ /* For 0 digits, provide 0 minutes. For 1 digit, */ /* round to nearest 10 min. For 2 digits, round */ /* to the nearest min. For >2 digits don't round-just */ /* pass on remainder for output as fraction-of-minute. */ /* This is all true, but Bob wants minutes no matter what, */ /* so we are in the <=2, >2 choice only */ frac->frac = modf(60.*frac->frac,&ftmp); /* Minutes to j; */ j = ftmp + .1; /* rest back to frac */ if ( (frac->fracdigits <= 0) && (frac->frac >= .5) ) j++; /* Round */ } } else { if (*out[MINUTE_FRAG].source_field_type != '\0') { i = out[MINUTE_FRAG].intime_index; /* Whence this frag? */ if ( ! missing(in[i].var) ) { in_ptr = out[MINUTE_FRAG].fragbuf; /* Fragment data */ if (*out[MINUTE_FRAG].source_field_type != '*') i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); switch (*out[MINUTE_FRAG].source_field_type) { case 'N': /* 24 hour time */ digs = div(i,100); if ( (0 <= digs.quot) && (digs.quot <= 23) && (0 <= digs.rem) && (digs.rem <= 59) ) { j = digs.rem; cum->hour = digs.quot; /* Save in case hour requested */ } else { err ("Illegal 24 hour time value ",in_ptr); cum->hour = MISSING_TIME_FLAG; } break; case JULIAN_SOURCES: /* HHMM work already done for hour */ if (cum->minute >= 0) j = cum->minute; break; case 'M': /* Minute (!too simple) */ if ( (0 <= i) && (i <= 59) ) j = i; else err ("Illegal minute value ",in_ptr); break; default: err ("Illegal minute fragment type ", out[MINUTE_FRAG].source_field_type); break; } } } } /* Do time zone conversion if requested. _offset needs to have */ /* been previously set, but no convenient place to set it to 0 */ /* if user didn't want conversion since its non-0 values are set */ /* in this routine */ if ( ! ((j == MISSING_TIME_FLAG) || (out[MINUTE_FRAG].conv == NO_TIME_ZONE_CONVERSION)) ) j += cum->minutes_offset; out[MINUTE_FRAG].val = j; /* Fraction of minute (if found) */ /* Should really check for seconds found w/o minutes & */ /* fractions-of-seconds found w/o seconds, but we don't */ /* Use frac instead of j for validity flag/value */ /* Fractype could be q or Q at this point. If q, frac */ /* already is carrying "the" validity flag for this datum */ /* If Q, it isn't-validity depends on whole seconds only */ /* (so I guess we really are checking "fractions-of-seconds */ /* found w/o seconds"). Since flag & value functions conflict */ /* in the Q case, use ftmp in non-q situation */ if (frac->fractype != 'q') { ftmp = frac->frac; frac->frac = -1.; if (*out[SECOND_FRAG].source_field_type != '\0') { i = out[SECOND_FRAG].intime_index; /* Whence this frag? */ if ( ! missing(in[i].var) ) { in_ptr = out[SECOND_FRAG].fragbuf; /* Fragment data */ if (*out[SECOND_FRAG].source_field_type != JULIAN_SOURCES) i = get_numeric_data(in_ptr,in[i].nfrags_in_substring,NULL,NULL); if ( (0 <= i) && (i <= 59) ) { /* If we have valid fractions of seconds, add whole */ /* seconds. Otherwise, set up whole seconds and */ /* corresponding precision */ if ( (frac->fractype == 'Q') && (frac->fracdigits > 2) ) frac->frac = ftmp + i; else { frac->frac = i; frac->fracdigits = 2; } frac->frac /= 60.; /* Convert secs to mins */ } else err ("Illegal second value ",in_ptr); } } } out[FRACTION_FRAG].val = MISSING_TIME_FLAG; if ( ! frac->frac_missing ) if (frac->fracdigits > 0) { if (frac->fracdigits > MAX_OUTTIME_PRECISION) { frac->fracdigits = MAX_OUTTIME_PRECISION; } /* Do our own roundoff-I don't understand the exact sprintf */ /* rules. Also don't understand c's real to integer rules...*/ ftmp = frac->fracdigits; ftmp = pow(10.,ftmp); /* Save next value for carryover purposes. If rounded */ /* value >= this, we have carryover... */ out[FRACTION_FRAG].max = ftmp + .1; frac->frac = modf(frac->frac*ftmp,&ftmp); j = ftmp + .1; if (frac->frac >= .5) j++; out[FRACTION_FRAG].val = j; /* At this point, we have a valid FRACTION_FRAG which may or */ /* may not have come directly from user input. To wit, this */ /* number may have come from a SECOND_FRAG. Thus it is */ /* possible for FRACTION_FRAG.source_field_type to indicate */ /* "nothing input" while FRACTION_FRAG.val != MISSING_TIME */ /* calc_juloutput checks both these things before using */ /* FRACTION_FRAG, so... */ /* I think this should really be done in ioopen_routines */ /* somehow, but 1st tries failed and I got lazy (12 May 00) */ if (*out[FRACTION_FRAG].source_field_type == '\0') *out[FRACTION_FRAG].source_field_type = JULIAN_SOURCES; } } } /* Output necessary frags after rounding. Since gmt<->local quan- */ /* tities have been added in, rounding finishes conversion, too. */ /* We assume that output vars appear in */ /* order (eg, can't have month at level 0 & year at level 1). If */ /* we cannot carry over because of variable level stuff, we output */ /* illegal field value (eg, 2359.999 becomes 2400 if day is not at */ /* same level as time; 2359.999 Oct 31 becomes 0000 Oct 32 if day */ /* is at time level but month isn't; etc) */ /* Carryovers must be done in order of increasing size... */ /* Do fractional time first. Different in that we must regularize */ /* fraction whether or not we can carry to minutes */ /* Note that .max is an impermissible value in this case... */ if (ok_to_redefine(&out[FRACTION_FRAG],level)) if (out[FRACTION_FRAG].val >= out[FRACTION_FRAG].max) { out[FRACTION_FRAG].val -= out[FRACTION_FRAG].max; if (ok_to_redefine(&out[MINUTE_FRAG],level)) out[MINUTE_FRAG].val++; } else if (out[FRACTION_FRAG].val < 0) { out[FRACTION_FRAG].val += out[FRACTION_FRAG].max; if (ok_to_redefine(&out[MINUTE_FRAG],level)) out[MINUTE_FRAG].val--; } /* Not sure why leapflag needs YEAR_FRAG test, but that's the way it */ /* was in td_carryover when I brought it here as part of cum. mod */ leapflag = ( cum->leapyear && (out[YEAR_FRAG].val != MISSING_TIME_FLAG) ); td_carryover(&out[MINUTE_FRAG],0,59,60,&out[HOUR_FRAG],level,leapflag); td_carryover(&out[HOUR_FRAG],0,23,24,&out[DAY_FRAG],level,leapflag); td_carryover(&out[DAY_FRAG],1,out[DAY_FRAG].max,-1, &out[MONTH_FRAG],level,leapflag); td_carryover(&out[MONTH_FRAG],1,12,12,&out[YEAR_FRAG],level,leapflag); /* Calculate yearmonthday */ /* If asked for, we have forced YEAR, MONTH, and DAY to have */ /* been calculated, so pick up the pieces... */ j = MISSING_TIME_FLAG; if (out[YEARMONTHDAY_FRAG].lev == level) { if ( (out[YEAR_FRAG].val != MISSING_TIME_FLAG) && (out[MONTH_FRAG].val != MISSING_TIME_FLAG) && (out[DAY_FRAG].val != MISSING_TIME_FLAG) ) j = out[DAY_FRAG].val + 100 * out[MONTH_FRAG].val + 100 * 100 * out[YEAR_FRAG].val; } out[YEARMONTHDAY_FRAG].val = j; /* Finally, output! */ for (j=0; jfracdigits ); if (i != TIME_INTERNAL_USE) { out_ptr = valarr + valstrlen*i; if (out[j].val == MISSING_TIME_FLAG) { strcpy (out_ptr,MISSING_VALUE_STRING); } else { /* Hour represents full time spec. Note that variable cor- */ /* responding to this fragment is NOT (typically) the input */ /* variable containing the hour; it's a variable at or below */ /* the level of the input corresponding to the smallest of */ /* hour, minute, or fraction present in the dataset */ if (j == HOUR_FRAG) { sprintf(out_ptr,"%02d%02d",out[HOUR_FRAG].val,out[MINUTE_FRAG].val); /* Since fraction is smallest piece, fracdigits still valid */ if ( (out[FRACTION_FRAG].val != MISSING_TIME_FLAG) && (frac->fracdigits > 0) ) sprintf(out_ptr+4,".%0*d",frac->fracdigits,out[FRACTION_FRAG].val); } else if (j == JULIAN_FRAG) { /* Decided to return all Julians as floats. "frac" is */ /* misnomer here. Note that double float has more */ /* precision than 32 bit int. (Calcs still done mostly */ /* as integer, though) */ sprintf(out_ptr,"%.*f",outjulprecision,outjulfrac); } else if (j == ISO8601_FRAG) { if ( (out[YEAR_FRAG].val == MISSING_TIME_FLAG) || (out[MONTH_FRAG].val == MISSING_TIME_FLAG) || (out[DAY_FRAG].val == MISSING_TIME_FLAG) || (out[HOUR_FRAG].val == MISSING_TIME_FLAG) || (out[MINUTE_FRAG].val == MISSING_TIME_FLAG) ) { strcpy (out_ptr,MISSING_VALUE_STRING); } else { output_ptr = out_ptr; sprintf(output_ptr,"%04d-%02d-%02dT%02d:%02d", out[YEAR_FRAG].val,out[MONTH_FRAG].val,out[DAY_FRAG].val, out[HOUR_FRAG].val,out[MINUTE_FRAG].val); /* Add length of string produced by above sprintf */ output_ptr += 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; /* out[SECOND_FRAG] not necessarily computed since we */ /* never output seconds (up until ISO8601, we output */ /* decimal minutes. Oops, forgot seconds-since-epoch (in */ /* calc_julday from which we stole the */ /* "back-convert-to-seconds line). */ /* out[FRACTION_FRAG] should be decimal minutes - */ /* convert back to seconds and hope the accuracy is OK */ if (out[FRACTION_FRAG].val == MISSING_TIME_FLAG) { strcpy (output_ptr,":00"); output_ptr += 3; } else { ftmp = 60. * (double)out[FRACTION_FRAG].val /pow(10.,(double)frac->fracdigits); /* Deal (badly) with "rollover" caused by fixed output */ /* precision */ if ( (ftmp + .005) >= 60. ) { sprintf(output_ptr,":59.99"); output_ptr += 6; } else { if ( frac->fracdigits <=2 ) { /* Width of 2, not 3 in an attempt to repress dec pt */ width=2; precision=0; } else if (frac->fracdigits == 3) { width=4; precision=1; } else { /* No more than 2 digits after the dec pt */ width=5; precision=2; } sprintf(output_ptr,":%0*.*f",width,precision,ftmp); output_ptr += 1 + i; } } if (out[j].conv == OUTPUT_IS_UTC) { *output_ptr++ = 'Z'; *output_ptr = '\0'; } } } else { sprintf(out_ptr,out[j].fmt,out[j].val); } } } } } return; }