/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright (c) 1996-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)time_comm.c	1.70	99/09/23 SMI"

/*LINTLIBRARY*/

/*
 * time_comm.c
 *
 * This file contains routines to convert struct tm to time_t and
 * back as well as adjust time values based on their timezone, which
 * is a local offset from GMT (Greenwich Mean Time).
 *
 * Many timezones actually consist of more than one offset from GMT.
 * The GMT offset that is considered the normal offset is referred
 * to as standard time.  The other offset is referred to as alternate
 * time, but is better known as daylight savings time or summer time.
 *
 * The current timezone for an application is derived from the TZ
 * environment variable either as defined in the environment or in
 * /etc/default/init.  As defined by IEEE 1003.1-1990 (POSIX), the
 * TZ variable can either be:
 *    :<characters>
 * or
 *    <std><offset1>[<dst>[<offset2>]][,<start>[/<time>],<end>[/<time>]
 *
 * <characters> is an implementation-defined string that somehow describes
 * a timezone.  The implementation-defined description of a timezone used
 * in Solaris is based on the public domain zoneinfo code available from
 * elsie.nci.nih.gov and a timezone that is specified in this way is
 * referred to as a zoneinfo timezone.  An example of this is ":US/Pacific".
 *
 * The precise definition of the second format can be found in POSIX,
 * but, basically, <std> is the abbreviation for the timezone in standard
 * (not daylight savings time), <offset1> is the standard offset from GMT,
 * <dst> is the abbreviation for the timezone in daylight savings time and
 * <offset2> is the daylight savings time offset from GMT.  The remainder
 * specifies when daylight savings time begins and ends.  A timezone
 * specified in this way is referred to as a POSIX timezone.  An example
 * of this is "PST7PDT".
 *
 * In Solaris, there is an extension to this.  If the timezone is not
 * preceded by a ":" and it does not parse as a POSIX timezone, then it
 * will be treated as a zoneinfo timezone.  Much usage of zoneinfo
 * timezones in Solaris is done without the leading ":".
 *
 * A zoneinfo timezone is a reference to a file that contains a set of
 * rules that describe the timezone.  In Solaris, the file is in
 * /usr/share/lib/zoneinfo.  The file is generated by zic(1M), based
 * on zoneinfo rules "source" files.  This is all described on the zic(1M)
 * man page.
 */

/*
 * Functions that are common to ctime(3C) and cftime(3C)
 */

#ifndef ABI
#pragma weak tzset = _tzset
#endif
#pragma weak localtime_r = _localtime_r
#pragma weak gmtime_r = _gmtime_r

#include	"synonyms.h"
#include	"shlib.h"
#include 	<mtlib.h>
#include	<sys/types.h>
#include	<ctype.h>
#include  	<stdio.h>
#include  	<limits.h>
#include	<sys/param.h>
#include 	<time.h>
#include 	<unistd.h>
#include 	<stdlib.h>
#include 	<string.h>
#include 	<tzfile.h>
#include	<synch.h>
#include	<fcntl.h>
#include	<errno.h>

static struct tm	*gmtime_u(const time_t *clock, struct tm *result);
static struct tm	*offtime_u(const time_t *clock, time_t offset,
			    struct tm *result);
static void		_ltzset_u(time_t tim);
static int		goodTZchar(const char c);
static char		*getzname(char *p, char **tz, int which);
static char		*tzcpy(char *s1, char *s2, int which, size_t len);
static char		*gettime(char *p, time_t *timez, int f);
static char		*getdigit(char *ptr, int *d);
static int		getdst(char *p, time_t *s, time_t *e);
static int		posixgetdst(char *p, time_t *s, time_t *e, time_t tim);
static char		*posixsubdst(char *p, uchar_t *type, int *jul,
			    int *m, int *n, int *d, time_t *tm);
static void		getusa(int *s, int *e, struct tm *t);
static int		sunday(struct tm *t, int d);
static int		_tzload(char *name);
static int		detzcode(char *codep);
static char		*getsystemTZ(void);

#define	SEC_PER_MIN	60l
#define	SEC_PER_HOUR	(60 * SEC_PER_MIN)
#define	SEC_PER_DAY	(24 * SEC_PER_HOUR)
#define	SEC_PER_YEAR	(365 * SEC_PER_DAY)
#define	FEB28		(58)
#define	MINTZNAME	3
#define	TIMEZONE	"/etc/default/init"
#define	TZSTRING	"TZ="
#define	year_size(A)	(((A) % 4) ? 365 : 366)

#ifndef TRUE
#define	TRUE		1
#define	FALSE		0
#endif /* !TRUE */

extern const short	__month_size[];
extern const int	__yday_to_month[];
extern const int	__lyday_to_month[];
extern char		_tz_gmt[];
extern char		_tz_spaces[];

/*
 * XXX
 * the following state variables are only valid after _ltzset_u() has been
 * called and only under the following conditions:
 * when isPosix == 0 (when TZ is a zoneinfo timezone), is_in_dst is set if
 * the time passed to _ltzset_u() (presumably the time in question) is in
 * the alternate or dst time.  when isPosix == 1 (when TZ is a POSIX
 * timezone), start_dst and end_dst are the start and end time of dst (in
 * seconds since the beginning of the year).
 * having the determination of whether the time is in dst being split
 * between two places, depending on the timezone type is pretty gross, but
 * it works.  someone should clean this up in the future.
 */
