/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

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


/*
 * A part of this file comes from public domain source, so
 * clarified as of June 5, 1996 by Arthur David Olson
 * (arthur_david_olson@nih.gov).
 */

/*
 * localtime.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)
 */

#pragma weak tzset = _tzset
#pragma weak localtime_r = _localtime_r
#pragma weak gmtime_r = _gmtime_r

#include "synonyms.h"
#include "libc.h"
#include "tsd.h"
#include <stdarg.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 <thread.h>
#include <synch.h>
#include <fcntl.h>
#include <errno.h>
#include <deflt.h>
#include <sys/stat.h>

/* JAN_01_1902 cast to (int) - negative number of seconds from 1970 */
#define	JAN_01_1902		(int)0x8017E880
#define	LEN_TZDIR		(sizeof (TZDIR) - 1)
#define	TIMEZONE		"/etc/default/init"
#define	TZSTRING		"TZ="
#define	HASHTABLE		109

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

/* Days since 1/1/70 to 12/31/(1900 + Y - 1) */
#define	DAYS_SINCE_70(Y) (YR((Y)-1L) - YR(70-1))
#define	YR(X) /* Calc # days since 0 A.D. X = curr. yr - 1900 */ \
	((1900L + (X)) * 365L + (1900L + (X)) / 4L - \
	(1900L + (X)) / 100L + ((1900L + (X)) - 1600L) / 400L)


/*
 * The following macros are replacements for detzcode(), which has
 * been in the public domain versions of the localtime.c code for
 * a long time. The primatives supporting the CVTZCODE macro are
 * implemented differently for different endianness (ie. little
 * vs. big endian) out of necessity, to account for the different
 * byte ordering of the quantities being fetched.  Both versions
 * are substantially faster than the detzcode() macro.  The big
 * endian version is approx. 6.8x faster than detzcode(), the
 * little endian version is approximately 3x faster, due to the
 * extra shifting requiring to change byte order.  The micro
 * benchmarks used to compare were based on the SUNWSpro SC6.1
 * (and later) compilers.
 */

#if defined(__sparc) || defined(__sparcv9)  /* big endian */

#define	GET_LONG(p) \
	    *(uint_t *)(p)

#define	GET_SHORTS(p) \
	    *(ushort_t *)(p) << 16 |\
	    *(ushort_t *)((p) + 2)

#define	GET_CHARS(p) \
	    *(uchar_t *)(p) << 24 |\
	    *(uchar_t *)((p) + 1) << 16 |\
	    *(uchar_t *)((p) + 2) << 8  |\
	    *(uchar_t *)((p) + 3)

#else /* little endian */

#define	GET_BYTE(x) \
	    ((x) & 0xff)

#define	SWAP_BYTES(x) ((\
	    GET_BYTE(x) << 8) |\
	    GET_BYTE((x) >> 8))

#define	SWAP_WORDS(x) ((\
	    SWAP_BYTES(x) << 16) |\
	    SWAP_BYTES((x) >> 16))

#define	GET_LONG(p) \
	    SWAP_WORDS(*(uint_t *)(p))

#define	GET_SHORTS(p) \
	    SWAP_BYTES(*(ushort_t *)(p)) << 16 |\
	    SWAP_BYTES(*(ushort_t *)((p) + 2))

#define	GET_CHARS(p) \
	    GET_BYTE(*(uchar_t *)(p)) << 24 |\
	    GET_BYTE(*(uchar_t *)((p) + 1)) << 16 |\
	    GET_BYTE(*(uchar_t *)((p) + 2)) << 8 |\
	    GET_BYTE(*(uchar_t *)((p) + 3))

#endif


#define	IF_ALIGNED(ptr, byte_alignment) \
			!((uintptr_t)(ptr) & (byte_alignment - 1))

#define	CVTZCODE(p) (int)(\
	    IF_ALIGNED(p, 4) ? GET_LONG(p) :\
	    IF_ALIGNED(p, 2) ? GET_SHORTS(p) : GET_CHARS(p));\
	    p += 4;

#ifndef	FALSE
#define	FALSE	(0)
#endif

#ifndef	TRUE
#define	TRUE	(1)
#endif

extern	mutex_t		_time_lock;

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

const char	_tz_gmt[4] = "GMT";	/* "GMT"  */
const char	_tz_spaces[4] = "   ";	/* "   "  */
static const char	_posix_gmt0[5] = "GMT0";	/* "GMT0" */

typedef struct ttinfo {			/* Time type information */
	long		tt_gmtoff;	/* GMT offset in seconds */
	int		tt_isdst;	/* used to set tm_isdst */
	int		tt_abbrind;	/* abbreviation list index */
	int		tt_ttisstd;	/* TRUE if trans is std time */
	int		tt_ttisgmt;	/* TRUE if transition is GMT */
} ttinfo_t;

typedef struct lsinfo {			/* Leap second information */
	time_t		ls_trans;	/* transition time */
	long		ls_corr;	/* correction to apply */
} lsinfo_t;

typedef struct previnfo {		/* Info about *prev* trans */
	ttinfo_t	*std;		/* Most recent std type */
	ttinfo_t	*alt;		/* Most recent alt type */
} prev_t;

typedef enum {
	MON_WEEK_DOW,		/* Mm.n.d - month, week, day of week */
	JULIAN_DAY,		/* Jn - Julian day */
	DAY_OF_YEAR		/* n - day of year */
} posrule_type_t;

typedef struct {
	posrule_type_t	r_type;		/* type of rule */
	int		r_day;		/* day number of rule */
	int		r_week;		/* week number of rule */
	int		r_mon;		/* month number of rule */
	long		r_time;		/* transition time of rule */
} rule_t;

typedef struct {
	rule_t		*rules[2];
	long		offset[2];
	long long	rtime[2];
} posix_daylight_t;

/*
 * Note: ZONERULES_INVALID used for global curr_zonerules variable, but not
 * for zonerules field of state_t.
 */
typedef enum {
	ZONERULES_INVALID, POSIX, POSIX_USA, ZONEINFO
} zone_rules_t;

/*
 * The following members are allocated from the libc-internal malloc:
 *
 *	zonename
 *	chars
 */
typedef struct state {
	const char	*zonename;		/* Timezone */
	struct state	*next;			/* next state */
	zone_rules_t	zonerules;		/* Type of zone */
	int		daylight;		/* daylight global */
	long		default_timezone;	/* Def. timezone val */
	long		default_altzone;	/* Def. altzone val */
	const char	*default_tzname0;	/* Def tz..[0] val */
	const char	*default_tzname1;	/* Def tz..[1] val  */
	int		leapcnt;		/* # leap sec trans */
	int		timecnt;		/* # transitions */
	int		typecnt;		/* # zone types */
	int		charcnt;		/* # zone abbv. chars */
	char		*chars;			/* Zone abbv. chars */
	size_t		charsbuf_size;		/* malloc'ed buflen */
	prev_t		prev[TZ_MAX_TIMES];	/* Pv. trans info */
	time_t		ats[TZ_MAX_TIMES];	/* Trans.  times */
	uchar_t		types[TZ_MAX_TIMES];	/* Type indices */
	ttinfo_t	ttis[TZ_MAX_TYPES];	/* Zone types */
	lsinfo_t	lsis[TZ_MAX_LEAPS];	/* Leap sec trans */
	rule_t		start_rule;		/* For POSIX w/rules */
	rule_t		end_rule;		/* For POSIX w/rules */
} state_t;

typedef struct systemtz {
	const char	*tz;
	state_t		*entry;
	int		flag;
} systemtz_t;

static const char	*namecache;

static state_t	*tzcache[HASHTABLE];

static state_t	*lclzonep;

static struct tm	tm;		/* For non-reentrant use */
static int		is_in_dst;	/* Set if t is in DST */
static zone_rules_t	curr_zonerules = ZONERULES_INVALID;
static int		cached_year;	/* mktime() perf. enhancement */
static long long	cached_secs_since_1970;	/* mktime() perf. */
static int		year_is_cached = FALSE;	/* mktime() perf. */


