/***********************************************************************
 *
 * TITLE:	utc.c
 *
 * AUTHOR:	Kevin J. Miller, Ana Maria Guerrero
 *
 * DESCRIPTION:
 *	This module is a generic OEL module which contains routines
 *	which are used to perform operations on time values.
 *
 *				CHANGE HISTORY
 *
 * $Log: utc.c,v $
 * Revision 1.13  1994/05/20  22:45:03  kevin
 * added <string.h>
 *
 * Revision 1.12  1994/05/04  21:01:03  kevin
 * changed print of utc_val
 *
 * Revision 1.11  1994/04/30  02:18:12  kevin
 * port to ANSI
 *
 * Revision 1.10  1992/12/31  23:44:13  kevin
 * added Chris' get_hms() and getx_days() functions
 *
 * Revision 1.9  1992/02/05  23:04:50  kevin
 * added local time function
 *
 * Revision 1.8  1991/09/05  00:01:15  kevin
 * added functions to return integer values
 *
 * Revision 1.7  1991/07/18  20:59:54  kevin
 * added week functions
 *
 * Revision 1.6  1991/06/21  01:56:46  kevin
 * added m/d/y functions
 *
 * Revision 1.5  1991/05/14  02:12:11  kevin
 * minor lint fixes
 *
 * Revision 1.4  1991/05/09  21:58:30  kevin
 * added standard function headers
 *
 * Revision 1.3  1991/04/12  01:11:31  kevin
 * added standard SFOC header
 *
 * Revision 1.2  1991/03/07  20:34:49  kevin
 * added getx_doy() and getx_year() functions
 *
 * Revision 1.1  1991/03/04  21:46:29  kevin
 * Initial revision
 *
 ***********************************************************************
 *
 * WARNINGS:
 *
 * EXTERNAL CALLABLE COMPONENTS (PUBLIC):
 *	init_utc_resol()
 *	set_utc()
 *	setx_utc()
 *	get_utc()
 *	getx_utc()
 *	getx_year()
 *	get_doy()
 *	getx_doy()
 *	get_wkday()
 *	getx_wkday()
 *	set_mdy()
 *	get_mdy()
 *	set_week()
 *	get_week()
 *
 * GLOBALS:
 *
 * WAIVERS:
 *
 * NOTES:
 *
 * MANPAGE:
 *
 ***********************************************************************/

#ifndef lint
static char rcsid[] = 
    "$Id: utc.c,v 1.13 1994/05/20 22:45:03 kevin OEL $";
#endif

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#define TRUE 1
#define FALSE 0

#include "utc.h"

static int resol = UTC_SECONDS;
static int std_local_offset = - 8 * UTC_ONE_HOUR;
static char *std_zone = "PST";
static char *dst_zone = "PDT";