static int		isPosix = 0;
static int		is_in_dst = 0;
static time_t 		start_dst = -1;	/* start time of dst */
static time_t		end_dst = -1;	/* end time of dst */

/*
 * ttinfo_t and state_t are used when a zoneinfo timezone is in effect.
 */
typedef struct ttinfo {			/* time type information */
	int		tt_gmtoff;	/* GMT offset in seconds */
	int		tt_isdst;	/* used to set tm_isdst */
	int		tt_abbrind;	/* abbreviation list index */
} ttinfo_t;

typedef struct state {
	int		timecnt;
	int		typecnt;
	int		charcnt;
	time_t		*ats;
	unsigned char	*types;
	ttinfo_t	*ttis;
	char		*chars;
	int		chars_size;	/* size calloc'ed for chars */
	char		*last_tzload;	/* name of the last tzload()'ed file */
} state_t;

static state_t		*_tz_state;

/*
 * when a routine needs to return a pointer to a structure, this is it.
 */
static struct tm	tm;

#ifdef _REENTRANT
extern mutex_t		_time_lock;
#endif /* _REENTRANT */

/*
 * Definitions for localtime_r and localtime
 */

static struct tm *
localtime_u(const time_t *timep, struct tm *tmp)
{
	_ltzset_u(*timep);

	tmp = offtime_u(timep, -timezone, tmp);
	if (!daylight) {
		/*
		 * if there is no dst supported by this timezone, we do not
		 * need to check if we are in dst, so we have the answer
		 * and can exit.
		 */
		return (tmp);
	}

	/*
	 * the remainder of this routine tries to determine if dst is in
	 * effect and, if so, recalculate the struct tm before returning it.
	 */

	if (isPosix == 0) {			/* zoneinfo style TZ */
		if (is_in_dst) {
			tmp = offtime_u(timep, -altzone, tmp);
			tmp->tm_isdst = 1;
		}
	} else {				/* POSIX style TZ */
		int now;
		int daybegin, dayend;

		/*
		 * determine the time in question in seconds from the
		 * beginning of the year.
		 */
		now = (tmp->tm_yday * SEC_PER_DAY) +
			(tmp->tm_hour * SEC_PER_HOUR) +
			(tmp->tm_min * SEC_PER_MIN) + tmp->tm_sec;

		/*
		 * determine the start and end time (in seconds from the
		 * beginning of the year) of the alternate time.
		 */
		if (start_dst == -1 && end_dst == -1)
			getusa(&daybegin, &dayend, tmp);
		else {
			/*
			 * start_dst and end_dst were set in tzset().
			 */
			daybegin = start_dst;
			dayend = end_dst;
		}

		/*
		 * check to see if we are in dst by checking if the time
		 * in question is between the start and end times of dst.
		 */
		if (daybegin <= dayend) {		/* n. hemisphere */
			if (now >= daybegin && now < dayend) {
				tmp = offtime_u(timep, -altzone, tmp);
				tmp->tm_isdst = 1;
			}
		} else {				/* s. hemisphere */
			if (!(now >= dayend && now < daybegin)) {
				tmp = offtime_u(timep, -altzone, tmp);
				tmp->tm_isdst = 1;
			}
		}
	}

	return (tmp);
}

struct tm *
localtime(const time_t *timep)
{
	return (localtime_u(timep, &tm));
}


struct tm *
_localtime_r(const time_t *timep, struct tm *tmp)
{
	struct tm *res;

	(void) mutex_lock(&_time_lock);
	res = localtime_u(timep, tmp);
	(void) mutex_unlock(&_time_lock);
	return (res);
}

/*
 * Definitions for gmtime_r and gmtime
 */

static struct tm *
gmtime_u(const time_t *clock, struct tm *result)
{
	return (offtime_u(clock, 0, result));
}


struct tm *
_gmtime_r(const time_t *clock, struct tm *result)
{
	struct tm *res;

	(void) mutex_lock(&_time_lock);
	res = offtime_u(clock, 0, result);
	(void) mutex_unlock(&_time_lock);
	return (res);
}


struct tm *
gmtime(const time_t *clock)
{
	return (gmtime_u(clock, &tm));
}


/*
 * Definitions for offtime_u (assumes lock is held)
 */

extern const int	__mon_lengths[2][MONS_PER_YEAR];
extern const int	__year_lengths[2];

