/*  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.6c 19 Mar 2004"
/*  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();
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(&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 != outlat->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;
  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
  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':
	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 = (year->val%4 == 0) &&
				((year->val%100 != 0) || (year->val%400 == 0));

/*	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 = cumdays[month->val-1] + day->val;
    /*  "Do" leap year							*/
  switch (outjul->conv) {
    case 'j':  case 'g':  case 'v':  case 'K':
	/*  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':
      --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;
  }
  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<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[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)		*/
  /*  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 <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;
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;
	/*  Algorithm from http://www.nist.gov/y2k/documents.htm#leapyear */
      if (j != cum->year)		/* Test is just to avoid recalc	*/
	cum->leapyear = (j%4 == 0) && ((j%100 != 0) || (j%400 == 0));
    } 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			*/
  /*			 * = 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':			/*  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				*/
  /*			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':	/* 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; 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],
				frac->fracdigits
		     );
    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;
}
