/*  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<NVAR; i++) {
    independent_varnum = dependencies[i];
    if (independent_varnum == ILLEGAL_VARNUM) break;
    independent_datum    =    data   +   independent_varnum * size_datum ;
    if ( strcmp(independent_datum,MISSING_VALUE_STRING) == 0  ) {
      dependent_datum    =    data   +   dependent_varnum * size_datum ;
      strcpy(dependent_datum,MISSING_VALUE_STRING);
      break;
    }
  }
  return;
}

Logical ok_to_calc (var,this_level)
int var,this_level;
/*	Returns true if it's OK to calculate variable var.		*/
/*	It's OK if variable exists and is on this level (hence we need	*/
/*	present level as an input argument)				*/
{
  Logical ok;

  ok = (var != OUTPUT_NOT_REQUESTED);
  if (ok) ok = (iovarlevel__(var) == this_level);
  return ok;
}

Logical ok_to_use (var,this_level)
int var,this_level;
/*	Returns true if it's OK to use variable var.			*/
/*	It's OK if variable exists and is on or above this level (hence */
/*	we need present level as an input argument), and is not missing	*/
{
  Logical ok;

  ok = (var != OUTPUT_NOT_REQUESTED);
  if (ok) ok = (iovarlevel__(var) <= this_level);
  if (ok) ok = ( ! missing(var));
  return ok;
}

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(&copy_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<expected_in_items; i++) strcpy(vec[i],MISSING_VALUE_STRING);
  else {
      /*  Need to try to decode expected_in_items # of args.  Max of	*/
      /*  expected_in_items is NUM_TIMEDATE_FRAGS.  Decode one more	*/
      /*  into scratch buffer to check for extra input.  Don't know how	*/
      /*  to parametrize this (or, indeed, how to handle this better!)	*/
      /*  See compile time check, above.				*/
    tmp = vec[expected_in_items];
    vec[expected_in_items] = scratch;
    i = sscanf(buffer,format,vec[0],vec[1],vec[2],vec[3],vec[4],
			     vec[5],vec[6],vec[7],vec[8],vec[9],
			     vec[10],vec[11],vec[12],vec[13]);
    vec[expected_in_items] = tmp;
  }

  if (i != expected_in_items) {
    tmp = buildstring("Time/date format/data mismatch.\n   Datum = ",
		      buffer,
		      "\n   Variable is pointed to by time/date ",
		      " building get_timedate_frag err msg");
    errtmp = buildstring(tmp,
			 TIMEDATE_INPUT_KEYWORD_PREFIX,
			 NULL,
			 " building get_timedate_frag err msg");
    errn (errtmp, index+1);
    free (tmp);
    free (errtmp);
  }

    /*  It may be that the input field is supposed to be decoded into	*/
    /*  more than one output field.  Do that here.  For now, insist	*/
    /*  that this only happens for a single input field ...		*/
    /*  Sometimes, instead of doing this, we set source_field_type to	*/
    /*  *, and don't have an input buffer for the 2nd output field.	*/
    /*  Would have done this in general, but got complicated for	*/
    /*  fractional year stuff.  So - you get choices!			*/
  if (n_out_copies > 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 <float.h>		*/
  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; j<NUM_OUTTIME_VARIABLES; j++) {
  i = out[j].var;
  if (ok_to_calc(i,level)) {
    if (j == JULIAN_FRAG)
      calc_juloutput(&out[JULIAN_FRAG],&outjulfrac,&outjulprecision,
				&out[YEAR_FRAG],
				&out[MONTH_FRAG],
				&out[DAY_FRAG],
				&out[HOUR_FRAG],
				&out[MINUTE_FRAG],
				&out[FRACTION_FRAG],
				&out[DST_FRAG],
				frac->fracdigits
		     );
    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;
  }
  
