/* 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) */ /* both 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 1.7 23 Aug 2005" /* 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] */ /* */ /* 20 Jan 05. 1.6d */ /* Bug fix: should test longitude against longitude convention, */ /* not latitude convention! */ /* [Begin 1.6d] */ /* 19 Mar 04. 1.6c */ /* Bug fix: frac digits for q (frac minutes) was being computed */ /* as frac digits for frac year formats, resulting in no frac */ /* minute output */ /* 4 Mar 04. 1.6c */ /* Bug fix: b & B never worked [! saw demo that they did, on */ /* globec. ??]. */ /* [Begin 1.6c] */ /* 3 Mar 04. 1.6b */ /* Bug fix: Mar 1 of leap years was coming out as jul day 60 */ /* [Begin 1.6b. Put on fleetlink for Bob. Used?] */ /* 3 Jul 03. 1.6a */ /* Accept valstrlen as arg and calculate vallen instead of vice */ /* versa */ /* [Begin 1.6a] */ /* */ /* 2 Jul 03. 1.6 */ /* Bug fix: newform_latlon_to_old did not skip leading seps in */ /* "string is one piece between seps" case */ /* Bug fix: Allow negate_string to handle case where string to */ /* negate starts off w/white space (or has space between its sign */ /* and digits) */ /* 24 Jun 03. 1.6 */ /* Bug fix: time zone offsets being used even if not init'ed */ /* (no effect since compiler init'ed them to 0 anyway, but...) */ /* Bug fix: time zone offsets must be calculated before time */ /* calcs - if both on same level, order was reversed */ /* 14 May 03. 1.6 */ /* Allow user-spec'd separators in a lat/long input string. */ /* Look for and handle explicit direction-is-positive spec (ie, */ /* trailing EWNS) */ /* Bug fix: call to err relating to latlon formats was missing */ /* an arg */ /* Bug fix: might have converted an "nd" lat/lon to "-nd" (also */ /* might not have if other routines intercepted the "nd", but code */ /* should be here, too) */ /* [Begin 1.6] */ /* 1 Mar 02. 1.5b */ /* Bug fix: routine doing UTC<->local conversion when not asked */ /* Bug fix: when doing UTC<->local conversion, did it backwards */ /* [Begin 1.5b] */ /* 12 May 00. 1.5a */ /* Some comments related to 20 Apr work */ /* 20 Apr 00. 1.5a */ /* Bug fix: seconds weren't making it into Julian output under */ /* some circumstances */ /* [Begin 1.5a] */ /* 4 Sep 99. 1.5 */ /* Julian output time capability */ /* Refine computations for julian stuff w/o 29 Feb. Includes */ /* bug fixes to previous versions */ /* Bug fix: existing calc_timedate calling sequence, etc would */ /* fail if >1 output time was requested, and each had, say, diff- */ /* erent year */ /* Bug fix: incorrect test for julian range check */ /* Bug fix: y format would sometimes accept value > 100 */ /* [Begin 1.5] */ /* 27 May 99. 1.4b */ /* Bug fix: yy format didn't work for year 99. */ /* [Begin 1.4b] */ /* 9 Mar 99. 1.4a */ /* Bug fix: int vals should be compared against INT_MAX, not */ /* LONG_MAX */ /* [Begin 1.4a] */ /* 24 Dec 98. 1.4 */ /* Bug fix: correct leapyear algorithm - tnx, RCG. */ /* Bug fix: trapped when handling numeric time zones */ /* Improve error handling in DST processing */ /* 30 Oct 98. 1.4 */ /* Continue fractional years */ /* 18 Jul 98. 1.4 */ /* Incorporate following 2 1.3a bug fixes */ /* Bug fix: syntax error(!!) calling ok_to_redefine. Resulted */ /* in one-minute-off julian->gregorian converts except at level 0 */ /* Bug fix [1.3a]: wrong variable in one of the time/date */ /* format/data validity checks. */ /* 8 Nov 97. 1.4 */ /* Fractional years */ /* [Begin 1.4] */ /* [Comments about versions 1.0->1.3 in defgb_revision.doc as of ] */ /* [ 5 Aug 99 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(); Logical ok_to_calc(),ok_to_use(),missing(),get_logical_from_string(); Logical is_a_leap_year(); char *strdupl(),*buildstring(),*nxttok(),*lookup_wjstbl(); double 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"}; /*************** */ char *newform_latlon_to_old(str_orig,sepstr) /* str points to potentially "new" format string - w/embedded seps */ /* & trailing cardinal. Since orig program can do strings w/o seps, */ /* return pointer such a string. Be nice and add a leading zero to */ /* mins if necessary. Return NULL if > 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,fracdigits) struct outtime *outjul; double *outjulfrac; int *outjulprecision; struct outtime *year,*month,*day,*hour,*minute,*fracminute; 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 */ { double fdenominator; int days,days_from_base_year,i; Logical leapyear; /* Possible values of outjul.conv. This was tested in ioopen... assume it stayed OK (hope, hope) 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 days-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) */ /* Year, if appropriate */ outjul->val = year->val; if (year->val == MISSING_TIME_FLAG) { switch (outjul->conv) { case 'g': case 'G': case 'K': case 'P': case 's': 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 == 's') { if (year->val < outjul->baseyear) return; for (i = outjul->baseyear; i < year->val; i++) { days_from_base_year += 365; if (is_a_leap_year(i)) days_from_base_year++; } } /* Day of year. Goes into outjul if we're returning jul day, */ /* frac if we're returning jul 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 '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 's': --days; } switch (outjul->conv) { case 'g': case 'K': fdenominator = (leapyear) ? 366. : 365.; break; case 'G': case 'P': fdenominator = 365.; break; default: fdenominator = 1.; break; } switch (outjul->conv) { case 'G': case 'P': case 'g': case 'K': *outjulfrac = ((double)days)/fdenominator; *outjulprecision = 3; /* 1 day ~= .001 year */ break; default: outjul->val = days; *outjulfrac = 0.; *outjulprecision = 0; break; } /* Time - optional. Return as soon as something's missing */ /* Things like minute-info-wo-hour-info are already reflected in */ /* an hour value of missing */ if (*hour->source_field_type == '\0') return; if (hour->val == MISSING_TIME_FLAG) { outjul->val = MISSING_TIME_FLAG; return; } /* Cumulative-from-base-year needs to have enough precision to */ /* print out the days-from-base-year */ if (outjul->conv == 's') { i = 1; while (days < i) { (*outjulprecision)++; i *= 10; } } fdenominator *= 24.; *outjulfrac += ((double)hour->val)/fdenominator; (*outjulprecision)++; if (*minute->source_field_type == '\0') return; if (minute->val == MISSING_TIME_FLAG) { outjul->val = MISSING_TIME_FLAG; return; } fdenominator *= 60.; *outjulfrac += ((double)minute->val)/fdenominator; (*outjulprecision) += 2; /* ? ++? */ /* fracminute is integer valued, so must be divided by its power */ /* of 10. value is fraction of minute, so fdenominator OK as is */ if (*fracminute->source_field_type == '\0') return; /* 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->val == MISSING_TIME_FLAG) return; fdenominator *= pow(10.,(double)fracdigits); *outjulfrac += ((double)fracminute->val)/fdenominator; (*outjulprecision) += fracdigits; 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 */ /* 12 is number of items in decode list below. In includes an */ /* "extra" item, hence the -1 */ #if NUM_TIMEDATE_FRAGS != 12 - 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) */ /* 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... */ { int last_month; if (ok_to_redefine(units_struct,level) && ok_to_redefine(tens_struct,level)) if (units_struct->val > max) { units_struct->val -= (max+1); 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; static char *casebuf = NULL; /* Buffer for caseblind compares */ int i,j,k; div_t digs; int vallen; Logical data_missing,leapflag; 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_timedateparams\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_timedateparams\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 */ /* s = days-since-day-0 (= v + a year offset) */ /* g, G, K, P = fraction of year */ /* * = 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 '*': 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': case 's': /* 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 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 */ /* s = days-since-day-0 (= v + a year offset) */ /* 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 != '*') { 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': case 's': /* 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 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 '*': /* 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 '*': /* 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 != '*') 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') && (ftmp > 0.) ) 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) { errn ("Excessive output time precision ignored. Max digits = ", 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 = '*'; } } } /* 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); /* Finally, output! */ for (j=0; jfracdigits ); 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) { if (outjulprecision > 0) sprintf(out_ptr,"%.*f",outjulprecision, out[JULIAN_FRAG].val + outjulfrac); else sprintf(out_ptr,"%d",out[JULIAN_FRAG].val); } else sprintf(out_ptr,out[j].fmt,out[j].val); } } return; }