static struct tm *
offtime_u(const time_t *clock, time_t offset, struct tm *result)
{
	struct tm	*tmp;
	long		days;
	int		rem;
	long		y;
	long		newy;
	const int	*ip;

	tmp = result;
	days = *clock / SECS_PER_DAY;
	rem = *clock % SECS_PER_DAY;
	rem += offset;
	while (rem < 0) {
		rem += SECS_PER_DAY;
		--days;
	}
	while (rem >= SECS_PER_DAY) {
		rem -= SECS_PER_DAY;
		++days;
	}
	tmp->tm_hour = (int) (rem / SECS_PER_HOUR);
	rem = rem % SECS_PER_HOUR;
	tmp->tm_min = (int) (rem / SECS_PER_MIN);
	tmp->tm_sec = (int) (rem % SECS_PER_MIN);
	tmp->tm_wday = (int) ((EPOCH_WDAY + days) % DAYS_PER_WEEK);
	if (tmp->tm_wday < 0)
		tmp->tm_wday += DAYS_PER_WEEK;
	y = EPOCH_YEAR;

#define	LEAPS_THRU_END_OF(y)    ((y) / 4 - (y) / 100 + (y) / 400)

	while (days < 0 || days >= (long) __year_lengths[isleap(y)]) {
		newy = y + days / DAYSPERNYEAR;
		if (days < 0)
			--newy;
		days -= ((long)newy - (long)y) * DAYSPERNYEAR +
			LEAPS_THRU_END_OF(newy > 0 ? newy - 1L : newy) -
			LEAPS_THRU_END_OF(y > 0 ? y - 1L : y);
		y = newy;
	}

	tmp->tm_year = y - TM_YEAR_BASE;
	tmp->tm_yday = days;
	ip = __mon_lengths[isleap(y)];
	for (tmp->tm_mon = 0; days >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
		days = days - ip[tmp->tm_mon];
	tmp->tm_mday = (days + 1);
	tmp->tm_isdst = 0;
	return (tmp);
}


/*
 * definition of difftime
 *
 * This code assumes time_t is type long.  Note the difference of two
 * longs in absolute value is representable as an unsigned long.  So,
 * compute the absolute value of the difference, cast the result to
 * double and attach the sign back on.
 *
 * Note this code assumes 2's complement arithmetic.  The subtraction
 * operation may overflow when using signed operands, but when the
 * result is cast to unsigned long, it yields the desired value
 * (ie, the absolute value of the difference).  The cast to unsigned
 * long is done using pointers to avoid undefined behavior if casting
 * a negative value to unsigned.
 */

double
difftime(time_t time1, time_t time0)
{
	if (time1 < time0) {
		time0 -= time1;
		return (-(double) *(unsigned long *) &time0);
	} else {
		time1 -= time0;
		return ((double) *(unsigned long *) &time1);
	}
}

/*
 * definitions for mktime
 */

time_t
mktime(struct tm *timeptr)
{
	struct tm	*tptr;
	struct tm	tmptm;
	long long	secs;	/* must hold more than time_t */
	time_t	conv_secs;	/* type_converted from secs */
	int		bad_year = 0;
	int		temp;
	int		mktime_errno;

	/* mktime leaves errno unchanged if no error is encountered */
	mktime_errno = errno;

	(void) mutex_lock(&_time_lock);
	/*
	 * calculate a time_t value based on the struct tm passed in.
	 * some values may be out of range, so things may be adjusted
	 * a bit.
	 */
	secs = timeptr->tm_sec + SEC_PER_MIN * timeptr->tm_min +
	    SEC_PER_HOUR * timeptr->tm_hour +
	    SEC_PER_DAY * (timeptr->tm_mday - 1);

	if (timeptr->tm_mon >= 12) {
		timeptr->tm_year += timeptr->tm_mon / 12;
		timeptr->tm_mon %= 12;
	} else if (timeptr->tm_mon < 0) {
		temp = -timeptr->tm_mon;
		timeptr->tm_mon = 0;	/* If tm_mon divides by 12. */
		timeptr->tm_year -= (temp / 12);
		if (temp %= 12) {
			/*
			 * Adjust for tm_mon not being evenly divisible by
			 * months/year.  The year adjustment is one short
			 * because of the truncate from the integer divide.
			 * The month adjustment will be the remainder.
			 */
			timeptr->tm_year--;
			timeptr->tm_mon = 12 - temp;
		}
	}

#ifndef _LP64
	/*
	 * With the current 32-bit time_t value, time can range from
	 * Dec 13, 1901 8:45:52 GMT to Jan 19, 2038 3:14:07 GMT.
	 * (There is no year limit for 64-bit time_t.)
	 */
	if ((timeptr->tm_year < 1) || (timeptr->tm_year > 138))
		bad_year = 1;
#endif


	/*
	 * YR(X) -- calculates the number of days since 0 A.D.
	 * X must be the current year minus 1900.
	 */
#define	YR(X) \
	((1900L + (X)) * 365L + (1900L + (X)) / 4L - \
	(1900L + (X)) / 100L + ((1900L + (X)) - 1600L) / 400L)

	/*
	 * DAYS_SINCE_70(Y) -- calculates the number of days
	 * since 1/1/1970 to 12/31/(1900 + Y - 1).
	 */
#define	DAYS_SINCE_70(Y) (YR((Y)-1L) - YR(70-1))

	/* For bounday values of tm_year, typecasting required */
	secs += (long long)SEC_PER_DAY * DAYS_SINCE_70(timeptr->tm_year);

	if (isleap(timeptr->tm_year + TM_YEAR_BASE))
		secs += SEC_PER_DAY * __lyday_to_month[timeptr->tm_mon];
	else
		secs += SEC_PER_DAY * __yday_to_month[timeptr->tm_mon];

	/* adjust secs for std time or dst based on tm_isdst */
	_ltzset_u((time_t)secs);
	if (timeptr->tm_isdst > 0)
		secs += altzone;
	else
		secs += timezone;

	conv_secs = (time_t)secs;
	tptr = localtime_u((const time_t *)&conv_secs, &tmptm);

	/*
	 * Determine whether dst is in effect.  We need to do this if
	 * the caller put -1 in the tm_isdst field.
	 */
	if (timeptr->tm_isdst < 0) {
		if (isPosix == 0) {		/* zoneinfo style TZ */
			if (is_in_dst) {
				secs -= (timezone - altzone);
				conv_secs = (time_t)secs;
				tptr = localtime_u((const time_t *)&conv_secs,
					&tmptm);
			}
		} else {			/* POSIX style TZ */
			int daybegin, dayend;
			int now;
			int dst_delta;

			/*
			 * find the time range for dst.  daybegin and
			 * dayend should be seconds into the current year.
			 */
			if (start_dst == -1 && end_dst == -1) {
				getusa(&daybegin, &dayend, tptr);
			} else {
				daybegin = start_dst;
				dayend = end_dst;
			}

			/*
			 * now is the current time in seconds since
			 * the year started.
			 */
			now = tptr->tm_sec +
				(SEC_PER_MIN * tptr->tm_min) +
				(SEC_PER_HOUR * tptr->tm_hour) +
				(SEC_PER_DAY * tptr->tm_yday);

			/*
			 * determine if dst is in effect by checking if
			 * temp is between daybegin and dayend.
			 */
			dst_delta = timezone - altzone;
			if (daybegin <= dayend) {	/* n. hemisphere */
				if ((now >= daybegin) &&
				    (now < (dayend + dst_delta))) {
					secs -= dst_delta;
					conv_secs = (time_t)secs;
					tptr = localtime_u(
						(const time_t *)&conv_secs,
					    &tmptm);
				}
			} else {	/* Southern Hemisphere */
				if (!((now >= (dayend + dst_delta)) &&
				    (now < daybegin))) {
					secs -= dst_delta;
					conv_secs = (time_t)secs;
					tptr = localtime_u(
						(const time_t *)&conv_secs,
					    &tmptm);
				}
			}
		}
	}

	if ((secs < LONG_MIN) || (secs > LONG_MAX) || (bad_year == 1)) {
		secs = -1;
		mktime_errno = EOVERFLOW;
	} else {
		*timeptr = *tptr;
	}
	(void) mutex_unlock(&_time_lock);

	errno = mktime_errno;
	return ((time_t)secs);
}


/*
 * Definitions for _ltzset and _ltzset_u
 */

static void
_ltzset_u(time_t tim)
{
	char		*name;
	state_t		*tz_state;
	int		i;

	name = getsystemTZ();
	if (*name == '\0') {
		/* TZ is not present, use GMT */
		(void) strcpy(tzname[0], "GMT");
		(void) strcpy(tzname[1], "   ");
		timezone = altzone = daylight = 0;
		return; /* TZ is not present, use GMT */
	}
	/* Lets try POSIX method first */
	/* Get main time zone name and difference from GMT */
	if (((name = getzname(name, &tzname[0], 0)) != 0) &&
	    ((name = gettime(name, &timezone, 1)) != 0)) {
		isPosix = 1;
		(void) strcpy(tzname[1], "   ");
		altzone = timezone - SEC_PER_HOUR;
		start_dst = end_dst = 0;
		daylight = 0;

		/* Get alternate time zone name */
		if ((name = getzname(name, &tzname[1], 1)) == 0) {
			(void) strcpy(tzname[1], "   ");
			altzone = timezone;
			return;
		}

		start_dst = end_dst = -1;
		daylight = 1;

		/*
		 * If the difference between alternate time zone and
		 * GMT is not given, use one hour as default.
		 */
		if (*name == '\0')
			return;
		if (*name != ';' && *name != ',')
		if ((name = gettime(name, &altzone, 1)) == 0 ||
		    (*name != ';' && *name != ','))
			return;
		if (*name == ';')
			(void) getdst(name + 1, &start_dst, &end_dst);
		else
			(void) posixgetdst(name+1, &start_dst, &end_dst, tim);
		return;
	}
	isPosix = 0;

	/*
	 * having failed to parse the TZ variable as a POSIX timezone,
	 * now try to parse it as a zoneinfo timezone.
	 */
	name = getsystemTZ();
	if (name[0] == ':')
		name++;

	if (_tzload(name) != 0) {
		/*
		 * _tzload() may fail without allocating memory
		 * for _tz_state.  Needs to check _tz_state.
		 */
		if (_tz_state) {
			if (_tz_state->ats)
				free(_tz_state->ats);
			if (_tz_state->types)
				free(_tz_state->types);
			if (_tz_state->ttis)
				free(_tz_state->ttis);
			if (_tz_state->chars)
				free(_tz_state->chars);
			if (_tz_state->last_tzload)
				free(_tz_state->last_tzload);
			free(_tz_state);
			_tz_state = 0;
		}
		return;
	} else {
		ttinfo_t *ttisp;

		tz_state = _tz_state;

		/* set the timezone to the first part of the rule */
		if (!(tzname[0] = tzcpy(tzname[0], &tz_state->chars[0], 0, 0)))
			return;
		(void) strcpy(tzname[1], "   ");
		timezone = -tz_state->ttis[0].tt_gmtoff;
		altzone = 0;
		daylight = 0;
		is_in_dst = 0;

		/*
		 * XXX
		 * this stuff is done in an rather inefficient manner.
		 * it is pretty easy to explain, though.  if you want to
		 * improve the efficiency, go for it.
		 */

		/*
		 * loop through the time transitions, setting the exported
		 * globals timezone and altzone as well as the static global
		 * is_in_dst along the way.
		 * the reason that we use a sequential search is that we
		 * we need to set the timezone offset (timezone or altzone)
		 * for the time that we are not in, as well as the one we
		 * are in.
		 */
		for (i = 0; i < tz_state->timecnt &&
		    tz_state->ats[i] <= tim; i++) {
			ttinfo_t *	ttisp;

			ttisp = &tz_state->ttis[tz_state->types[i]];
			if (ttisp->tt_isdst) {
				if (!(tzname[1] = tzcpy(tzname[1],
				    &tz_state->chars[ttisp->tt_abbrind], 1, 0)))
					return;
				altzone = -ttisp->tt_gmtoff;
				is_in_dst = 1;
				daylight = 1;
			} else {
				if (!(tzname[0] = tzcpy(tzname[0],
				    &tz_state->chars[ttisp->tt_abbrind], 0, 0)))
					return;
				timezone = -ttisp->tt_gmtoff;
				is_in_dst = 0;

			}
		}

		/*
		 * why are we doing this?  we need to find out if any of
		 * the time transitions go to a dst rule.  why?  because
		 * libc must export the global variable 'daylight' which
		 * indicates whether the timezone EVER does DST.
		 * once we find a dst time, we can stop looking.
		 */
		if (daylight == 0) {
			for (; i < tz_state->timecnt; i++) {
				ttisp = &tz_state->ttis[tz_state->types[i]];
				if (ttisp->tt_isdst) {
					daylight = 1;
					break;
				}
			}
		}
	}
}

void
_ltzset(time_t tim)
{
	(void) mutex_lock(&_time_lock);
	_ltzset_u(tim);
	(void) mutex_unlock(&_time_lock);
}

void
tzset(void)
{
	(void) mutex_lock(&_time_lock);
	_ltzset_u(time(NULL));
	(void) mutex_unlock(&_time_lock);
}


/*
 * All the following functions assume that the _time_lock is being held
 */

static int
goodTZchar(const char c)
{
	return (isgraph(c) &&
		!isdigit(c) &&
		(c != ',') &&
		(c != '-') &&
		(c != '+'));
}

static char *
getzname(char *p, char **tz, int which)
{
	char *q = p;

	if (*q == ':')			/* ':' not allowed as first character */
		return (0);
	if (!goodTZchar(*q))
		return (0);
	while (goodTZchar(*++q))
		;

	if (!(*tz = tzcpy(*tz, p, which, (q-p))))
		return (0);
	return (q);
}

static char *
tzcpy(char *s1, char *s2, int which, size_t len)
{
	static char *tzspace[2];
	char *cp;

	if (len == 0)
		len = strlen(s2);
	/* check for strlen(s2) > TZNAME_MAX */
	if (strlen(s1) < len) {
		/*
		 * This part of the routine allocates space if not enough
		 * space was provided.
		 * This routine is used like:
		 *	tzname[0] = tzcpy(tzname[0], source_string, 0);
		 * followed by
		 *	tzname[1] = tzcpy(tzname[1], source_string, 1);
		 */
		if (tzspace[which] != NULL && tzspace[which] != _tz_gmt &&
		    tzspace[which] != _tz_spaces)
			free(tzspace[which]);

		if ((tzspace[which] =
			malloc(((len > MINTZNAME) ? len : MINTZNAME) + 1))
				== NULL)
			return (0);

		s1 = tzspace[which];
	}

	(void) strncpy(s1, s2, len);
	cp = s1 + len;
	for (; len < MINTZNAME; len++)
		*cp++ = ' ';
	*cp = '\0';
	return (s1);
}

static char *
gettime(char *p, time_t *timez, int f)
{
	time_t		t = 0;
	int		d;
	int		sign = 0;

	d = 0;
	if (f)
		if (((sign = (*p == '-')) != 0) || (*p == '+'))
			p++;
	if ((p = getdigit(p, &d)) != 0) {
		t = d * SEC_PER_HOUR;
		if (*p == ':') {
			if ((p = getdigit(p+1, &d)) != 0) {
				t += d * SEC_PER_MIN;
				if (*p == ':') {
					if ((p = getdigit(p+1, &d)) != 0)
						t += d;
				}
			}
		}
	}
	if (sign)
		*timez = -t;
	else
		*timez = t;
	return (p);
}


static char *
getdigit(char *ptr, int *d)
{
	if (!isdigit(*ptr))
		return (0);

	*d = 0;
	do {
		*d *= 10;
		*d += *ptr - '0';
	} while	((isdigit(*++ptr)));
	return (ptr);
}


static int
getdst(char *p, time_t *s, time_t *e)
{
	int lsd, led;
	time_t st, et;

	st = et = 0; 		/* Default for start and end time is 00:00:00 */
	if ((p = getdigit(p, &lsd)) == 0)
		return (0);
	lsd -= 1; 	/* keep julian count in sync with date  1-366 */
	if (lsd < 0 || lsd > 365)
		return (0);
	if ((*p == '/') && ((p = gettime(p+1, &st, 0)) == 0))
		return (0);
	if (*p == ',') {
		if ((p = getdigit(p+1, &led)) == 0)
			return (0);
		led -= 1; 	/* keep julian count in sync with date  1-366 */
		if (led < 0 || led > 365)
			return (0);
		if ((*p == '/') && ((p = gettime(p+1, &et, 0)) == 0))
			return (0);
	}
	/* Convert the time into seconds */
	*s = lsd * SEC_PER_DAY + st;
	*e = led * SEC_PER_DAY + et - (timezone - altzone);
	return (1);
}


static int
posixgetdst(char *p, time_t *s, time_t *e, time_t tim)
{
	int lsd, led;
	struct tm *std_tm, tmp_tm;
	unsigned char stdtype, alttype;
	int stdjul, altjul;
	int stdm, altm;
	int stdn, altn;
	int stdd, altd;
	int xthru = 0;
	int wd_jan01, wd;
	int d, w;
	time_t t;
	time_t st, et;
	st = et = 7200;	/* Default for start and end time is 02:00:00 */

	if ((p = posixsubdst(p, &stdtype, &stdjul,
	    &stdm, &stdn, &stdd, &st)) == 0)
		return (0);
	if (*p != ',')
		return (0);
	if ((p = posixsubdst(p+1, &alttype, &altjul,
	    &altm, &altn, &altd, &et)) == 0)
		return (0);

	t = tim - timezone;
	std_tm = gmtime_u(&t, &tmp_tm);

	while (xthru++ < 2) {
		lsd = stdjul;
		led = altjul;
		if (stdtype == 'J' && isleap(std_tm->tm_year + TM_YEAR_BASE))
			if (lsd > FEB28)
				++lsd;		/* Correct for leap year */
		if (alttype == 'J' && isleap(std_tm->tm_year + TM_YEAR_BASE))
			if (led > FEB28)
				++led;		/* Correct for leap year */
		if (stdtype == 'M') {		/* Figure out the Julian Day */
			wd_jan01 = std_tm->tm_wday -
				(std_tm->tm_yday % DAYS_PER_WEEK);
			if (wd_jan01 < 0)
				wd_jan01 += DAYS_PER_WEEK;
			if (isleap(std_tm->tm_year + TM_YEAR_BASE))
				wd = (wd_jan01 + __lyday_to_month[stdm-1]) %
					DAYS_PER_WEEK;
			else
				wd = (wd_jan01 + __yday_to_month[stdm-1]) %
					DAYS_PER_WEEK;
			for (d = 1; wd != stdd; ++d)
				wd = ((wd+1) % DAYS_PER_WEEK);
			for (w = 1; w != stdn; ++w) {
				d += DAYS_PER_WEEK;
				if (d > __mon_lengths[
					isleap(std_tm->tm_year + TM_YEAR_BASE)]
						[stdm-1]) {
					d -= DAYS_PER_WEEK;
					break;
				}
			}
			if (isleap(std_tm->tm_year + TM_YEAR_BASE))
				lsd = __lyday_to_month[stdm-1] + d - 1;
			else
				lsd = __yday_to_month[stdm-1] + d - 1;
		}
		if (alttype == 'M') {		/* Figure out the Julian Day */
			wd_jan01 = std_tm->tm_wday -
				(std_tm->tm_yday % DAYS_PER_WEEK);
			if (wd_jan01 < 0)
				wd_jan01 += DAYS_PER_WEEK;
			if (isleap(std_tm->tm_year + TM_YEAR_BASE))
				wd = (wd_jan01 + __lyday_to_month[altm-1]) %
					DAYS_PER_WEEK;
			else
				wd = (wd_jan01 + __yday_to_month[altm-1]) %
					DAYS_PER_WEEK;
			for (d = 1; wd != altd; ++d)
				wd = ((wd+1) % DAYS_PER_WEEK);
			for (w = 1; w != altn; ++w) {
				d += DAYS_PER_WEEK;
				if (d > __mon_lengths[
					isleap(std_tm->tm_year + TM_YEAR_BASE)]
						[altm-1]) {
					d -= DAYS_PER_WEEK;
					break;
				}
			}
			if (isleap(std_tm->tm_year + TM_YEAR_BASE))
				led = __lyday_to_month[altm-1] + d - 1;
			else
				led = __yday_to_month[altm-1] + d - 1;
		}
		if ((lsd <= led) || (xthru == 2))
			break;
		else {	/* Southern Hemisphere */
			t = tim - altzone;
			std_tm = gmtime_u(&t, &tmp_tm);
		}
	}	/* for (;;)	*/
	*s = lsd * SEC_PER_DAY + st;
	*e = led * SEC_PER_DAY + et - (timezone - altzone);
	return (1);
}


static char *
posixsubdst(char *p, uchar_t *type, int *jul, int *m, int *n,
	int *d, time_t *tm)
{

/*
 *	nnn where nnn is between 0 and 365.
 */
	if (isdigit(*p)) {
		if ((p = getdigit(p, jul)) == 0)
			return (0);
		if (*jul < 0 || *jul > 365)
			return (0);
		*type = '\0';
	}
/*
** J < 1-365 > where February 28 is always day 59, and March 1 is ALWAYS
** day 60.  It is not possible to specify February 29.
**
** This is a hard problem.  We can't figure out what time it is until
** we know when daylight savings time begins and ends, and we can't
** know that without knowing what time it is!?!  Thank you, POSIX!
**
**
*/
	else if (*p == 'J') {
		if ((p = getdigit(p+1, jul)) == 0)
			return (0);
		if (*jul <= 0 || *jul > 365)
			return (0);
		--(*jul);		/* make it between 0 and 364 */
		*type = 'J';
	}
/*
** Mm.n.d
**	Where:
**	m is month of year(1-12)
**	n is week of month(1-5)
**		Week 1 is the week in which the d'th day first falls
**		Week 5 means the last day of that type in the month
**			whether it falls in the 4th or 5th weeks.
**	d is day of week(0-6) 0 == Sunday
**
** This is a hard problem.  We can't figure out what time it is until
** we know when daylight savings time begins and ends, and we can't
** know that without knowing what time it is!?!  Design by committee.
** The saving grace is that this probably the right way to specify
** Daylight Savings since most countries change on the first/last
** Someday of the month.
*/
	else if (*p == 'M') {
		if ((p = getdigit(p+1, m)) == 0)
			return (0);
		if (*m <= 0 || *m > 12)
			return (0);
		if (*p != '.')
			return (0);
		if ((p = getdigit(p+1, n)) == 0)
			return (0);
		if (*n <= 0 || *n > 5)
			return (0);
		if (*p != '.')
			return (0);
		if ((p = getdigit(p+1, d)) == 0)
			return (0);
		if (*d < 0 || *d > 6)
			return (0);
		*type = 'M';
	}
	else
		return (0);

	if ((*p == '/') && ((p = gettime(p+1, tm, 0)) == 0))
		return (0);
	return (p);
}


static void
getusa(int *s, int *e, struct tm *t)
{
	extern const struct {
		int	yrbgn;
		int	daylb;
		int	dayle;
	} __daytab[];
	int i = 0;
	int yr;

	/* can't be less than 70 */
	while (t->tm_year < (yr = __daytab[i].yrbgn) && yr != 0)
		i++;
	*s = __daytab[i].daylb; /* fall through loop when in correct interval */
	*e = __daytab[i].dayle;

	*s = sunday(t, *s);
	*e = sunday(t, *e);
	*s = *s * SEC_PER_DAY + 2 * SEC_PER_HOUR;
	*e = *e * SEC_PER_DAY + SEC_PER_HOUR;
}


static int
sunday(struct tm *t, int d)
{
	if (d >= 58)
		d += year_size(t->tm_year) - 365;
	return (d - (d - t->tm_yday + t->tm_wday + 700) % 7);
}


static int
_tzload(char *name)
{
	int	i;
	int	fid;
	state_t *s = _tz_state;

	if (s == 0) {
		s = calloc(1, sizeof (*s));
		if (s == 0)
			return (-1);
		_tz_state = s;
	}
	if (name == 0 && (name = TZDEFAULT) == 0)
		return (-1);
	{
		char	*p;
		int	doaccess;
		char	fullname[MAXPATHLEN];

		doaccess = name[0] == '/';
		if (!doaccess) {
			if ((p = TZDIR) == 0)
				return (-1);
			if ((strlen(p) + strlen(name) + 1) >= sizeof (fullname))
				return (-1);
			(void) strcpy(fullname, p);
			(void) strcat(fullname, "/");
			(void) strcat(fullname, name);
			/*
			** Set doaccess if '.' (as in "../") shows up in name.
			*/
			while (*name != '\0')
				if (*name++ == '.')
					doaccess = TRUE;
			name = fullname;
		}
		if (s->last_tzload && strcmp(s->last_tzload, name) == 0)
			return (0);
		if (doaccess && access(name, 4) != 0)
			return (-1);
		if ((fid = open(name, 0)) == -1)
			return (-1);
	}

	{
		char		*p;
		struct tzhead	*tzhp;
		char		buf[8192];
		ssize_t		cnt;

		cnt = read(fid, buf, sizeof (buf));
		if (close(fid) != 0 || cnt < sizeof (*tzhp))
			return (-1);
		tzhp = (struct tzhead *) buf;
		s->timecnt = detzcode(tzhp->tzh_timecnt);
		s->typecnt = detzcode(tzhp->tzh_typecnt);
		s->charcnt = detzcode(tzhp->tzh_charcnt);
		if (s->timecnt > TZ_MAX_TIMES ||
			s->typecnt == 0 ||
			s->typecnt > TZ_MAX_TYPES ||
			s->charcnt > TZ_MAX_CHARS)
				return (-1);
		if (cnt < sizeof (*tzhp) +
			s->timecnt * (4 + sizeof (char)) +
			s->typecnt * (4 + 2 * sizeof (char)) +
			s->charcnt * sizeof (char))
				return (-1);
		if (s->ats)
			free(s->ats);
		if (s->types)
			free(s->types);
		if (s->ttis)
			free(s->ttis);
		if (s->timecnt == 0) {
			s->ats = 0;
			s->types = 0;
		} else {
			s->ats = calloc(s->timecnt, sizeof (time_t));
			if (s->ats == 0)
				return (-1);
			s->types = calloc(s->timecnt, sizeof (char));
			if (s->types == 0) {
				free(s->ats);
				return (-1);
			}
		}
		s->ttis = calloc(s->typecnt, sizeof (ttinfo_t));
		if (s->ttis == 0) {
			if (s->ats)
				free(s->ats);
			if (s->types)
				free(s->types);
			return (-1);
		}
		/* only calloc more space if needed */
		if (s->charcnt+1 > s->chars_size) {
			if (s->chars)
				free(s->chars);
			s->chars = calloc(s->charcnt+1, sizeof (char));
			s->chars_size = s->charcnt+1;
		}
		if (s->chars == 0) {
			if (s->ats)
				free(s->ats);
			if (s->types)
				free(s->types);
			free(s->ttis);
			return (-1);
		}
		p = buf + sizeof (*tzhp);
		for (i = 0; i < s->timecnt; ++i) {
			s->ats[i] = detzcode(p);
			p += 4;
		}
		for (i = 0; i < s->timecnt; ++i)
			s->types[i] = (unsigned char) *p++;
		for (i = 0; i < s->typecnt; ++i) {
			ttinfo_t	*ttisp;

			ttisp = &s->ttis[i];
			ttisp->tt_gmtoff = detzcode(p);
			p += 4;
			ttisp->tt_isdst = (unsigned char) *p++;
			ttisp->tt_abbrind = (unsigned char) *p++;
		}
		for (i = 0; i < s->charcnt; ++i)
			s->chars[i] = *p++;
		s->chars[i] = '\0';	/* ensure '\0' at end */
	}
	/*
	** Check that all the local time type indices are valid.
	*/
	for (i = 0; i < s->timecnt; ++i)
		if (s->types[i] >= (unsigned char) s->typecnt)
			return (-1);
	/*
	** Check that all abbreviation indices are valid.
	*/
	for (i = 0; i < s->typecnt; ++i)
		if (s->ttis[i].tt_abbrind >= s->charcnt)
			return (-1);

	if (s->last_tzload)
		free(s->last_tzload);
	s->last_tzload = strdup(name);
	return (0);
}


static int
detzcode(char *codep)
{
	int	result;
	int	i;

	result = 0;
	for (i = 0; i < 4; ++i)
		result = (result << 8) | (codep[i] & 0xff);
	return (result);
}


static char *
getsystemTZ(void)
{
	static char *systemTZ = NULL;
	char *tz;
	FILE *fp;

	tz = getenv("TZ");
	if ((tz == NULL) || (tz[0] == '\0')) {
		if (systemTZ)
			return (systemTZ);
	} else
		return (tz);

	/* not set in environment, so look in file */
	systemTZ = tz;
	fp = fopen(TIMEZONE, "r");
	if (fp) {
		char *tzValue;
		char *tzValueEnd;
		char tzFileLine[BUFSIZ];
		size_t  tzstrLength = strlen(TZSTRING);

		while (fgets(tzFileLine, BUFSIZ, fp)) {
			if (tzFileLine[strlen(tzFileLine) - 1] == '\n')
				tzFileLine[strlen(tzFileLine)-1] = '\0';
			if (strncmp(TZSTRING, tzFileLine, tzstrLength) == 0) {
				tzValue = tzFileLine + tzstrLength;
				/* skip leading white space */
				while (isspace(*tzValue))
					tzValue++;
				tzValueEnd = tzValue;
				/* skip to last character in TZ value */
				while ((!isspace(*tzValueEnd)) &&
				    (*tzValueEnd != ';') &&
				    (*tzValueEnd != '#') &&
				    (*tzValueEnd != '\0'))
					tzValueEnd++;
				*tzValueEnd = '\0';
				systemTZ = strdup(tzValue);
				break;
			}
		}
		(void) fclose(fp);
	}

	if (systemTZ == NULL)		/* no entry in file */
		systemTZ = "";

	return (systemTZ);
}