static int mndays[2][13] = {
    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

/***********************************************************************
 *
 * FUNCTION:
 *	init_utc_resol()
 *
 * INPUTS:
 *	val		- (int) new time resolution
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *	resol		- (static int) time resolution
 *
 * DESCRIPTION:
 *	Set the time resolution for text output routines.  This routine
 *	exists in order to keep the resol variable static.
 */

void 
init_utc_resol(int val)
{
    resol = val;
}

/***********************************************************************
 *
 * FUNCTION:
 *	setx_utc()
 *
 * INPUTS:
 *	text		- (char *) standard utc text string
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	tval		- (utc_val) utc in seconds as a long int
 *	UTC_INVALID	- on conversion error (0xffffffff)
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Convert standard utc text string to utc value.  Less significant
 *	fields need not be present.
 */

utc_val 
setx_utc(char text[])
{
    int year, doy, hour, min, sec;
    int i;
    utc_val tval;

    year = doy = hour = min = sec = 0;

    for (i = 0; i < 4; i++) {		/* extract year */
	if (!isdigit(text[i])) return UTC_INVALID;
	year = 10 * year + text[i] - '0';
    }
    if ((year < 1970) || (year >= 2100)) return UTC_INVALID;

    if (text[4] != '-') return UTC_INVALID;	/* separator */

    for (i = 5; i < 8; i++) {		/* extract day of year */
	if (!isdigit(text[i])) return UTC_INVALID;
	doy = 10 * doy + text[i] - '0';
    }
    if ((doy < 1) || (doy > ((year % 4) ? 365 : 366))) return UTC_INVALID;

    if (text[8] == '\0') goto finish_up;
    if (text[8] != 'T') return UTC_INVALID;	/* separator */

    for (i = 9; i < 11; i++) {		/* extract hour */
	if (!isdigit(text[i])) return UTC_INVALID;
	hour = 10 * hour + text[i] - '0';
    }
    if ((hour < 0) || (hour >= 24)) return UTC_INVALID;

    if (text[11] == '\0') goto finish_up;
    if (text[11] != ':') return UTC_INVALID;	/* separator */

    for (i = 12; i < 14; i++) {		/* extract minute */
	if (!isdigit(text[i])) return UTC_INVALID;
	min = 10 * min + text[i] - '0';
    }
    if ((min < 0) || (min >= 60)) return UTC_INVALID;

    if (text[14] == '\0') goto finish_up;
    if (text[14] != ':') return UTC_INVALID;	/* separator */

    for (i = 15; i < 17; i++) {		/* extract second */
	if (!isdigit(text[i])) return UTC_INVALID;
	sec = 10 * sec + text[i] - '0';
    }
    if ((sec < 0) || (sec >= 60)) return UTC_INVALID;

    if (text[17] != '\0') return UTC_INVALID;	/* terminator */

finish_up:
    tval = (year - 1970) * 365 + (year - 1969) / 4 + (doy - 1);
    tval = (tval * 24) + hour;
    tval = (tval * 60) + min;
    tval = (tval * 60) + sec;

    return tval;
}

/***********************************************************************
 *
 * FUNCTION:
 *      get_utc()
 *
 * INPUTS:
 *      tval    - (utc_val) input time value (required)
 *
 * OUTPUTS:
 *      year    - (int *) four digit year (required)
 *      doy     - (int *) three digit day of year (required)
 *      hour    - (int *) two digit hour (if reolution <= UTC_HOURS)
 *      min     - (int *) two digit minute (if reolution <= UTC_MINUTES)
 *      sec     - (int *) two digit second (if reolution == UTC_SECONDS)
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *      Extracts year, doy, hour, min, and second from UTC time
 *	value.  This function is a varargs type, and the number
 *	of arguments depends on the resolution set in the function
 *	init_utc_resol().
 */

void
get_utc(utc_val tval, int *year_p, int *doy_p, ...)
{
    va_list args;
    int year, doy, hour, min, sec;
    int *hour_p, *min_p, *sec_p;

    sec = tval % 60;			tval /= 60;
    min = tval % 60;			tval /= 60;
    hour = tval % 24;			tval /= 24;
    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    year = 1969 + ((tval + 365) / 1461) * 4;
    if (doy == 1460) {
	year += 3;
	doy = 366;
    } else {
	year += (doy / 365);
	doy = (doy % 365) + 1;
    }

    *year_p = year;

    *doy_p = doy;

    va_start(args, doy_p);

    if (resol < UTC_DAYS) {
	hour_p = va_arg(args, int *);
	*hour_p = hour;
	if (resol < UTC_HOURS) {
	    min_p = va_arg(args, int *);
	    *min_p = min;
	    if (resol < UTC_MINUTES) {
		sec_p = va_arg(args, int *);
		*sec_p = sec;
	    }
	}
    }

    va_end(args);
}

/***********************************************************************
 *
 * FUNCTION:
 *	getx_utc()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	text		- (char *) output time string
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *	resol		- (static int) time resolution
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Convert utc value to standard utc text string using resol
 *	to specify the resolution.
 */

void 
getx_utc(char text[], utc_val tval)
{
    int year, doy, hour, min, sec;

    sec = tval % 60;			tval /= 60;
    min = tval % 60;			tval /= 60;
    hour = tval % 24;			tval /= 24;
    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    year = 1969 + ((tval + 365) / 1461) * 4;
    if (doy == 1460) {
	year += 3;
	doy = 366;
    } else {
	year += (doy / 365);
	doy = (doy % 365) + 1;
    }

    text[0] = (year / 1000) + '0'; year %= 1000;
    text[1] = (year / 100) + '0'; year %= 100;
    text[2] = (year / 10) + '0'; year %= 10;
    text[3] = year + '0';
    text[4] = '-';
    text[5] = (doy / 100) + '0'; doy %= 100;
    text[6] = (doy / 10) + '0'; doy %= 10;
    text[7] = doy + '0';
    if (resol == UTC_DAYS) {
	text[8] = '\0';
    } else {
	text[8] = 'T';
	text[9] = (hour / 10) + '0'; hour %= 10;
	text[10] = hour + '0';
	if (resol == UTC_HOURS) {
	    text[11] = '\0';
	} else {
	    text[11] = ':';
	    text[12] = (min / 10) + '0'; min %= 10;
	    text[13] = min + '0';
	    if (resol == UTC_MINUTES) {
		text[14] = '\0';
	    } else {
		text[14] = ':';
		text[15] = (sec / 10) + '0'; sec %= 10;
		text[16] = sec + '0';
		text[17] = '\0';
	    }
	}
    }
}

/***********************************************************************
 *
 * FUNCTION:
 *	getx_year()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	text		- (char *) output year string
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Extract 4 digit year text from utc_val.
 */

void 
getx_year(char text[], utc_val tval)
{
    int year, doy;

    tval /= UTC_ONE_DAY;
    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    year = 1969 + ((tval + 365) / 1461) * 4;
    if (doy == 1460) {
	year += 3;
    } else {
	year += (doy / 365);
    }

    text[0] = (year / 1000) + '0'; year %= 1000;
    text[1] = (year / 100) + '0'; year %= 100;
    text[2] = (year / 10) + '0'; year %= 10;
    text[3] = year + '0';
    text[4] = '\0';
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_doy()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	doy		- (int) day-of-year 
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Return day-of-year integer from utc_val.
 */


int 
get_doy(utc_val tval)
{
    int doy;

    tval /= UTC_ONE_DAY;
    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    if (doy == 1460) {
        doy = 366;
    } else {
        doy = (doy % 365) + 1;
    }
    
    return doy;
}

/***********************************************************************
 *
 * FUNCTION:
 *	getx_doy()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	text		- (char *) output day-of-year string
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Extract 3 digit day-of-year text from utc_val.
 */

void 
getx_doy(char text[], utc_val tval)
{
    int doy;

    tval /= UTC_ONE_DAY;
    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    if (doy == 1460) {
	doy = 366;
    } else {
	doy = (doy % 365) + 1;
    }

    text[0] = (doy / 100) + '0'; doy %= 100;
    text[1] = (doy / 10) + '0'; doy %= 10;
    text[2] = doy + '0';
    text[3] = '\0';
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_wkday()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *
 * RETURNS:
 *			- (int) day of week
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Returns day of week (Monday is day 0).
 */

int 
get_wkday(utc_val tval)
{
    tval /= UTC_ONE_DAY;
    return (tval + 3) % 7;
}

/***********************************************************************
 *
 * FUNCTION:
 *	getx_wkday()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *
 * RETURNS:
 *			- (char *) day of week
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Returns day of week (Monday is day 0).
 */

char *
getx_wkday(utc_val tval)
{
    static char *day_text[] = {
	"Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
	"Saturday", "Sunday"
    };

    tval /= UTC_ONE_DAY;
    return day_text[(tval + 3) % 7];
}

/***********************************************************************
 *
 * FUNCTION:
 *	set_mdy()
 *
 * INPUTS:
 *	mo		- (int) month value (1 to 12)
 *	da		- (int) day of month (1 to 31)
 *	yr		- (int) four digit year
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	tval		- (utc_val) utc in seconds as a long int
 *	UTC_INVALID	- on conversion error (0xffffffff)
 *
 * EXTERNALLY READ:
 *	mndays		- (static int[][]) cum. array of days per month
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Converts time as mo/da/yr to standard utc_val.
 */

utc_val 
set_mdy(int mo, int da, int yr)
{
    utc_val tval;
    int leap, doy;

    if ((yr < 1970) || (yr >= 2100)) return UTC_INVALID;

    leap = (yr % 4) ? 0 : 1;

    if ((mo < 1) || (mo > 12)) return UTC_INVALID;

    if ((da < 1) || (da > (mndays[leap][mo] - mndays[leap][mo - 1])))
	return UTC_INVALID;

    doy = mndays[leap][mo - 1] + da;

    tval = (yr - 1970) * 365 + (yr - 1969) / 4 + (doy - 1);
    tval *= UTC_ONE_DAY;

    return tval;
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_mdy()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	mo		- (int *) pointer to returned month
 *	da		- (int *) pointer to returned day
 *	yr		- (int *) pointer to returned year
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *	mndays		- (static int[][]) cum. array of days per month
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Convert utc value to integer mo/da/yr format.  Hours, minutes,
 *	and seconds are ignored.
 */

void 
get_mdy(int *mo, int *da, int *yr, utc_val tval)
{
    int year, doy, leap;
    int l, m, h;

    tval /= UTC_ONE_DAY;

    doy = (tval + 365) % 1461;

    /* calculate days in blocks of four years */
    year = 1969 + ((tval + 365) / 1461) * 4;
    if (doy == 1460) {
	year += 3;
	doy = 366;
    } else {
	year += (doy / 365);
	doy = (doy % 365) + 1;
    }
    leap = (year % 4) ? 0 : 1;

    /*
     * this is a standard binary search
     */
    l = 0;
    h = 12;
    while (1) {
	if (l > h) {			/* doy lies between */
	    m = l;
	    break;
	}
	m = (l + h) / 2;
	if (mndays[leap][m] < doy) {
	    l = m + 1;
	} else if (mndays[leap][m] > doy) {
	    h = m - 1;
	} else {			/* found doy exactly */
	    break;
	}
    }

    *yr = year;
    *mo = m;
    *da = doy - mndays[leap][m - 1];
}

/***********************************************************************
 *
 * FUNCTION:
 *	set_week()
 *
 * INPUTS:
 *	wk		- (int) week number (1 to 53)
 *	yr		- (int) year (4 digits)
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	tval		- (utc_val) utc in seconds of the 00:00:00 Monday
 *	UTC_INVALID	- on conversion error (0xffffffff)
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Takes an input week and year and converts these to the utc_val
 *	of 00:00:00 on Monday of that week.
 *
 *	The following shows several definitions of 'WEEK 1'
 *
 *		1988  JAN  4 - JAN 10	                  #7
 *		1989  JAN  2 - JAN  8	            #5
 *		1990  JAN  1 - JAN  7	         #4
 *		1991  DEC 31 - JAN  6	      #3
 *		1992  DEC 30 - JAN  5	   #2
 *		1993  JAN  4 - JAN 10	                  #7
 *		1994  JAN  3 - JAN  9	               #6
 *		1995  JAN  2 - JAN  8	            #5
 *		1996  JAN  1 - JAN  7	         #4
 *		1997  DEC 30 - JAN  5	   #2
 *		1998  DEC 29 - JAN  4	#1
 *		1999  JAN  4 - JAN 10	                  #7
 *
 *	We can find out what day the year starts on with:
 *
 *		dy = (yr + (yr - 1) / 4 - 2) % 7
 *
 *	where: 0 = MON, 1 = TUE, ... 6 = SUN
 *
 *	The number of days that we need to add in order to get the first
 *	Monday in January (where  (dy % 7) == 0) is just (7 - dy) % 7.
 *	Since years start on the 1st, the first Monday of the year is
 *	((7 - dy) % 7) + 1.
 *	The full formula for the first Monday in January is:
 *
 *		fm = ((7 - (yr + (yr - 1) / 4 - 2) % 7) % 7) + 1
 *
 *	which can be simplified to:
 *
 *		fm = ((7 * N + 2) - (yr + (yr - 1) / 4)) % 7 + 1
 *
 *	where N is some large number chosen so that the expression in
 *	parentheses is always positive.  I will pick (7 * N + 2) = 7009.
 *
 *	If the first Monday in January <= 4, then that is the start of the
 *	year, otherwise the first week in the year starts in December of
 *	the previous year.
 */

utc_val 
set_week(int wk, int yr)
{
    utc_val tval, sval;
    int fm;

    if ((yr < 1970) || (yr >= 2100)) return UTC_INVALID;
    if ((wk < 1) || (wk >= 54)) return UTC_INVALID;
    /* some years permit a Week #53 */

    fm = (7009 - (yr + (yr - 1) / 4)) % 7 + 1;

    tval = (yr - 1970) * 365 + (yr - 1969) / 4 + (fm - 1);
    if (fm > 4) tval -= 7;		/* start last December */

    tval += 7 * (wk - 1);		/* add the week */

    if (wk == 53) {			/* need to check start of next year */
	fm = (7009 - (yr + 1 + yr / 4)) % 7 + 1;
	sval = (yr - 1969) * 365 + (yr - 1968) / 4 + (fm - 1);
	if (fm > 4) sval -= 7;
	if (tval >= sval) return UTC_INVALID;
    }

    tval *= UTC_ONE_DAY;		/* convert to seconds */
    return tval;
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_week()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	wk		- (int *) pointer to returned week
 *	yr		- (int *) pointer to returned year
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Convert utc value to integer wk/yr format.  Hours, minutes,
 *	and seconds are ignored.  All weeks start on Monday.
 *
 *	First back off the day to the previous Monday, then find the year
 *	and day of year,
 *
 *	These Mondays always translate as follows:
 *	
 *		Jan 1		-> week 1		doy = 1
 *		Jan 2		-> week 1		doy = 2
 *		Jan 3		-> week 1		doy = 3
 *		Jan 4		-> week 1		doy = 4
 *		Jan 5		-> week 2		doy = 5
 *		Jan 6		-> week 3		doy = 6
 *		Jan 7		-> week 4		doy = 7
 *		  :		    :			    :
 *		Dec 26		-> week 52		doy = 360, 361
 *		Dec 27 (normal)	-> week 52		doy = 361
 *		Dec 27 (leap)	-> week 53		doy = 362
 *		Dec 28		-> week 53		doy = 362, 363
 *		Dec 29		-> week 1 (next year)	doy = 363, 364
 *		Dec 30		-> week 1 (next year)	doy = 364, 365
 *		Dec 31		-> week 1 (next year)	doy = 365, 366
 *
 *	From this we see that one can generally use the formula:
 *
 *				wk = (doy + 9) / 7
 *
 *	The exceptions are when (doy >= 364) or if (doy = 363) and is
 *	not a leap year.
 */

void 
get_week(int *wk, int *yr, utc_val tval)
{
    int year, doy, leap;

    tval /= UTC_ONE_DAY;

    /*
     * back off to the previous Monday
     */
    tval -= (tval + 3) % 7;

    doy = (tval + 365) % 1461;

    /*
     * calculate days in blocks of four years
     */
    year = 1969 + ((tval + 365) / 1461) * 4;
    if (doy == 1460) {
	year += 3;
	doy = 366;
    } else {
	year += (doy / 365);
	doy = (doy % 365) + 1;
    }
    leap = (year % 4) ? 0 : 1;

    if (doy - leap >= 363) {	/* Dec 29, 30, 31 */
	*wk = 1;
	*yr = year + 1;
    } else {			/* normal situation */
	*wk = (doy + 9) / 7;
	*yr = year;
    }
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_local_offset()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	*isdst_ptr	- (int)	TRUE if DST if not NULL
 *	*zone_ptr	- (char [4]) time zone if not NULL
 *
 * RETURNS:
 *	offset		- (int) time to add to tval to get local
 *			  time
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Return day-of-year integer from utc_val.
 *
 *	We change to DT on the first Sunday in April at 2 ST (3 DT).
 *	We return to ST on the last Sunday in October at 1 ST (2 DT).
 *
 *	The first Sunday in April is DOY: 91 to 97 in normal years.
 *	The first Sunday in April is DOY: 92 to 98 in leap years.
 *	The last Sunday in October is DOY: 298 to 304 in normal years.
 *	The last Sunday in October is DOY: 299 to 305 in leap years.
 *
 *		- Add standard offset to get local ST.
 *		- Find DOY.
 *		- Subtract 1 for leap year.
 *		- Subtract 0 for Sunday, 1 for Monday, ...
 *		- testday: --- ..  90  -->  use ST
 *		  testday:  91 ..  97  (Sunday < 02:00:00 ST) ST else DT
 *		  testday:  98 .. 297  -->  use DT
 *		  testday: 298 .. 304  (Sunday < 01:00:00 ST) DT else ST
 *		  testday: 305 .. +++  -->  use ST
 */

int 
get_local_offset(utc_val tval, int *isdst_ptr, char *zone_ptr)
{
    int secs;		/* number of seconds in current day */
    int jday;		/* number of days since the epoch */
    int year;
    int doy;		/* day of year */
    int dow;		/* day of week (Sunday is 0 !!!) */
    int testday;	/* test value */
    int isdst;		/* TRUE if DST */
    int offset;		/* offset returned to get local times */

    tval += std_local_offset;
    jday = tval / UTC_ONE_DAY;

    /* calculate days in blocks of four years */
    doy = (jday + 365) % 1461;
    year = 1969 + ((jday + 365) / 1461) * 4;

    if (doy == 1460) {
	year += 3;
	doy = 366;
    } else {
	year += (doy / 365);
	doy = (doy % 365) + 1;
    }

    dow = (jday + 4) % 7;	/* note change in formula */

    testday = doy - dow;
    if (!(year % 4)) --testday;

    if ((testday <= 90) || (testday >= 305)) {
	isdst = FALSE;
    } else if ((testday >= 98) && (testday <= 297)) {
	isdst = TRUE;
    } else if ((testday >= 91) && (testday <= 97)) {
	if (dow == 0) {		/* only check Sundays */
	    secs = tval % UTC_ONE_DAY;
	    isdst = (secs >= 2 * UTC_ONE_HOUR);
	} else {
	    isdst = TRUE;
	}
    } else if ((testday >= 298) && (testday <= 304)) {
	if (dow == 0) {		/* only check Sundays */
	    secs = tval % UTC_ONE_DAY;
	    isdst = (secs < UTC_ONE_HOUR);
	} else {
	    isdst = FALSE;
	}
    } else {
	fprintf(stderr, "Major failure in UTC routines:\n"); 
	fprintf(stderr, "   tval = %u\n\n", (int)tval);
	isdst = 0;
    }


    if (isdst_ptr != NULL) *isdst_ptr = isdst;

    if (isdst) {
	if (zone_ptr != NULL) strcpy(zone_ptr, dst_zone);
	offset = std_local_offset + UTC_ONE_HOUR;
    } else {
	if (zone_ptr != NULL) strcpy(zone_ptr, std_zone);
	offset = std_local_offset;
    }

    return offset;
}

/***********************************************************************
 *
 * FUNCTION:
 *	get_hms()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	hrs		- (int *) pointer to returned hours
 *	min		- (int *) pointer to returned minutes
 *	sec		- (int *) pointer to returned seconds
 *
 * RETURNS:
 *	none
 *
 * EXTERNALLY READ:
 *	none
 *
 * EXTERNALLY MODIFIED:
 *	none
 *
 * DESCRIPTION:
 *	Convert utc value to integer hrs:min:sec format.  Month, day,
 *	and year are ignored.
 */

void 
get_hms(int *hrs, int *min, int *sec, utc_val tval)
{
    tval %= UTC_ONE_DAY;
    *hrs = tval / UTC_ONE_HOUR;
    tval %= UTC_ONE_HOUR;
    *min = tval / UTC_ONE_MINUTE;
    *sec = tval % UTC_ONE_MINUTE;
}

/***********************************************************************
 *
 * FUNCTION:
 *	getx_days()
 *
 * INPUTS:
 *	tval		- (utc_val) input time
 *
 * OUTPUTS:
 *	text		- (char *) output year string
 *
 * RETURNS:
 *	none
 *
 * EXTERNALLY READ:
 *	none
 *
 * EXTERNALLY MODIFIED:
 *	none
 *
 * DESCRIPTION:
 *	Extract 5 digit number of days text from utc_val.
 */
 
void 
getx_days(char *days, utc_val time)
{   int i, len;
    char temp[10];
 
    time /= UTC_ONE_DAY;                        /* get number of days */
        /* convert to string */
    temp[0] = '0';
    for (i=0; time > 0; i++) {
        temp[i] = (time % 10) + '0';
        time /= 10;
    }
    if (i > 0) {
        temp[i] = '\0';
    } else {
        temp[1] = '\0';
    }
    len = strlen(temp);
    for (i=0; i < len; i++) {	/* reverse the order of the digits */
        days[i] = temp[len-i-1];
    }
    days[len] = '\0';
}