#define	_2AM		(2 * SECS_PER_HOUR)
#define	FIRSTWEEK	1
#define	LASTWEEK	5

enum wks {
	_1st_week = 1,
	_2nd_week,
	_3rd_week,
	_4th_week,
	_Last_week
};

enum dwk {
	Sun,
	Mon,
	Tue,
	Wed,
	Thu,
	Fri,
	Sat
};

enum mth {
	Jan = 1,
	Feb,
	Mar,
	Apr,
	May,
	Jun,
	Jul,
	Aug,
	Sep,
	Oct,
	Nov,
	Dec
};

/*
 * The following table defines standard USA DST transitions
 * as they have been declared throughout history, disregarding
 * the legally sanctioned local variants.
 *
 * Note:  At some point, this table may be supplanted by
 * more popular 'posixrules' logic.
 */
typedef struct {
	int	s_year;
	int	e_year;
	rule_t	start;
	rule_t	end;
} __usa_rules_t;

static const __usa_rules_t	__usa_rules[] = {
	{
		2007, 2037,
		{ MON_WEEK_DOW, Sun, _2nd_week, Mar, _2AM },
		{ MON_WEEK_DOW, Sun, _1st_week, Nov, _2AM },
	},
	{
		1987, 2006,
		{ MON_WEEK_DOW, Sun, _1st_week,  Apr, _2AM },
		{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
	},
	{
		1976, 1986,
		{ MON_WEEK_DOW, Sun, _Last_week, Apr, _2AM },
		{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
	},
	{
		1975, 1975,
		{ MON_WEEK_DOW, Sun, _Last_week, Feb, _2AM },
		{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
	},

	{
		1974, 1974,
		{ MON_WEEK_DOW, Sun, _1st_week,  Jan, _2AM },
		{ MON_WEEK_DOW, Sun, _Last_week, Nov, _2AM },
	},
	/*
	 * The entry below combines two previously separate entries for
	 * 1969-1973 and 1902-1968
	 */
	{
		1902, 1973,
		{ MON_WEEK_DOW, Sun, _Last_week, Apr, _2AM },
		{ MON_WEEK_DOW, Sun, _Last_week, Oct, _2AM },
	}
};
#define	MAX_RULE_TABLE	(sizeof (__usa_rules) / sizeof (__usa_rules_t) - 1)

/*
 * Prototypes for static functions.
 */
static systemtz_t *getsystemTZ(systemtz_t *);
static const char *getzname(const char *, int);
static const char *getnum(const char *, int *, int, int);
static const char *getsecs(const char *, long *);
static const char *getoffset(const char *, long *);
static const char *getrule(const char *, rule_t *, int);
static int	load_posixinfo(const char *, state_t *);
static int	load_zoneinfo(const char *, state_t *);
static void	ltzset_u(time_t, systemtz_t *);
static struct tm *offtime_u(time_t, long, struct tm *);
static int	posix_check_dst(long long, state_t *);
static int	posix_daylight(long long *, int, posix_daylight_t *);
static void	set_zone_context(time_t);

/*
 * 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);
	}
}

/*
 * Accepts a time_t, returns a tm struct based on it, with
 * no local timezone adjustment.
 *
 * This routine is the thread-safe variant of gmtime(), and
 * requires that the call provide the address of their own tm
 * struct.
 *
 * Locking is not done here because set_zone_context()
 * is not called, thus timezone, altzone, and tzname[] are not
 * accessed, no memory is allocated, and no common dynamic
 * data is accessed.
 *
 * See ctime(3C)
 */
struct tm *
_gmtime_r(const time_t *timep, struct tm *p_tm)
{
	return (offtime_u((time_t)*timep, 0L, p_tm));
}

/*
 * Accepts a time_t, returns a tm struct based on it, with
 * no local timezone adjustment.
 *
 * This function is explicitly NOT THREAD-SAFE.  The standards
 * indicate it should provide its results in its own statically
 * allocated tm struct that gets overwritten. The thread-safe
 * variant is gmtime_r().  We make it mostly thread-safe by
 * allocating its buffer in thread-specific data.
 *
 * See ctime(3C)
 */
struct tm *
gmtime(const time_t *timep)
{
	struct tm *p_tm = tsdalloc(_T_STRUCT_TM, sizeof (struct tm), NULL);

	if (p_tm == NULL)	/* memory allocation failure */
		p_tm = &tm;	/* use static buffer and hope for the best */
	return (_gmtime_r(timep, p_tm));
}

/*
 * This is the hashing function, based on the input timezone name.
 */
static int
get_hashid(const char *id)
{
	const unsigned char	*s = (const unsigned char *)id;
	unsigned char	c;
	unsigned int	h;

	h = *s++;
	while ((c = *s++) != '\0') {
		h = (h << 5) - h + c;
	}
	return ((int)(h % HASHTABLE));
}

/*
 * find_zone() gets the hashid for zonename, then uses the hashid
 * to search the hash table for the appropriate timezone entry.  If
 * the entry for zonename is found in the hash table, return a pointer
 * to the entry.  Otherwise, update the input link_prev and link_next
 * to the addresses of pointers for the caller to update to add the new
 * entry to the hash table.
 */
static state_t *
find_zone(const char *zonename, state_t ***link_prev, state_t **link_next)
{
	int	hashid;
	state_t	*cur, *prv;

	hashid = get_hashid(zonename);
	cur = tzcache[hashid];
	prv = NULL;
	while (cur) {
		int	res;
		res = strcmp(cur->zonename, zonename);
		if (res == 0) {
			return (cur);
		} else if (res > 0) {
			break;
		}
		prv = cur;
		cur = cur->next;
	}
	if (prv) {
		*link_prev = &prv->next;
		*link_next = cur;
	} else {
		*link_prev = &tzcache[hashid];
		*link_next = NULL;
	}
	return (NULL);
}


/*
 * Returns tm struct based on input time_t argument, correcting
 * for the local timezone, producing documented side-effects
 * to extern global state, timezone, altzone, daylight and tzname[].
 *
 * localtime_r() is the thread-safe variant of localtime().
 *
 * IMPLEMENTATION NOTE:
 *
 *	Locking slows multithreaded access and is probably ultimately
 *	unnecessary here. The POSIX specification is a bit vague
 *	as to whether the extern variables set by tzset() need to
 *	set as a result of a call to localtime_r()
 *
 *	Currently, the spec only mentions that tzname[] doesn't
 *	need to be set.  As soon as it becomes unequivocal
 *	that the external zone state doesn't need to be asserted
 *	for this call, and it really doesn't make much sense
 *	to set common state from multi-threaded calls made to this
 *	function, locking can be dispensed with here.
 *
 *	local zone state would still need to be aquired for the
 *	time in question in order for calculations elicited here
 *	to be correct, but that state wouldn't need to be shared,
 *	thus no multi-threaded synchronization would be required.
 *
 *	It would be nice if POSIX would approve an ltzset_r()
 *	function, but if not, it wouldn't stop us from making one
 *	privately.
 *
 *	localtime_r() can now return NULL if overflow is detected.
 *	offtime_u() is the function that detects overflow, and sets
 *	errno appropriately.  We unlock before the call to offtime_u(),
 *	so that lmutex_unlock() does not reassign errno.  The function
 *	offtime_u() is MT-safe and does not have to be locked.  Use
 *	my_is_in_dst to reference local copy of is_in_dst outside locks.
 *
 * See ctime(3C)
 */
struct tm *
_localtime_r(const time_t *timep, struct tm *p_tm)
{
	long	offset;
	struct tm *rt;
	int	my_is_in_dst;
	systemtz_t	stz;
	systemtz_t	*tzp;

	tzp = getsystemTZ(&stz);

	lmutex_lock(&_time_lock);
	ltzset_u(*timep, tzp);
	if (lclzonep == NULL) {
		lmutex_unlock(&_time_lock);
		if (tzp->flag)
			free(tzp->entry);
		return (offtime_u(*timep, 0L, p_tm));
	}
	my_is_in_dst = is_in_dst;
	offset = (my_is_in_dst) ? -altzone : -timezone;
	lmutex_unlock(&_time_lock);
	rt = offtime_u(*timep, offset, p_tm);
	p_tm->tm_isdst = my_is_in_dst;
	if (tzp->flag)
		free(tzp->entry);
	return (rt);
}

/*
 * Accepts a time_t, returns a tm struct based on it, correcting
 * for the local timezone.  Produces documented side-effects to
 * extern global timezone state data.
 *
 * This function is explicitly NOT THREAD-SAFE.  The standards
 * indicate it should provide its results in its own statically
 * allocated tm struct that gets overwritten. The thread-safe
 * variant is localtime_r().  We make it mostly thread-safe by
 * allocating its buffer in thread-specific data.
 *
 * localtime() can now return NULL if overflow is detected.
 * offtime_u() is the function that detects overflow, and sets
 * errno appropriately.
 *
 * See ctime(3C)
 */
struct tm *
localtime(const time_t *timep)
{
	struct tm *p_tm = tsdalloc(_T_STRUCT_TM, sizeof (struct tm), NULL);

	if (p_tm == NULL)	/* memory allocation failure */
		p_tm = &tm;	/* use static buffer and hope for the best */
	return (_localtime_r(timep, p_tm));
}

/*
 * This function takes a pointer to a tm struct and returns a
 * normalized time_t, also inducing documented side-effects in
 * extern global zone state variables.  (See mktime(3C)).
 */
time_t
mktime(struct tm *tmptr)
{
	struct tm _tm;
	long long t;		/* must hold more than 32-bit time_t */
	int	temp;
	int	mketimerrno;
	int	overflow;
	systemtz_t	stz;
	systemtz_t	*tzp;

	mketimerrno = errno;

	tzp = getsystemTZ(&stz);

	/* mktime leaves errno unchanged if no error is encountered */

	lmutex_lock(&_time_lock);

	/* Calculate time_t from tm arg.  tm may need to be normalized. */
	t = tmptr->tm_sec + SECSPERMIN * tmptr->tm_min +
	    SECSPERHOUR * tmptr->tm_hour +
	    SECSPERDAY * (tmptr->tm_mday - 1);

	if (tmptr->tm_mon >= 12) {
		tmptr->tm_year += tmptr->tm_mon / 12;
		tmptr->tm_mon %= 12;
	} else if (tmptr->tm_mon < 0) {
		temp = -tmptr->tm_mon;
		tmptr->tm_mon = 0;	/* If tm_mon divides by 12. */
		tmptr->tm_year -= (temp / 12);
		if (temp %= 12) {	/* Remainder... */
			tmptr->tm_year--;
			tmptr->tm_mon = 12 - temp;
		}
	}

	/* Avoid numerous calculations embedded in macro if possible */
	if (!year_is_cached || (cached_year != tmptr->tm_year))	 {
		cached_year = tmptr->tm_year;
		year_is_cached = TRUE;
		/* For boundry values of tm_year, typecasting required */
		cached_secs_since_1970 =
		    (long long)SECSPERDAY * DAYS_SINCE_70(cached_year);
	}
	t += cached_secs_since_1970;

	if (isleap(tmptr->tm_year + TM_YEAR_BASE))
		t += SECSPERDAY * __lyday_to_month[tmptr->tm_mon];
	else
		t += SECSPERDAY * __yday_to_month[tmptr->tm_mon];

	ltzset_u((time_t)t, tzp);
	/* Attempt to convert time to GMT based on tm_isdst setting */
	t += (tmptr->tm_isdst > 0) ? altzone : timezone;

#ifdef _ILP32
	overflow = t > LONG_MAX || t < LONG_MIN ||
			tmptr->tm_year < 1 || tmptr->tm_year > 138;
#else
	overflow = t > LONG_MAX || t < LONG_MIN;
#endif
	set_zone_context((time_t)t);
	if (tmptr->tm_isdst < 0) {
		long dst_delta = timezone - altzone;
		switch (curr_zonerules) {
		case ZONEINFO:
			if (is_in_dst) {
				t -= dst_delta;
				set_zone_context((time_t)t);
				if (is_in_dst) {
					(void) offtime_u((time_t)t,
						    -altzone, &_tm);
					_tm.tm_isdst = 1;
				} else {
					(void) offtime_u((time_t)t,
						    -timezone, &_tm);
				}
			} else {
				(void) offtime_u((time_t)t, -timezone, &_tm);
			}
			break;
		case POSIX_USA:
		case POSIX:
			if (is_in_dst) {
				t -= dst_delta;
				set_zone_context((time_t)t);
				if (is_in_dst) {
					(void) offtime_u((time_t)t,
						    -altzone, &_tm);
					_tm.tm_isdst = 1;
				} else {
					(void) offtime_u((time_t)t,
						    -timezone, &_tm);
				}
			} else { /* check for ambiguous 'fallback' transition */
				set_zone_context((time_t)t - dst_delta);
				if (is_in_dst) {  /* In fallback, force DST */
					t -= dst_delta;
					(void) offtime_u((time_t)t,
						    -altzone, &_tm);
					_tm.tm_isdst = 1;
				} else {
					(void) offtime_u((time_t)t,
						    -timezone, &_tm);
				}
			}
			break;

		case ZONERULES_INVALID:
			(void) offtime_u((time_t)t, 0L, &_tm);
			break;

		}
	} else if (is_in_dst) {
		(void) offtime_u((time_t)t, -altzone, &_tm);
		_tm.tm_isdst = 1;
	} else {
		(void) offtime_u((time_t)t, -timezone, &_tm);
	}

	if (overflow || t > LONG_MAX || t < LONG_MIN) {
		mketimerrno = EOVERFLOW;
		t = -1;
	} else {
		*tmptr = _tm;
	}

	lmutex_unlock(&_time_lock);

	if (tzp->flag)
		free(tzp->entry);
	errno = mketimerrno;
	return ((time_t)t);
}

/*
 * Sets extern global zone state variables based on the current
 * time.  Specifically, tzname[], timezone, altzone, and daylight
 * are updated.  See ctime(3C) manpage.
 */
void
_tzset(void)
{
	systemtz_t	stz;
	systemtz_t	*tzp;

	tzp = getsystemTZ(&stz);

	lmutex_lock(&_time_lock);
	ltzset_u(time(NULL), tzp);
	lmutex_unlock(&_time_lock);
	if (tzp->flag)
		free(tzp->entry);
}

void
_ltzset(time_t tim)
{
	systemtz_t	stz;
	systemtz_t	*tzp;

	tzp = getsystemTZ(&stz);

	lmutex_lock(&_time_lock);
	ltzset_u(tim, tzp);
	lmutex_unlock(&_time_lock);
	if (tzp->flag)
		free(tzp->entry);
}

/*
 * Loads local zone information if TZ changed since last time zone
 * information was loaded, or if this is the first time thru.
 * We already hold _time_lock; no further locking is required.
 */
static void
ltzset_u(time_t t, systemtz_t *tzp)
{
	const char	*zonename = tzp->tz;
	state_t	*entry, **p, *q;

	if (zonename == NULL || *zonename == '\0')
		zonename = _posix_gmt0;

	if (curr_zonerules != ZONERULES_INVALID &&
	    strcmp(namecache, zonename) == 0) {
		set_zone_context(t);
		return;
	}

	entry = find_zone(zonename, &p, &q);
	if (entry == NULL) {
		/*
		 * No timezone entry found in hash table, so load it,
		 * and create a new timezone entry.
		 */
		char	*newzonename, *charsbuf;

		/* Invalidate the current timezone */
		curr_zonerules = ZONERULES_INVALID;

		newzonename = libc_strdup(zonename);
		daylight = 0;
		entry = tzp->entry;

		if (entry == NULL || newzonename == NULL) {
			/* something wrong happened. */
			if (newzonename != NULL)
				libc_free(newzonename);
			timezone = altzone = 0;
			is_in_dst = 0;
			tzname[0] = (char *)_tz_gmt;
			tzname[1] = (char *)_tz_spaces;
			return;
		}

		/*
		 * Builds transition cache and sets up zone state data for zone
		 * specified in TZ, which can be specified as a POSIX zone or an
		 * Olson zoneinfo file reference.
		 *
		 * If local data cannot be parsed or loaded, the local zone
		 * tables are set up for GMT.
		 *
		 * Unless a leading ':' is prepended to TZ, TZ is initially
		 * parsed as a POSIX zone;  failing that, it reverts to
		 * a zoneinfo check.
		 * However, if a ':' is prepended, the zone will *only* be
		 * parsed as zoneinfo.  If any failure occurs parsing or
		 * loading a zoneinfo TZ, GMT data is loaded for the local zone.
		 *
		 * Example:  There is a zoneinfo file in the standard
		 * distribution called 'PST8PDT'.  The only way the user can
		 * specify that file under Solaris is to set TZ to ":PST8PDT".
		 * Otherwise the initial parse of PST8PDT as a POSIX zone will
		 * succeed and be used.
		 */
		if ((charsbuf = libc_malloc(TZ_MAX_CHARS)) == NULL) {
			libc_free(newzonename);

			timezone = altzone = 0;
			is_in_dst = 0;
			tzname[0] = (char *)_tz_gmt;
			tzname[1] = (char *)_tz_spaces;
			return;
		}
		entry->charsbuf_size = TZ_MAX_CHARS;
		entry->chars = charsbuf;
		entry->default_tzname0 = _tz_gmt;
		entry->default_tzname1 = _tz_spaces;
		entry->zonename = newzonename;

		if (*zonename == ':') {
			if (load_zoneinfo(zonename + 1, entry) != 0) {
				(void) load_posixinfo(_posix_gmt0, entry);
			}
		} else if (load_posixinfo(zonename, entry) != 0) {
			if (load_zoneinfo(zonename, entry) != 0) {
				(void) load_posixinfo(_posix_gmt0, entry);
			}
		}
		/*
		 * The pre-allocated buffer is used; reset the free flag
		 * so the buffer won't be freed.
		 */
		tzp->flag = 0;
		entry->next = q;
		*p = entry;
	}

	curr_zonerules = entry->zonerules;
	namecache = entry->zonename;
	daylight = entry->daylight;
	lclzonep = entry;

	set_zone_context(t);
}

/*
 * Sets timezone, altzone, tzname[], extern globals, to represent
 * disposition of t with respect to TZ; See ctime(3C). is_in_dst,
 * internal global is also set.  daylight is set at zone load time.
 *
 * Issues:
 *
 *	In this function, any time_t not located in the cache is handled
 *	as a miss.  To build/update transition cache, load_zoneinfo()
 *	must be called prior to this routine.
 *
 *	If POSIX zone, cache miss penalty is slightly degraded
 *	performance.  For zoneinfo, penalty is decreased is_in_dst
 *	accuracy.
 *
 *	POSIX, despite its chicken/egg problem, ie. not knowing DST
 *	until time known, and not knowing time until DST known, at
 *	least uses the same algorithm for 64-bit time as 32-bit.
 *
 *	The fact that zoneinfo files only contain transistions for 32-bit
 *	time space is a well known problem, as yet unresolved.
 *	Without an official standard for coping with out-of-range
 *	zoneinfo times,  assumptions must be made.  For now
 *	the assumption is:   If t exceeds 32-bit boundries and local zone
 *	is zoneinfo type, is_in_dst is set to to 0 for negative values
 *	of t, and set to the same DST state as the highest ordered
 * 	transition in cache for positive values of t.
 */
static void
set_zone_context(time_t t)
{
	prev_t		*prevp;
	int    		lo, hi, tidx;
	ttinfo_t	*ttisp, *std, *alt;

	/* If state data not loaded or TZ busted, just use GMT */
	if (lclzonep == NULL || curr_zonerules == ZONERULES_INVALID) {
		timezone = altzone = 0;
		daylight = is_in_dst = 0;
		tzname[0] = (char *)_tz_gmt;
		tzname[1] = (char *)_tz_spaces;
		return;
	}

	/* Retrieve suitable defaults for this zone */
	altzone = lclzonep->default_altzone;
	timezone = lclzonep->default_timezone;
	tzname[0] = (char *)lclzonep->default_tzname0;
	tzname[1] = (char *)lclzonep->default_tzname1;
	is_in_dst = 0;

	if (lclzonep->timecnt <= 0 || lclzonep->typecnt < 2)
		/* Loaded zone incapable of transitioning. */
		return;

	/*
	 * At least one alt. zone and one transistion exist. Locate
	 * state for 't' quickly as possible.  Use defaults as necessary.
	 */
	lo = 0;
	hi = lclzonep->timecnt - 1;

	if (t < lclzonep->ats[0] || t >= lclzonep->ats[hi]) {

		/*  CACHE MISS.  Calculate DST as best as possible */
		if (lclzonep->zonerules == POSIX_USA ||
		    lclzonep->zonerules == POSIX) {
			/* Must nvoke calculations to determine DST */
			is_in_dst = (daylight) ?
			    posix_check_dst(t, lclzonep) : 0;
			return;
		} else if (t < lclzonep->ats[0]) {   /* zoneinfo... */
			/* t precedes 1st transition.  Use defaults */
			return;
		} else	{    /* zoneinfo */
			/* t follows final transistion.  Use final */
			tidx = hi;
		}

	} else {

		/*  CACHE HIT.  Locate transition using binary search. */

		while (lo <= hi) {
			tidx = (lo + hi) / 2;
			if (t == lclzonep->ats[tidx])
				break;
			else if (t < lclzonep->ats[tidx])
				hi = tidx - 1;
			else
				lo = tidx + 1;
		}
		if (lo > hi)
			tidx = hi;
	}

	/*
	 * Set extern globals based on located transition and summary of
	 * its previous state, which were cached when zone was loaded
	 */
	ttisp = &lclzonep->ttis[lclzonep->types[tidx]];
	prevp = &lclzonep->prev[tidx];

	if ((is_in_dst = ttisp->tt_isdst) == 0) { /* std. time */
		timezone = -ttisp->tt_gmtoff;
		tzname[0] = &lclzonep->chars[ttisp->tt_abbrind];
		if ((alt = prevp->alt) != NULL) {
			altzone = -alt->tt_gmtoff;
			tzname[1] = &lclzonep->chars[alt->tt_abbrind];
		}
	} else { /* alt. time */
		altzone = -ttisp->tt_gmtoff;
		tzname[1] = &lclzonep->chars[ttisp->tt_abbrind];
		if ((std = prevp->std) != NULL) {
			timezone = -std->tt_gmtoff;
			tzname[0] = &lclzonep->chars[std->tt_abbrind];
		}
	}
}

/*
 * This function takes a time_t and gmt offset and produces a
 * tm struct based on specified time.
 *
 * The the following fields are calculated, based entirely
 * on the offset-adjusted value of t:
 *
 * tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec
 * tm_yday. tm_wday.  (tm_isdst is ALWAYS set to 0).
 */

static struct tm *
offtime_u(time_t t, long offset, struct tm *tmptr)
{
	long		days;
	long		rem;
	long		y;
	int		yleap;
	const int	*ip;

	days = t / SECSPERDAY;
	rem = t % SECSPERDAY;
	rem += offset;
	while (rem < 0) {
		rem += SECSPERDAY;
		--days;
	}
	while (rem >= SECSPERDAY) {
		rem -= SECSPERDAY;
		++days;
	}
	tmptr->tm_hour = (int)(rem / SECSPERHOUR);
	rem = rem % SECSPERHOUR;
	tmptr->tm_min = (int)(rem / SECSPERMIN);
	tmptr->tm_sec = (int)(rem % SECSPERMIN);

	tmptr->tm_wday = (int)((EPOCH_WDAY + days) % DAYSPERWEEK);
	if (tmptr->tm_wday < 0)
		tmptr->tm_wday += DAYSPERWEEK;
	y = EPOCH_YEAR;
	while (days < 0 || days >= (long)__year_lengths[yleap = isleap(y)]) {
		long newy;

		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;
	}
	tmptr->tm_year = (int)(y - TM_YEAR_BASE);
	tmptr->tm_yday = (int)days;
	ip = __mon_lengths[yleap];
	for (tmptr->tm_mon = 0; days >=
		(long)ip[tmptr->tm_mon]; ++(tmptr->tm_mon))
			days = days - (long)ip[tmptr->tm_mon];
	tmptr->tm_mday = (int)(days + 1);
	tmptr->tm_isdst = 0;

#ifdef _LP64
	/* do as much as possible before checking for error. */
	if ((y > (long)INT_MAX + TM_YEAR_BASE) ||
	    (y < (long)INT_MIN + TM_YEAR_BASE)) {
		errno = EOVERFLOW;
		return (NULL);
	}
#endif
	return (tmptr);
}

/*
 * Check whether DST is set for time in question.  Only applies to
 * POSIX timezones.  If explicit POSIX transition rules were provided
 * for the current zone, use those, otherwise use default USA POSIX
 * transitions.
 */
static int
posix_check_dst(long long t, state_t *sp)
{
	struct tm	gmttm;
	long long	jan01;
	int		year, i, idx, ridx;
	posix_daylight_t	pdaylight;

	(void) offtime_u(t, 0L, &gmttm);

	year = gmttm.tm_year + 1900;
	jan01 = t - ((gmttm.tm_yday * SECSPERDAY) +
			(gmttm.tm_hour * SECSPERHOUR) +
			(gmttm.tm_min * SECSPERMIN) + gmttm.tm_sec);
	/*
	 * If transition rules were provided for this zone,
	 * use them, otherwise, default to USA daylight rules,
	 * which are historically correct for the continental USA,
	 * excluding local provisions.  (This logic may be replaced
	 * at some point in the future with "posixrules" to offer
	 * more flexibility to the system administrator).
	 */
	if (sp->zonerules == POSIX)	 {	/* POSIX rules */
		pdaylight.rules[0] = &sp->start_rule;
		pdaylight.rules[1] = &sp->end_rule;
	} else { 			/* POSIX_USA: USA */
		i = 0;
		while (year < __usa_rules[i].s_year && i < MAX_RULE_TABLE) {
			i++;
		}
		pdaylight.rules[0] = (rule_t *)&__usa_rules[i].start;
		pdaylight.rules[1] = (rule_t *)&__usa_rules[i].end;
	}
	pdaylight.offset[0] = timezone;
	pdaylight.offset[1] = altzone;

	idx = posix_daylight(&jan01, year, &pdaylight);
	ridx = !idx;

	/*
	 * Note:  t, rtime[0], and rtime[1] are all bounded within 'year'
	 * beginning on 'jan01'
	 */
	if (t >= pdaylight.rtime[idx] && t < pdaylight.rtime[ridx]) {
		return (ridx);
	} else {
		return (idx);
	}
}

/*
 * Given January 1, 00:00:00 GMT for a year as an Epoch-relative time,
 * along with the integer year #, a posix_daylight_t that is composed
 * of two rules, and two GMT offsets (timezone and altzone), calculate
 * the two Epoch-relative times the two rules take effect, and return
 * them in the two rtime fields of the posix_daylight_t structure.
 * Also update janfirst by a year, by adding the appropriate number of
 * seconds depending on whether the year is a leap year or not.  (We take
 * advantage that this routine knows the leap year status.)
 */
static int
posix_daylight(long long *janfirst, int year, posix_daylight_t *pdaylightp)
{
	rule_t	*rulep;
	long	offset;
	int	idx;
	int	i, d, m1, yy0, yy1, yy2, dow;
	long	leapyear;
	long long	value;

	static const int	__secs_year_lengths[2] = {
		DAYS_PER_NYEAR * SECSPERDAY,
		DAYS_PER_LYEAR * SECSPERDAY
	};

	leapyear = isleap(year);

	for (idx = 0; idx < 2; idx++) {
		rulep = pdaylightp->rules[idx];
		offset = pdaylightp->offset[idx];

		switch (rulep->r_type) {

		case MON_WEEK_DOW:
			/*
			 * Mm.n.d - nth "dth day" of month m.
			 */
			value = *janfirst;
			for (i = 0; i < rulep->r_mon - 1; ++i)
				value += __mon_lengths[leapyear][i] *
				    SECSPERDAY;

			/*
			 * Use Zeller's Congruence to get day-of-week of first
			 * day of month.
			 */
			m1 = (rulep->r_mon + 9) % 12 + 1;
			yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
			yy1 = yy0 / 100;
			yy2 = yy0 % 100;
			dow = ((26 * m1 - 2) / 10 +
			    1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;

			if (dow < 0)
				dow += DAYSPERWEEK;

			/*
			 * Following heuristic increases accuracy of USA rules
			 * for negative years.
			 */
			if (year < 1 && leapyear)
				++dow;
			/*
			 * "dow" is the day-of-week of the first day of the
			 * month.  Get the day-of-month, zero-origin, of the
			 * first "dow" day of the month.
			 */
			d = rulep->r_day - dow;
			if (d < 0)
				d += DAYSPERWEEK;
			for (i = 1; i < rulep->r_week; ++i) {
				if (d + DAYSPERWEEK >=
				    __mon_lengths[leapyear][rulep->r_mon - 1])
					break;
				d += DAYSPERWEEK;
			}
			/*
			 * "d" is the day-of-month, zero-origin, of the day
			 * we want.
			 */
			value += d * SECSPERDAY;
			break;

		case JULIAN_DAY:
			/*
			 * Jn - Julian day, 1 == Jan 1, 60 == March 1 even
			 * in leap yrs.
			 */
			value = *janfirst + (rulep->r_day - 1) * SECSPERDAY;
			if (leapyear && rulep->r_day >= 60)
				value += SECSPERDAY;
			break;

		case DAY_OF_YEAR:
			/*
			 * n - day of year.
			 */
			value = *janfirst + rulep->r_day * SECSPERDAY;
			break;
		}
		pdaylightp->rtime[idx] = value + rulep->r_time + offset;
	}
	*janfirst += __secs_year_lengths[leapyear];

	return ((pdaylightp->rtime[0] > pdaylightp->rtime[1]) ? 1 : 0);
}

/*
 * Try to load zoneinfo file into internal transition tables using name
 * indicated in TZ, and do validity checks.  The format of zic(1M)
 * compiled zoneinfo files isdescribed in tzfile.h
 */
static int
load_zoneinfo(const char *name, state_t *sp)
{
	char	*cp;
	char	*cp2;
	int	i;
	long	cnt;
	int	fid;
	int	ttisstdcnt;
	int	ttisgmtcnt;
	char	*fullname;
	size_t	namelen;
	char	*bufp;
	size_t	flen;
	prev_t	*prevp;
/* LINTED */
	struct	tzhead *tzhp;
	struct	stat64	stbuf;
	ttinfo_t	*most_recent_alt = NULL;
	ttinfo_t	*most_recent_std = NULL;
	ttinfo_t	*ttisp;


	if (name == NULL && (name = TZDEFAULT) == NULL)
		return (-1);

	if ((name[0] == '/') || strstr(name, "../"))
		return (-1);

	/*
	 * We allocate fullname this way to avoid having
	 * a PATH_MAX size buffer in our stack frame.
	 */
	namelen = LEN_TZDIR + 1 + strlen(name) + 1;
	if ((fullname = lmalloc(namelen)) == NULL)
		return (-1);
	(void) strcpy(fullname, TZDIR "/");
	(void) strcpy(fullname + LEN_TZDIR + 1, name);
	if ((fid = open(fullname, O_RDONLY)) == -1) {
		lfree(fullname, namelen);
		return (-1);
	}
	lfree(fullname, namelen);

	if (fstat64(fid, &stbuf) == -1) {
		(void) close(fid);
		return (-1);
	}

	flen = (size_t)stbuf.st_size;
	if (flen < sizeof (struct tzhead)) {
		(void) close(fid);
		return (-1);
	}

	/*
	 * It would be nice to use alloca() to allocate bufp but,
	 * as above, we wish to avoid allocating a big buffer in
	 * our stack frame, and also because alloca() gives us no
	 * opportunity to fail gracefully on allocation failure.
	 */
	cp = bufp = lmalloc(flen);
	if (bufp == NULL) {
		(void) close(fid);
		return (-1);
	}

	if ((cnt = read(fid, bufp, flen)) != flen) {
		lfree(bufp, flen);
		(void) close(fid);
		return (-1);
	}

	if (close(fid) != 0) {
		lfree(bufp, flen);
		return (-1);
	}

	cp += (sizeof (tzhp->tzh_magic)) + (sizeof (tzhp->tzh_reserved));

/* LINTED: alignment */
	ttisstdcnt = CVTZCODE(cp);
/* LINTED: alignment */
	ttisgmtcnt = CVTZCODE(cp);
/* LINTED: alignment */
	sp->leapcnt = CVTZCODE(cp);
/* LINTED: alignment */
	sp->timecnt = CVTZCODE(cp);
/* LINTED: alignment */
	sp->typecnt = CVTZCODE(cp);
/* LINTED: alignment */
	sp->charcnt = CVTZCODE(cp);

	if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
	    sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
	    sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
	    sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
	    (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
	    (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) {
		lfree(bufp, flen);
		return (-1);
	}

	if (cnt - (cp - bufp) < (long)(sp->timecnt * 4 +	/* ats */
	    sp->timecnt +			/* types */
	    sp->typecnt * (4 + 2) +		/* ttinfos */
	    sp->charcnt +			/* chars */
	    sp->leapcnt * (4 + 4) +		/* lsinfos */
	    ttisstdcnt +			/* ttisstds */
	    ttisgmtcnt)) {			/* ttisgmts */
		lfree(bufp, flen);
		return (-1);
	}


	for (i = 0; i < sp->timecnt; ++i) {
/* LINTED: alignment */
		sp->ats[i] = CVTZCODE(cp);
	}

	/*
	 * Skip over types[] for now and load ttis[] so that when
	 * types[] are loaded we can check for transitions to STD & DST.
	 * This allows us to shave cycles in ltzset_u(), including
	 * eliminating the need to check set 'daylight' later.
	 */

	cp2 = (char *)((uintptr_t)cp + sp->timecnt);

	for (i = 0; i < sp->typecnt; ++i) {
		ttisp = &sp->ttis[i];
/* LINTED: alignment */
		ttisp->tt_gmtoff = CVTZCODE(cp2);
		ttisp->tt_isdst = (uchar_t)*cp2++;

		if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) {
			lfree(bufp, flen);
			return (-1);
		}

		ttisp->tt_abbrind = (uchar_t)*cp2++;
		if (ttisp->tt_abbrind < 0 ||
		    ttisp->tt_abbrind > sp->charcnt) {
			lfree(bufp, flen);
			return (-1);
		}
	}

	/*
	 * Since ttis were loaded ahead of types, it is possible to
	 * detect whether daylight is ever set for this zone now, and
	 * also preload other information to avoid repeated lookups later.
	 * This logic facilitates keeping a running tab on the state of
	 * std zone and alternate zone transitions such that timezone,
	 * altzone and tzname[] can be determined quickly via an
	 * index to any transition.
	 *
	 * For transition #0 there are no previous transitions,
	 * so prev->std and prev->alt will be null, but that's OK,
	 * because null prev->std/prev->alt effectively
	 * indicates none existed prior.
	 */

	prevp = &sp->prev[0];

	for (i = 0; i < sp->timecnt; ++i) {

		sp->types[i] = (uchar_t)*cp++;
		ttisp = &sp->ttis[sp->types[i]];

		prevp->std = most_recent_std;
		prevp->alt = most_recent_alt;

		if (ttisp->tt_isdst == 1) {
			most_recent_alt = ttisp;
		} else {
			most_recent_std = ttisp;
		}

		if ((int)sp->types[i] >= sp->typecnt) {
			lfree(bufp, flen);
			return (-1);
		}

		++prevp;
	}
	if (most_recent_alt == NULL)
		sp->daylight = 0;
	else
		sp->daylight = 1;

	/*
	 * Set pointer ahead to where it would have been if we
	 * had read types[] and ttis[] in the same order they
	 * occurred in the file.
	 */
	cp = cp2;
	for (i = 0; i < sp->charcnt; ++i)
		sp->chars[i] = *cp++;

	sp->chars[i] = '\0';	/* ensure '\0' at end */

	for (i = 0; i < sp->leapcnt; ++i) {
		struct lsinfo *lsisp;

		lsisp = &sp->lsis[i];
/* LINTED: alignment */
		lsisp->ls_trans = CVTZCODE(cp);
/* LINTED: alignment */
		lsisp->ls_corr = CVTZCODE(cp);
	}

	for (i = 0; i < sp->typecnt; ++i) {
		ttisp = &sp->ttis[i];
		if (ttisstdcnt == 0) {
			ttisp->tt_ttisstd = FALSE;
		} else {
			ttisp->tt_ttisstd = *cp++;
			if (ttisp->tt_ttisstd != TRUE &&
			    ttisp->tt_ttisstd != FALSE) {
				lfree(bufp, flen);
				return (-1);
			}
		}
	}

	for (i = 0; i < sp->typecnt; ++i) {
		ttisp = &sp->ttis[i];
		if (ttisgmtcnt == 0) {
			ttisp->tt_ttisgmt = FALSE;
		} else {
			ttisp->tt_ttisgmt = *cp++;
			if (ttisp->tt_ttisgmt != TRUE &&
			    ttisp->tt_ttisgmt != FALSE) {
				lfree(bufp, flen);
				return (-1);
			}
		}
	}

	/*
	 * Other defaults set at beginning of this routine
	 * to cover case where zoneinfo file cannot be loaded
	 */
	sp->default_timezone = -sp->ttis[0].tt_gmtoff;
	sp->default_altzone  = 0;
	sp->default_tzname0  = &sp->chars[0];
	sp->default_tzname1  = _tz_spaces;

	lfree(bufp, flen);

	sp->zonerules = ZONEINFO;

	return (0);
}

/*
 * Given a POSIX section 8-style TZ string, fill in transition tables.
 *
 * Examples:
 *
 * TZ = PST8 or GMT0
 *	Timecnt set to 0 and typecnt set to 1, reflecting std time only.
 *
 * TZ = PST8PDT or PST8PDT7
 *	Create transition times by applying USA transitions from
 *	Jan 1 of each year covering 1902-2038.  POSIX offsets
 *	as specified in the TZ are used to calculate the tt_gmtoff
 *	for each of the two zones.  If ommitted, DST defaults to
 *	std. time minus one hour.
 *
 * TZ = <PST8>8PDT  or <PST8>8<PDT9>
 *      Quoted transition.  The values in angled brackets are treated
 *      as zone name text, not parsed as offsets.  The offsets
 *      occuring following the zonename section.  In this way,
 *      instead of PST being displayed for standard time, it could
 *      be displayed as PST8 to give an indication of the offset
 *      of that zone to GMT.
 *
 * TZ = GMT0BST, M3.5.0/1, M10.5.0/2   or  GMT0BST, J23953, J23989
 *	Create transition times based on the application new-year
 *	relative POSIX transitions, parsed from TZ, from Jan 1
 *	for each year covering 1902-2038.  POSIX offsets specified
 *	in TZ are used to calculate tt_gmtoff for each of the two
 *	zones.
 *
 */
static int
load_posixinfo(const char *name, state_t *sp)
{
	const char	*stdname;
	const char	*dstname = 0;
	size_t		stdlen;
	size_t		dstlen;
	long		stdoff = 0;
	long		dstoff = 0;
	time_t		*tranp;
	uchar_t		*typep;
	prev_t		*prevp;
	char		*cp;
	int		year;
	int		i;
	long long	janfirst;
	ttinfo_t	*dst;
	ttinfo_t	*std;
	int		quoted;
	zone_rules_t	zonetype;
	posix_daylight_t	pdaylight;

	zonetype = POSIX_USA;
	stdname = name;

	if ((quoted = (*stdname == '<')) != 0)
		++stdname;

	/* Parse/extract STD zone name, len and GMT offset */
	if (*name != '\0') {
		if ((name = getzname(name, quoted)) == NULL)
			return (-1);
		stdlen = name - stdname;
		if (*name == '>')
			++name;
		if (*name == '\0' || stdlen < 1) {
			return (-1);
		} else {
			if ((name = getoffset(name, &stdoff)) == NULL)
				return (-1);
		}
	}

	/* If DST specified in TZ, extract DST zone details */
	if (*name != '\0') {

		dstname = name;
		if ((quoted = (*dstname == '<')) != 0)
			++dstname;
		if ((name = getzname(name, quoted)) == NULL)
			return (-1);
		dstlen = name - dstname;
		if (dstlen < 1)
		    return (-1);
		if (*name == '>')
			++name;
		if (*name != '\0' && *name != ',' && *name != ';') {
			if ((name = getoffset(name, &dstoff)) == NULL)
				return (-1);
		} else {
			dstoff = stdoff - SECSPERHOUR;
		}

		/* If any present, extract POSIX transitions from TZ */
		if (*name == ',' || *name == ';') {
			/* Backward compatibility using ';' separator */
			int	compat_flag = (*name == ';');
			++name;
			if ((name = getrule(name, &sp->start_rule, compat_flag))
			    == NULL)
				return (-1);
			if (*name++ != ',')
				return (-1);
			if ((name = getrule(name, &sp->end_rule, compat_flag))
			    == NULL)
				return (-1);
			if (*name != '\0')
				return (-1);
			zonetype = POSIX;
		}

		/*
		 * We know STD and DST zones are specified with this timezone
		 * therefore the cache will be set up with 2 transitions per
		 * year transitioning to their respective std and dst zones.
		 */
		sp->daylight = 1;
		sp->typecnt = 2;
		sp->timecnt = 272;

		/*
		 * Insert zone data from POSIX TZ into state table
		 * The Olson public domain POSIX code sets up ttis[0] to be DST,
		 * as we are doing here.  It seems to be the correct behavior.
		 * The US/Pacific zoneinfo file also lists DST as first type.
		 */
		dst = &sp->ttis[0];
		dst->tt_gmtoff = -dstoff;
		dst->tt_isdst = 1;

		std = &sp->ttis[1];
		std->tt_gmtoff = -stdoff;
		std->tt_isdst = 0;

		sp->prev[0].std = NULL;
		sp->prev[0].alt = NULL;

		/* Create transition data based on POSIX TZ */
		tranp = sp->ats;
		prevp  = &sp->prev[1];
		typep  = sp->types;

		/*
		 * We only cache from 1902 to 2037 to avoid transistions
		 * that wrap at the 32-bit boundries, since 1901 and 2038
		 * are not full years in 32-bit time.  The rough edges
		 * will be handled as transition cache misses.
		 */

		janfirst = JAN_01_1902;

		pdaylight.rules[0] = &sp->start_rule;
		pdaylight.rules[1] = &sp->end_rule;
		pdaylight.offset[0] = stdoff;
		pdaylight.offset[1] = dstoff;

		for (i = MAX_RULE_TABLE; i >= 0; i--) {
			if (zonetype == POSIX_USA) {
				pdaylight.rules[0] =
				    (rule_t *)&__usa_rules[i].start;
				pdaylight.rules[1] =
				    (rule_t *)&__usa_rules[i].end;
			}
			for (year = __usa_rules[i].s_year;
			    year <= __usa_rules[i].e_year;
			    year++) {
				int	idx, ridx;
				idx =
				    posix_daylight(&janfirst, year, &pdaylight);
				ridx = !idx;

				/*
				 * Two transitions per year. Since there are
				 * only two zone types for this POSIX zone,
				 * previous std and alt are always set to
				 * &ttis[0] and &ttis[1].
				 */
				*tranp++ = (time_t)pdaylight.rtime[idx];
				*typep++ = idx;
				prevp->std = std;
				prevp->alt = dst;
				++prevp;

				*tranp++ = (time_t)pdaylight.rtime[ridx];
				*typep++ = ridx;
				prevp->std = std;
				prevp->alt = dst;
				++prevp;
			}
		}
	} else {  /* DST wasn't specified in POSIX TZ */

		/*  Since we only have STD time, there are no transitions */
		dstlen = 0;
		sp->daylight = 0;
		sp->typecnt = 1;
		sp->timecnt = 0;
		std = &sp->ttis[0];
		std->tt_gmtoff = -stdoff;
		std->tt_isdst = 0;

	}

	/* Setup zone name character data for state table */
	sp->charcnt = (int)(stdlen + 1);
	if (dstlen != 0)
		sp->charcnt += dstlen + 1;

	/* If bigger than zone name abbv. buffer, grow it */
	if ((size_t)sp->charcnt > sp->charsbuf_size) {
		if ((cp = libc_realloc(sp->chars, sp->charcnt)) == NULL)
			return (-1);
		sp->chars = cp;
		sp->charsbuf_size = sp->charcnt;
	}

	/*
	 * Copy zone name text null-terminatedly into state table.
	 * By doing the copy once during zone loading, setting
	 * tzname[] subsequently merely involves setting pointer
	 *
	 * If either or both std. or alt. zone name < 3 chars,
	 * space pad the deficient name(s) to right.
	 */

	std->tt_abbrind = 0;
	cp = sp->chars;
	(void) strncpy(cp, stdname, stdlen);
	while (stdlen < 3)
		cp[stdlen++] = ' ';
	cp[stdlen] = '\0';

	i = (int)(stdlen + 1);
	if (dstlen != 0) {
		dst->tt_abbrind = i;
		cp += i;
		(void) strncpy(cp, dstname, dstlen);
		while (dstlen < 3)
			cp[dstlen++] = ' ';
		cp[dstlen] = '\0';
	}

	/* Save default values */
	if (sp->typecnt == 1) {
		sp->default_timezone = stdoff;
		sp->default_altzone = stdoff;
		sp->default_tzname0 = &sp->chars[0];
		sp->default_tzname1 = _tz_spaces;
	} else {
		sp->default_timezone = -std->tt_gmtoff;
		sp->default_altzone = -dst->tt_gmtoff;
		sp->default_tzname0 = &sp->chars[std->tt_abbrind];
		sp->default_tzname1 = &sp->chars[dst->tt_abbrind];
	}

	sp->zonerules = zonetype;

	return (0);
}


/*
 * Given a pointer into a time zone string, scan until a character that is not
 * a valid character in a zone name is found.  Return ptr to that character.
 * Return NULL if error (ie. non-printable character located in name)
 */
static const char *
getzname(const char *strp, int quoted)
{
	char	c;

	if (quoted) {
		while ((c = *strp) != '\0' && c != '>' &&
			isgraph((unsigned char)c))
				++strp;
	} else {
		while ((c = *strp) != '\0' && isgraph((unsigned char)c) &&
		    !isdigit((unsigned char)c) && c != ',' && c != '-' &&
			    c != '+')
				++strp;
	}

	/* Found an excessively invalid character.  Discredit whole name */
	if (c != '\0' && !isgraph((unsigned char)c))
		return (NULL);

	return (strp);
}

/*
 * Given pointer into time zone string, extract first
 * number pointed to.  Validate number within range specified,
 * Return ptr to first char following valid numeric sequence.
 */
static const char *
getnum(const char *strp, int *nump, int min, int max)
{
	char	c;
	int	num;

	if (strp == NULL || !isdigit((unsigned char)(c = *strp)))
		return (NULL);
	num = 0;
	do {
		num = num * 10 + (c - '0');
		if (num > max)
			return (NULL);	/* illegal value */
		c = *++strp;
	} while (isdigit((unsigned char)c));
	if (num < min)
		return (NULL);		/* illegal value */
	*nump = num;
	return (strp);
}

/*
 * Given a pointer into a time zone string, extract a number of seconds,
 * in hh[:mm[:ss]] form, from the string.  If an error occurs, return NULL,
 * otherwise, return a pointer to the first character not part of the number
 * of seconds.
 */
static const char *
getsecs(const char *strp, long *secsp)
{
	int	num;

	/*
	 * `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
	 * "M10.4.6/26", which does not conform to Posix,
	 * but which specifies the equivalent of
	 * ``02:00 on the first Sunday on or after 23 Oct''.
	 */
	strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
	if (strp == NULL)
		return (NULL);
	*secsp = num * (long)SECSPERHOUR;
	if (*strp == ':') {
		++strp;
		strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
		if (strp == NULL)
			return (NULL);
		*secsp += num * SECSPERMIN;
		if (*strp == ':') {
			++strp;
			/* `SECSPERMIN' allows for leap seconds.  */
			strp = getnum(strp, &num, 0, SECSPERMIN);
			if (strp == NULL)
				return (NULL);
			*secsp += num;
		}
	}
	return (strp);
}

/*
 * Given a pointer into a time zone string, extract an offset, in
 * [+-]hh[:mm[:ss]] form, from the string.
 * If any error occurs, return NULL.
 * Otherwise, return a pointer to the first character not part of the time.
 */
static const char *
getoffset(const char *strp, long *offsetp)
{
	int	neg = 0;

	if (*strp == '-') {
		neg = 1;
		++strp;
	} else if (*strp == '+') {
		++strp;
	}
	strp = getsecs(strp, offsetp);
	if (strp == NULL)
		return (NULL);		/* illegal time */
	if (neg)
		*offsetp = -*offsetp;
	return (strp);
}

/*
 * Given a pointer into a time zone string, extract a rule in the form
 * date[/time].  See POSIX section 8 for the format of "date" and "time".
 * If a valid rule is not found, return NULL.
 * Otherwise, return a pointer to the first character not part of the rule.
 *
 * If compat_flag is set, support old 1-based day of year values.
 */
static const char *
getrule(const char *strp, rule_t *rulep, int compat_flag)
{
	if (compat_flag == 0 && *strp == 'M') {
		/*
		 * Month, week, day.
		 */
		rulep->r_type = MON_WEEK_DOW;
		++strp;
		strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
		if (strp == NULL)
			return (NULL);
		if (*strp++ != '.')
			return (NULL);
		strp = getnum(strp, &rulep->r_week, 1, 5);
		if (strp == NULL)
			return (NULL);
		if (*strp++ != '.')
			return (NULL);
		strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
	} else if (compat_flag == 0 && *strp == 'J') {
		/*
		 * Julian day.
		 */
		rulep->r_type = JULIAN_DAY;
		++strp;
		strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);

	} else if (isdigit((unsigned char)*strp)) {
		/*
		 * Day of year.
		 */
		rulep->r_type = DAY_OF_YEAR;
		if (compat_flag == 0) {
			/* zero-based day of year */
			strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
		} else {
			/* one-based day of year */
			strp = getnum(strp, &rulep->r_day, 1, DAYSPERLYEAR);
			rulep->r_day--;
		}
	} else {
		return (NULL);		/* ZONERULES_INVALID format */
	}
	if (strp == NULL)
		return (NULL);
	if (*strp == '/') {
		/*
		 * Time specified.
		 */
		++strp;
		strp = getsecs(strp, &rulep->r_time);
	} else	{
		rulep->r_time = 2 * SECSPERHOUR;	/* default = 2:00:00 */
	}
	return (strp);
}

/*
 * Returns default value for TZ as specified in /etc/default/init file, if
 * a default value for TZ is provided there.
 */
static char *
get_default_tz(void)
{
	char	*tz = NULL;
	uchar_t	*tzp, *tzq;
	int	flags;

	if (defopen(TIMEZONE) == 0) {
		flags = defcntl(DC_GETFLAGS, 0);
		TURNON(flags, DC_STRIP_QUOTES);
		(void) defcntl(DC_SETFLAGS, flags);

		if ((tzp = (uchar_t *)defread(TZSTRING)) != NULL) {
			while (isspace(*tzp))
				tzp++;
			tzq = tzp;
			while (!isspace(*tzq) &&
			    *tzq != ';' &&
			    *tzq != '#' &&
			    *tzq != '\0')
				tzq++;
			*tzq = '\0';
			if (*tzp != '\0')
				tz = strdup((char *)tzp);
		}

		(void) defopen(NULL);
	}
	return (tz);
}

static state_t *
get_zone(systemtz_t *tzp)
{
	int	hashid;
	state_t	*m, *p;
	const char *zonename = tzp->tz;

	hashid = get_hashid(zonename);
	m = tzcache[hashid];
	while (m) {
		int	r;
		r = strcmp(m->zonename, zonename);
		if (r == 0) {
			/* matched */
			return (NULL);
		} else if (r > 0) {
			break;
		}
		m = m->next;
	}
	/* malloc() return value is also checked for NULL in ltzset_u() */
	p = malloc(sizeof (state_t));

	/* ltzset_u() resets the free flag to 0 if it uses the p buffer */
	if (p != NULL)
		tzp->flag = 1;
	return (p);
}

/*
 * getsystemTZ() returns the TZ value if it is set in the environment, or
 * it returns the system TZ;  if the systemTZ has not yet been set,
 * get_default_tz() is called to read the /etc/default/init file to get
 * the value.
 *
 * getsystemTZ() also calls get_zone() to do an initial check to see if the
 * timezone is the current timezone, or one that is already loaded in the
 * hash table.  If get_zone() determines the timezone has not yet been loaded,
 * it pre-allocates a buffer for a state_t struct, which ltzset_u() can use
 * later to load the timezone and add to the hash table.
 *
 * The large state_t buffer is allocated here to avoid calls to malloc()
 * within mutex_locks.
 */
static systemtz_t *
getsystemTZ(systemtz_t *stzp)
{
	static const char	*systemTZ = NULL;
	char	*tz;

	assert_no_libc_locks_held();

	stzp->flag = 0;

	tz = getenv("TZ");
	if (tz != NULL && *tz != '\0') {
		stzp->tz = (const char *)tz;
		goto get_entry;
	}

	if (systemTZ != NULL) {
		stzp->tz = systemTZ;
		goto get_entry;
	}

	tz = get_default_tz();
	lmutex_lock(&_time_lock);
	if (systemTZ == NULL) {
		if ((systemTZ = tz) != NULL)	/* found TZ entry in the file */
			tz = NULL;
		else
			systemTZ = _posix_gmt0;	/* no TZ entry in the file */
	}
	lmutex_unlock(&_time_lock);

	if (tz != NULL)		/* someone beat us to it; free our entry */
		free(tz);

	stzp->tz = systemTZ;

get_entry:
	/*
	 * The object referred to by the 1st 'namecache'
	 * may be different from the one by the 2nd 'namecache' below.
	 * But, it does not matter.  The bottomline is at this point
	 * 'namecache' points to non-NULL and whether the string pointed
	 * to by 'namecache' is equivalent to stzp->tz or not.
	 */
	if (namecache != NULL && strcmp(namecache, stzp->tz) == 0) {
		/*
		 * At this point, we found the entry having the same
		 * zonename as stzp->tz exists.  Later we will find
		 * the exact one, so we don't need to allocate
		 * the memory here.
		 */
		stzp->entry = NULL;
	} else {
		/*
		 * At this point, we could not get the evidence that this
		 * zonename had been cached.  We will look into the cache
		 * further.
		 */
		stzp->entry = get_zone(stzp);
	}
	return (stzp);
}