//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/***
*tzset.c - set timezone information and see if we're in daylight time
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       defines _tzset() - set timezone and daylight saving time vars
*
*Revision History:
*       03-??-84  RLB   initial version
*       03-26-86  TC    added minus capability to time difference w.r.t GMT
*       03-27-86  TC    fixed daylight davings time calculation, off by a day
*                       error
*       12-03-86  SKS   daylight savings time is different starting april 1987
*                       Fixed off-by-1 errors when either Apr 30 or Oct 31 is
*                       Sat. Simplified leap year check: this works for
*                       1970-2099 only!
*       11-19-87  SKS   Add __tzset() which calls tzset only the first time
*                       Made _isindst() a near procedure
*       11-25-87  WAJ   Added calls to _lock and _unlock
*       12-11-87  JCR   Added "_LOAD_DS" to declaration
*       01-27-88  SKS   Made _isindst() and _dtoxtime() are no longer near (for
*                       QC)
*       05-24-88  PHG   Merged DLL and normal versions
*       03-20-90  GJF   Made calling type _CALLTYPE1, added #include
*                       <cruntime.h>, removed #include <register.h>, removed
*                       some leftover 16-bit support and fixed the copyright.
*                       Also, cleaned up the formatting a bit.
*       03-23-90  GJF   Made static functions _CALLTYPE4.
*       07-30-90  SBM   Added void to empty function arg lists to create
*                       prototypes
*       10-04-90  GJF   New-style function declarators.
*       01-21-91  GJF   ANSI naming.
*       08-10-92  PBS   Posix Support (TZ stuff).
*       03-30-93  GJF   Ported C8-16 version to Win32.
*       04-06-93  SKS   Replace _CRTAPI* with __cdecl
*       04-07-93  SKS   Replace strdup() with ANSI conformant _strdup()
*       06-28-93  GJF   Limited support for system's notion of time zone
*                       in Windows NT.
*       07-15-93  GJF   Resurrected __tzset().
*       04-22-94  GJF   Made definitions of lastTZ and first_time conditonal
*                       on DLL_FOR_WIN32S.
*       01-10-95  CFW   Debug CRT allocs.
*       02-13-95  GJF   Appended Mac version of source file (somewhat cleaned
*                       up), with appropriate #ifdef-s.
*       04-07-95  JCF   Change gmtFlags with u. Due to Changes in macos\osutils.h
*       06-28-95  CFW   Mac: when TZ not set, _tzname[0,1]="" not "???";
*       08-30-95  GJF   Complete support for Win32's notion of time zones.
*       11-08-95  GJF   Fixed isindst() to release lock.
*       11-15-95  GJF   Ensure dststart, dstend get recomputed after _tzset.
*       01-18-96  GJF   Ensure _tzname[] strings are null-terminated.
*       03-22-96  GJF   Zero out _dstbias if there is no DST. This works
*                       around a bug in NT's GetTimeZoneInformation API.
*       07-25-96  RDK   Removed PMAC init ptr here to clock.c.
*       10-11-96  GJF   Return value of TIME_ZONE_ID_UNKNOWN from 
*                       GetTimeZoneInformation does NOT mean there is no time
*                       zone info (it may simply mean there is no DST).
*       08-28-97  GJF   Fixed underflow adjustment in cvtdate. Also, deleted
*                       some old Win32S support and detab-ed.
*       01-28-98  GJF   Use WideCharToMultiByte API instead of mbstowcs so
*                       that the host's default ANSI codepage is used when we
*                       are in the C locale.
*       02-09-98  GJF   Changes for Win64: removing unnecessary typing of vars
*                       and constants as long.
*       05-11-98  GJF   Use more general leap determination to support time
*                       values past 2099. 
*       08-27-98  GJF   Copy __lc_codepage, a global, to a local var for 
*                       multithread safety.
*       09-25-98  GJF   Minor bug in transition day calculation.
*       04-28-99  GJF   Changed dwFlags arg value to 0 in WideCharToMultiByte
*                       calls to avoid problems with codepage 1258 on NT 5.0.
*       05-17-99  PML   Remove all Macintosh support.
*       06-08-99  GJF   Fixed handling of empty TZ environment variable.
*       09-28-99  PML   Fixed double free of lastTZ (ntbug#390281)
*       11-12-01  GB    Added support for new locale implementation.
*       04-29-02  GB    Added try-finally arounds lock-unlock.
*       03-06-03  SSM   Move IS_LEAP_YEAR to internal.h
*       03-13-04  MSL   Avoid returning from __try for prefast
*       01-26-06  AC    Fix daylight settings in tzset
*                       VSW#561503
*
*******************************************************************************/

#include <cruntime.h>
#include <ctype.h>
#include <ctime.h>
#include <time.h>
#include <stdlib.h>
#include <internal.h>
#ifdef  _POSIX_
#include <limits.h>
#else
#include <mtdll.h>
#include <windows.h>
#endif
#include <setlocal.h>
#include <string.h>
#include <dbgint.h>


#ifndef _POSIX_

/*
 * Number of milliseconds in one day
 */
#define DAY_MILLISEC    (24 * 60 * 60 * 1000)

/*
 * Pointer to a saved copy of the TZ value obtained in the previous call
 * to tzset() set (if any).
 */
static char * lastTZ = NULL;

/*
 * Flag indicating that time zone information came from GetTimeZoneInformation
 * API call.
 */
static int tzapiused;

static TIME_ZONE_INFORMATION tzinfo;

/*
 * Structure used to represent DST transition date/times.
 */
typedef struct {
        int  yr;        /* year of interest */
        int  yd;        /* day of year */
        int  ms;        /* milli-seconds in the day */
        } transitiondate;

/*
 * DST start and end structs.
 */
static transitiondate dststart = { -1, 0, 0 };
static transitiondate dstend   = { -1, 0, 0 };

static int __cdecl _isindst_nolock(struct tm *);

#endif


/***
*void tzset() - sets timezone information and calc if in daylight time
*
*Purpose:
*       Sets the timezone information from the TZ environment variable
*       and then sets _timezone, _daylight, and _tzname. If we're in daylight
*       time is automatically calculated.
*
*Entry:
*       None, reads TZ environment variable.
*
*Exit:
*       sets _daylight, _timezone, and _tzname global vars, no return value
*
*Exceptions:
*
*******************************************************************************/

#ifndef _POSIX_

static void __cdecl _tzset_nolock(void);

void __cdecl __tzset(void)
{
        static int first_time = 0;

        if ( !first_time ) {

            _mlock( _TIME_LOCK );
            __TRY

                if ( !first_time ) {
                    _tzset_nolock();
                    first_time++;
                }

            __FINALLY
                _munlock(_TIME_LOCK );
            __END_TRY_FINALLY

        }
}


void __cdecl _tzset (
        void
        )
{
        _mlock( _TIME_LOCK );
        __TRY

            _tzset_nolock();

        __FINALLY
            _munlock( _TIME_LOCK );
        __END_TRY_FINALLY
}


static void __cdecl _tzset_nolock (
        void
        )
{
    const char *TZ;
    int defused;
    int negdiff = 0;
    unsigned int lc_cp;
	int nochange=FALSE;
    long timezone = 0;
    int daylight = 0;
    long dstbias = 0;
    char **tzname = NULL;

    _mlock(_ENV_LOCK);
    __TRY

_BEGIN_SECURE_CRT_DEPRECATION_DISABLE
        tzname = _tzname;
_END_SECURE_CRT_DEPRECATION_DISABLE

        _ERRCHECK(_get_timezone(&timezone));
        _ERRCHECK(_get_daylight(&daylight));
        _ERRCHECK(_get_dstbias(&dstbias));

        /*
         * Copy codepage to local (only really necessary for multithread case)
         */
        lc_cp = ___lc_codepage_func();

        /*
         * Clear the flag indicated whether GetTimeZoneInformation was used.
         */
        tzapiused = 0;

        /*
         * Set year fields of dststart and dstend structures to -1 to ensure
         * they are recomputed as after this 
         */
        dststart.yr = dstend.yr = -1;

        /*
         * Fetch the value of the TZ environment variable.
         */
        if ( ((TZ = _getenv_helper_nolock("TZ")) == NULL) || (*TZ =='\0') ) {
            /*
             * There is no TZ environment variable, try to use the time zone
             * information from the system.
             */

            /*
             * If there is a lastTZ, discard it
             */
            if ( lastTZ != NULL ) {
                _free_crt(lastTZ);
                lastTZ = NULL;
            }

            if ( GetTimeZoneInformation( &tzinfo ) != 0xFFFFFFFF ) {
                /*
                 * Note that the API was used.
                 */
                tzapiused = 1;

                /*
                 * Derive _timezone value from Bias and StandardBias fields.
                 */
                timezone = tzinfo.Bias * 60;

                if ( tzinfo.StandardDate.wMonth != 0 )
                    timezone += (tzinfo.StandardBias * 60);

                /*
                 * Check to see if there is a daylight time bias. Since the
                 * StandardBias has been added into _timezone, it must be 
                 * compensated for in the value computed for _dstbias.
                 */
                if ( (tzinfo.DaylightDate.wMonth != 0) &&
                     (tzinfo.DaylightBias != 0) )
                {
                    daylight = 1;
                    dstbias = (tzinfo.DaylightBias - tzinfo.StandardBias) *
                               60;
                }
                else {
                        daylight = 0;

                    /*
                     * Set daylight bias to 0 because GetTimeZoneInformation 
                     * may return TIME_ZONE_ID_DAYLIGHT even though there is
                     * no DST (in NT 3.51, just turn off the automatic DST
                     * adjust in the control panel)!
                     */
                    dstbias = 0;
                }

                /*
                 * Try to grab the name strings for both the time zone and the
                 * daylight zone. Note the wide character strings in tzinfo
                 * must be converted to multibyte characters strings. The 
                 * locale codepage, __lc_codepage, is used for this. Note that
                 * if setlocale() with LC_ALL or LC_CTYPE has not been called,
                 * then __lc_codepage will be 0 (_CLOCALECP), which is CP_ACP
                 * (which means use the host's default ANSI codepage).
                 */
                if ( (WideCharToMultiByte( lc_cp,
                                           0,
                                           tzinfo.StandardName,
                                           -1,
                                           tzname[0],
                                           _TZ_STRINGS_SIZE - 1,
                                           NULL, 
                                           &defused ) != 0) &&
                     (!defused) )
                    tzname[0][_TZ_STRINGS_SIZE - 1] = '\0';
                else
                    tzname[0][0] = '\0';

                if ( (WideCharToMultiByte( lc_cp,
                                           0,
                                           tzinfo.DaylightName,
                                           -1,
                                           tzname[1],
                                           _TZ_STRINGS_SIZE - 1,
                                           NULL, 
                                           &defused ) != 0) &&
                     (!defused) )
                    tzname[1][_TZ_STRINGS_SIZE - 1] = '\0';
                else
                    tzname[1][0] = '\0';
            }

            /*
             * Time zone information is unavailable, just return.
             */
            nochange=TRUE;
        }
		else
		{
			if ( (lastTZ != NULL) && (strcmp(TZ, lastTZ) == 0) )
			{
				/*
				* TZ is unchanged from a earlier call (to this function). Just
				* return.
				*/
				nochange=TRUE;
			}
			else
			{

				/*
				* Update lastTZ
				*/
				if ( lastTZ != NULL )
					_free_crt(lastTZ);

				if ((lastTZ = _malloc_crt(strlen(TZ)+1)) == NULL)
				{
					nochange=TRUE;
				}
				else
				{
					_ERRCHECK(strcpy_s(lastTZ, strlen(TZ) + 1, TZ));
				}
			}
		}

		_set_timezone(timezone);
		_set_daylight(daylight);
		_set_dstbias(dstbias);

    __FINALLY
        _munlock(_ENV_LOCK);
    __END_TRY_FINALLY

	if(!nochange)
	{
		/*
		* Process TZ value and update _tzname, _timezone and _daylight.
		*/
		_ERRCHECK(strncpy_s(tzname[0], _TZ_STRINGS_SIZE, TZ, 3));

		/*
		* time difference is of the form:
		*
		*      [+|-]hh[:mm[:ss]]
		*
		* check minus sign first.
		*/
		if ( *(TZ += 3) == '-' ) {
			negdiff++;
			TZ++;
		}

		/*
		* process, then skip over, the hours
		*/
		timezone = atol(TZ) * 3600;

		while ( (*TZ == '+') || ((*TZ >= '0') && (*TZ <= '9')) ) TZ++;

		/*
		* check if minutes were specified
		*/
		if ( *TZ == ':' ) {
			/*
			* process, then skip over, the minutes
			*/
			timezone += atol(++TZ) * 60;
			while ( (*TZ >= '0') && (*TZ <= '9') ) TZ++;

			/*
			* check if seconds were specified
			*/
			if ( *TZ == ':' ) {
				/*
				* process, then skip over, the seconds
				*/
				timezone += atol(++TZ);
				while ( (*TZ >= '0') && (*TZ <= '9') ) TZ++;
			}
		}

		if ( negdiff )
			timezone = -timezone;

		/*
		* finally, check for a DST zone suffix
		*/
		if (*TZ)
			daylight = 1;
		else
			daylight = 0;

		if ( daylight ) {
			_ERRCHECK(strncpy_s(tzname[1], _TZ_STRINGS_SIZE, TZ, 3));
		}
		else
			*tzname[1] = '\0';

		_set_timezone(timezone);
		_set_daylight(daylight);
	}
}

/***
:t static void cvtdate( trantype, datetype, year, month, week, dayofweek,
*                     date, hour, min, second, millisec ) - convert 
*       transition date format
*
*Purpose:
*       Convert the format of a transition date specification to a value of
*       a transitiondate structure.
*
*Entry:
*       int trantype    - 1, if it is the start of DST
*                         0, if is the end of DST (in which case the date is
*                            is a DST date)
*       int datetype    - 1, if a day-in-month format is specified.
*                         0, if an absolute date is specified.
*       int year        - year for which the date is being converted (70 ==
*                         1970)
*       int month       - month (0 == January)
*       int week        - week of month, if datetype == 1 (note that 5== last
*                         week of month), 
*                         0, otherwise.
*       int dayofweek   - day of week (0 == Sunday), if datetype == 1.
*                         0, otherwise.
*       int date        - date of month (1 - 31)
*       int hour        - hours (0 - 23)
*       int min         - minutes (0 - 59)
*       int sec         - seconds (0 - 59)
*       int msec        - milliseconds (0 - 999)
*
*Exit:
*       dststart or dstend is filled in with the converted date.
*
*******************************************************************************/

static void __cdecl cvtdate (
        int trantype,
        int datetype,
        int year,
        int month,
        int week,
        int dayofweek,
        int date,
        int hour,
        int min,
        int sec,
        int msec
        )
{
        int yearday;
        int monthdow;
        long dstbias = 0;

        if ( datetype == 1 ) {

            /*
             * Transition day specified in day-in-month format.
             */

            /*
             * Figure the year-day of the start of the month.
             */
            yearday = 1 + (_IS_LEAP_YEAR(year) ? _lpdays[month - 1] :
                      _days[month - 1]);

            /* 
             * Figure the day of the week of the start of the month.
             */
            monthdow = (yearday + ((year - 70) * 365) +
                        _ELAPSED_LEAP_YEARS(year) + _BASE_DOW) % 7;

            /*
             * Figure the year-day of the transition date
             */
            if ( monthdow <= dayofweek )
                yearday += (dayofweek - monthdow) + (week - 1) * 7;
            else
                yearday += (dayofweek - monthdow) + week * 7;

            /*
             * May have to adjust the calculation above if week == 5 (meaning
             * the last instance of the day in the month). Check if year falls
             * beyond after month and adjust accordingly.
             */
            if ( (week == 5) && 
                 (yearday > (_IS_LEAP_YEAR(year) ? _lpdays[month] :
                             _days[month])) )
            {
                yearday -= 7;
            }
        }
        else {
            /* 
             * Transition day specified as an absolute day
             */
            yearday = _IS_LEAP_YEAR(year) ? _lpdays[month - 1] :
                      _days[month - 1];

            yearday += date;
        }

        if ( trantype == 1 ) {
            /*
             * Converted date was for the start of DST
             */
            dststart.yd = yearday;
            dststart.ms = msec + (1000 * (sec + 60 * (min + 60 * hour)));
            /*
             * Set year field of dststart so that unnecessary calls to
             * cvtdate() may be avoided.
             */
            dststart.yr = year;
        }
        else {
            /*
             * Converted date was for the end of DST
             */
            dstend.yd = yearday;
            dstend.ms = msec + (1000 * (sec + 60 * (min + 60 * hour)));
            /*
             * The converted date is still a DST date. Must convert to a 
             * standard (local) date while being careful the millisecond field
             * does not overflow or underflow.
             */
            _ERRCHECK(_get_dstbias(&dstbias));
            if ( (dstend.ms += (dstbias * 1000)) < 0 ) {
                dstend.ms += DAY_MILLISEC;
                dstend.yd--;
            }
            else if ( dstend.ms >= DAY_MILLISEC ) {
                dstend.ms -= DAY_MILLISEC;
                dstend.yd++;
            }

            /*
             * Set year field of dstend so that unnecessary calls to cvtdate()
             * may be avoided.
             */
            dstend.yr = year;
        }

        return;
}

/***
*int _isindst(tb) - determine if broken-down time falls in DST
*
*Purpose:
*       Determine if the given broken-down time falls within daylight saving
*       time (DST). The DST rules are either obtained from Win32 (tzapiused !=
*       TRUE) or assumed to be USA rules, post 1986.
*
*       If the DST rules are obtained from Win32's GetTimeZoneInformation API,
*       the transition dates to/from DST can be specified in either of two
*       formats. First, a day-in-month format, similar to the way USA rules 
*       are specified, can be used. The transition date is given as the n-th 
*       occurence of a specified day of the week in a specified month. Second,
*       an absolute date can be specified. The two cases are distinguished by
*       the value of wYear field in the SYSTEMTIME structure (0 denotes a
*       day-in-month format).
*
*       USA rules for DST are that a time is in DST iff it is on or after 
*       02:00 on the first Sunday in April, and before 01:00 on the last 
*       Sunday in October.
*
*Entry:
*       struct tm *tb - structure holding broken-down time value
*
*Exit:
*       1, if time represented is in DST
*       0, otherwise
*
*******************************************************************************/

int __cdecl _isindst (
        struct tm *tb
        )
{
        int retval;

        _mlock( _TIME_LOCK );
        __TRY
            retval = _isindst_nolock( tb );
        __FINALLY
            _munlock( _TIME_LOCK );
        __END_TRY_FINALLY

        return retval;
}

static int __cdecl _isindst_nolock (
        struct tm *tb
        )
{
        long ms;
        int daylight = 0;

        _ERRCHECK(_get_daylight(&daylight));
        
        if ( daylight == 0 )
            return 0;

        /*
         * Compute (recompute) the transition dates for daylight saving time
         * if necessary.The yr (year) fields of dststart and dstend is 
         * compared to the year of interest to determine necessity.
         */
        if ( (tb->tm_year != dststart.yr) || (tb->tm_year != dstend.yr) ) {
            if ( tzapiused ) {
                /*
                 * Convert the start of daylight saving time to dststart.
                 */
                if ( tzinfo.DaylightDate.wYear == 0 ) 
                    cvtdate( 1, 
                             1,             /* day-in-month format */
                             tb->tm_year, 
                             tzinfo.DaylightDate.wMonth,
                             tzinfo.DaylightDate.wDay,
                             tzinfo.DaylightDate.wDayOfWeek,
                             0,
                             tzinfo.DaylightDate.wHour,
                             tzinfo.DaylightDate.wMinute,
                             tzinfo.DaylightDate.wSecond,
                             tzinfo.DaylightDate.wMilliseconds );
                else
                    cvtdate( 1,
                             0,             /* absolute date */
                             tb->tm_year,
                             tzinfo.DaylightDate.wMonth,
                             0,
                             0,
                             tzinfo.DaylightDate.wDay,
                             tzinfo.DaylightDate.wHour,
                             tzinfo.DaylightDate.wMinute,
                             tzinfo.DaylightDate.wSecond,
                             tzinfo.DaylightDate.wMilliseconds );
                /*
                 * Convert start of standard time to dstend.
                 */
                if ( tzinfo.StandardDate.wYear == 0 ) 
                    cvtdate( 0, 
                             1,             /* day-in-month format */
                             tb->tm_year, 
                             tzinfo.StandardDate.wMonth,
                             tzinfo.StandardDate.wDay,
                             tzinfo.StandardDate.wDayOfWeek,
                             0,
                             tzinfo.StandardDate.wHour,
                             tzinfo.StandardDate.wMinute,
                             tzinfo.StandardDate.wSecond,
                             tzinfo.StandardDate.wMilliseconds );
                else
                    cvtdate( 0, 
                             0,             /* absolute date */
                             tb->tm_year, 
                             tzinfo.StandardDate.wMonth,
                             0,
                             0,
                             tzinfo.StandardDate.wDay,
                             tzinfo.StandardDate.wHour,
                             tzinfo.StandardDate.wMinute,
                             tzinfo.StandardDate.wSecond,
                             tzinfo.StandardDate.wMilliseconds );

            }
            else {
                /*
                 * GetTimeZoneInformation API was NOT used, or failed. USA 
                 * daylight saving time rules are assumed.
                 */
                int startmonth = 3; /* March */
                int startweek  = 2; /* second week... */
                int endmonth   = 11;/* November */
                int endweek    = 1; /* first week */
                if( 107 > tb->tm_year )
                {
                    startmonth = 4; /* April */
                    startweek  = 1; /* first week... */
                    endmonth   = 10;/* October */
                    endweek    = 5; /* last week */
                }

                cvtdate( 1, 
                         1,
                         tb->tm_year, 
                         startmonth,
                         startweek,
                         0,                 /* ...Sunday */
                         0,
                         2,                 /* 02:00 (2 AM) */
                         0,
                         0,
                         0 );

                cvtdate( 0, 
                         1,
                         tb->tm_year, 
                         endmonth,
                         endweek,
                         0,                 /* ...Sunday */
                         0,
                         2,                 /* 02:00 (2 AM) */
                         0,
                         0,
                         0 );
            }
        }

        /* 
         * Handle simple cases first.
         */
        if ( dststart.yd < dstend.yd ) {
            /*
             * Northern hemisphere ordering
             */
            if ( (tb->tm_yday < dststart.yd) || (tb->tm_yday > dstend.yd) )
                return 0;           
            if ( (tb->tm_yday > dststart.yd) && (tb->tm_yday < dstend.yd) )
                return 1;
        }
        else { 
            /*
             * Southern hemisphere ordering
             */
            if ( (tb->tm_yday < dstend.yd) || (tb->tm_yday > dststart.yd) )
                return 1;
            if ( (tb->tm_yday > dstend.yd) && (tb->tm_yday < dststart.yd) )
                return 0;
        }

        ms = 1000 * (tb->tm_sec + 60 * tb->tm_min + 3600 * tb->tm_hour);

        if ( tb->tm_yday == dststart.yd ) {
            if ( ms >= dststart.ms )
                return 1;
            else
                return 0;
        }
        else {
            /* 
             * tb->tm_yday == dstend.yd
             */
            if ( ms < dstend.ms )
                return 1;
            else
                return 0;
        }

}

#else   /* _POSIX_ */

/*
 *  The following is an implementation of the TZ grammar specified in the
 *  document:
 *
 *       8.1.1 Extension to Time Functions
 *       IEEE Std 1003.1 - 1990
 *       Page 152 - 153
 *
 *  The TZ grammar looks like:
 *
 *          stdoffset[dst[offset][,start[/time],end[/time]]]
 *
 *  Variables used in code:
 *
 *      tzname[0] ==> std
 *      _timezone  ==> offset(the one after 'std')
 *      tzname[1] ==> dst
 *      _dstoffset ==> offset(the one after 'dst')
 *      _startdate ==> start
 *      _starttime ==> time(the one after 'start')
 *      _enddate   ==> end
 *      _endtime   ==> time(the one after 'end')
 *
 */

/*
 *  Refer to the document for the detailed description of fields of _DSTDATE.
 *  Two of Jn, n, and Mm are -1, indicating the one(not -1) is a vaild value.
 */

typedef struct _DSTDATE {
        int Jn; /* -1 or [1, 365](year day and leap day shall not be counted) */
        int n;  /* -1 or [0, 365](year day and leap day shall be counted) */
        int Mm; /* -1 or [1, 12](month) */
        int Mn; /* [1, 5] if Mm != -1 (week) */
        int Md; /* [0, 6] if Mm != -1 (weekday, Sunday == 0) */
} DSTDATE, *PDSTDATE;

#define SEC_PER_HOUR    (60 * 60)
#define SEC_PER_DAY     (SEC_PER_HOUR * 24)


/*
 *  The default TZ in tzset() should look like:
 *
 *       TZ = "PST8PDT,M4.1.0/2:00,M10.5.0/2:00";
 */

/* Day light saving start/end date and default vaules */
static DSTDATE _startdate = { -1, -1, 4, 1, 0};
static DSTDATE _enddate = {-1, -1, 10, 5, 0};


/* Seconds since midnight on _startdate/_enddate with default values.
 * _endtime is 1am instead of 2am because the DST end time is 2am
 * local time, which by default is 1am standard time.
 */
long  _starttime = 7200L, _endtime = 7200L;

/*
 *  ParsePosixStdOrDst - parse the std or dst element in TZ.
 *
 *  ENTRY   pch - beginning of the substring in TZ.
 *
 *  RETURN  pointer to one position after the std or dst element parsed,
 *          or NULL if failed.
 */


static  char * __cdecl
ParsePosixStdOrDst(
        char *pch
        )
{
#define UNWANTED(x) (isdigit(x) || x=='\0' || x==',' || x=='-' || x=='+')
        int i;

        /*
         *  Check against the rule.
         */

        if(*pch == ':' || UNWANTED(*pch)) {
                return NULL;
        }

        /*
         *  Get a valid std or dst(i.e. 3 <= lenth_of(std | dst) <= TZNAME_MAX).
         */

        for(i=1, ++pch; (i < TZNAME_MAX) && !UNWANTED(*pch); i++, pch++) {
                ;
        }

        /*
         *  pch now point to 1 position after the valid std or dst.
         */

        return (i >= 3) ? pch : NULL;
}


/*
 *  ParsePosixOffset - parse the offset element in TZ. The format of time is:
 *
 *                   [- | +]hh[:mm[:ss]]
 *
 *  ENTRY   pch - beginning of the substring in TZ.
 *
 *          ptime - pointer to a variable(_timezone or _dstoffset) storing the
 *                  time(in seconds) parsed.
 *
 *  RETURN  pointer to one position after the end of the offset element parsed.
 */


static  char * __cdecl
ParsePosixOffset(
        char *pch,
        long *poffset
        )
{
        int  fNegative;
        long offset;

        if((fNegative = (*pch == '-')) || *pch == '+') {
                pch++;
        }

        offset = atol(pch)*3600L; /* hh */

        while(*pch && isdigit(*pch)) {
                pch++;
        }

        if(*pch == ':') {
                offset += atol(++pch)*60L; /* mm */
                while(*pch && isdigit(*pch)) {
                        pch++;
                }

                if(*pch == ':') {
                        offset += atol(++pch); /* ss */
                        while(*pch && isdigit(*pch)) {
                                pch++;
                        }
                }
        }

        *poffset = fNegative ? -offset : offset;

        return pch;
}



/*
 *  ParsePosixDate - parse the date element in TZ. The format of date is one
 *                   of following:
 *
 *                   Jn, n, and Mm.n.d
 *
 *  ENTRY   pch - beginning of the substring in TZ.
 *
 *          pDstDate - pointer to _startdate or _enddate storing the result.
 *
 *  RETURN  pointer to one position after the end of the date element parsed,
 *          or NULL if failed.
 */

static  char * __cdecl
ParsePosixDate(
        char *pch,
        PDSTDATE pDstDate
        )
{
        pDstDate->Jn = -1;
        pDstDate->n = -1;
        pDstDate->Mn = -1;

        /*
         *  Two out of the three -1's will remain.
         */

        if(*pch == 'J') {                        /* Jn */
                pDstDate->Jn = atoi(++pch);
        } else if(*pch != 'M') {                 /* n */
                pDstDate->n = atoi(pch);
        } else {                                 /* Mm.n.d */

                pDstDate->Mm = atoi(++pch);

                if(*++pch != '.') {
                        pch++;
                }
                pDstDate->Mn = atoi(++pch);

                if(*++pch != '.') {
                        pch++;
                }
                pDstDate->Md = atoi(++pch);
        }

        while(*pch && isdigit(*pch)) {
                pch++;
        }

#define IN_RANGE(x, a, b)    (x >= a && x <= b)

        return ((pDstDate->Jn != -1 && IN_RANGE(pDstDate->Jn, 1, 365)) ||
                (pDstDate->n != -1 && IN_RANGE(pDstDate->n, 0, 365)) ||
                (pDstDate->Mm != -1 && IN_RANGE(pDstDate->Mm, 1, 12) &&
                IN_RANGE(pDstDate->Mn, 1, 5) && IN_RANGE(pDstDate->Md, 0, 6)))
                ? pch : NULL;
}

/*
 *  ParsePosixTime - parse the time element in TZ. The format of time is:
 *
 *                   hh[:mm[:ss]]
 *
 *  ENTRY   pch - beginning of the substring in TZ.
 *
 *          ptime - pointer to a variable(_starttime or _endtime) storing the
 *                  time(in seconds) parsed.
 *
 *  RETURN  pointer to one position after the end of the time element parsed.
 */

static  char * __cdecl
ParsePosixTime(
        char *pch,
        long *ptime
        )
{
        long time;

        time = atol(pch)*SEC_PER_HOUR; /* hh */

        while(*pch && isdigit(*pch)) {
                pch++;
        }

        if(*pch == ':') {

                time += atol(++pch)*60L; /* mm */
                while(*pch && isdigit(*pch)) {
                        pch++;
                }

                if(*pch == ':') {

                        time += atol(++pch); /* ss */
                        while(*pch && isdigit(*pch)) {
                                pch++;
                        }
                }
        }

        *ptime = time;

        return pch;
}

/*
 *  tzset -  sets the timezone information from the TZ environment variable.
 *           Global tzname[], _timezone, _daylight, and _dstoffset will be
 *           set. Static _startdate, _enddate, _starttime, and _endtime will
 *           also be set. TZ string looks like:
 *
 *                stdoffset[dst[offset][,start[/time],end[/time]]]
 *
 *  In form of variables: tzname[0]_timezone[tzname[1][_dstoffset]
 *                        [,_startdate[/_starttime],_enddate[/_endtime]]]
 *
 *  ENTRY   none.
 *
 *  RETURN  none.
 */

void __cdecl tzset(
        void
        )
{
        /* pch points to the beginning of an element to be parsed. */
        char *pch;

        /* pchCurr points to one position after the end of last element parsed. */
        char *pchCurr;

        char *TZ;

        _endtime = 7200L;
        _starttime = 7200L;

        if (!(TZ = getenv("TZ")) || !*TZ) {
                TZ = "PST8PDT7,M4.1.0/2:00,M10.5.0/2:00"; /* use default */
        }

        if((pchCurr = ParsePosixStdOrDst(pch=TZ)) == NULL) {
                return;
        }

        memcpy(tzname[0], pch, (int)(pchCurr-pch));
        tzname[0][(int)(pchCurr-pch)] = '\0';

        if((pchCurr = ParsePosixOffset(pch=pchCurr, &_timezone)) == NULL) {
                return;
        }

        _daylight = (*pchCurr != '\0');

        if(!_daylight) {
                return;
        }

        if((pchCurr = ParsePosixStdOrDst(pch=pchCurr)) == NULL) {
                return;
        }

        memcpy(tzname[1], pch, (int)(pchCurr-pch));
        tzname[1][(int)(pchCurr-pch)] = '\0';

        if(isdigit(*pchCurr) || *pchCurr == '-' || *pchCurr == '+') {
                if((pchCurr = ParsePosixOffset(pch=pchCurr, &_dstoffset)) == NULL) {
                        return;
                }
        } else {
                /* default: 1 hour ahead of standard time */
                _dstoffset = _timezone - SEC_PER_HOUR;
        }

        if(*pchCurr == ',') { /* ,start[/time],end[/time] */

                if((pchCurr = ParsePosixDate(pchCurr+1, &_startdate)) == NULL) {
                        goto out;
                }

                if(*pchCurr == '/') {
                        if(!(pchCurr = ParsePosixTime(pchCurr+1, &_starttime))) {
                                goto out;
                        }
                }

                if(*pchCurr != ',') {
                        goto out;
                }

                if ((pchCurr = ParsePosixDate(pchCurr+1, &_enddate)) == NULL) {
                        goto out;
                }

                if (*pchCurr == '/') {
                        if(!(pchCurr = ParsePosixTime(pchCurr+1, &_endtime))) {
                                goto out;
                        }
                }
        }
out:
        /*
         * Adjust the _endtime to account for the fact that
         * dst ends at _endtime local time, rather than
         * standard time.
         */

        _endtime -= (_timezone - _dstoffset);
}


#define DAY1    (4)             /* Jan 1 1970 was a Thursday */

/*
 *  GetDstStartOrEndYearDay - Converts day info from DSTDATE into 0-based
 *                            year-day.
 *
 *  ENTRY   tm_year - the year concerned(tb->tm_year).
 *
 *          pDstDate - pointer to either _startdate or _enddate.
 *
 *  RETURN  the year-day calculated.
 */

static int __cdecl
GetDstStartOrEndYearDay(
        int tm_year,
        PDSTDATE pDstDate
        )
{
        int yday; /* year-day */
        int theyear;

        theyear = tm_year + 1900;

        if(pDstDate->Jn != -1) {

                /*
                 *  Jn is in [1, 365] and leap day is not counted.
                 *  Convert Jn to 0-based yday; Note: 60 is March 1.
                 */


                yday = (IS_LEAP_YEAR(theyear) && (pDstDate->Jn >= 60))
                        ? pDstDate->Jn : pDstDate->Jn - 1;

        } else if(pDstDate->n != -1) {

                /*
                 *  n is in [0, 365] and leap day is counted.
                 */

                yday = pDstDate->n;

        } else { /* Mm.n.d */

                int *ptrday;
                int years;
                int wday;

                /*
                 *  We first need to calculate year-day(yday) and week-day
                 *  (wday) of 1st day of month pDstDate->Mm. We then figure
                 *  out year-day(yday) of Md day of week Mn of month Mm.
                 */

                ptrday = IS_LEAP_YEAR(theyear) ? _lpdays : _days;

                yday = ptrday[pDstDate->Mm-1] + 1; /* ptrday[i] are all by -1 off */

                years = tm_year - 70;

                /*
                 *  Here constant Day1 is the week-day of Jan 1, 1970.
                 *  (years+1)/4 is for correcting the leap years.
                 */

                wday = (yday + 365*years + (years+1)/4 + DAY1) % 7;

                /*
                 *  Calculate yday of Md day of week 1 of month Mm.
                 */

                yday += pDstDate->Md - wday;
                if(pDstDate->Md < wday) {
                        yday += 7;
                }

                /*
                 *  Calculate yday of Md day of week Mn of month Mm.
                 */

                yday += (pDstDate->Mn-1)*7;

                /*
                 *  Adjust if yday goes beyond the end of the month.
                 */

                if(pDstDate->Md == 5 && yday >= ptrday[pDstDate->Mm] + 1) {
                        yday -= 7;
                }

        }

        return yday;
}

/*
 *  _isindst - Tells whether Xenix-type time value falls under DST.
 *
 *  ENTRY   tb - 'time' structure holding broken-down time value.
 *
 *  RETURN  1 if time represented is in DST, else 0.
 */

int __cdecl _isindst (
        struct tm *tb
        )
{
        int st_yday, end_yday;
        int st_sec, end_sec;

        int check_time;

        /*
         * We need start/end year-days of DST in syday/eyday which are converted
         * from one of the format Jn, n, and Mm.n.d. We already have start/end
         * time (in seconds) of DST in _starttime/_endtime.
         */

        st_yday = GetDstStartOrEndYearDay(tb->tm_year, &_startdate);
        end_yday = GetDstStartOrEndYearDay(tb->tm_year, &_enddate);

        st_sec = st_yday * SEC_PER_DAY + _starttime;
        end_sec = end_yday * SEC_PER_DAY + _endtime;

        check_time = tb->tm_yday * SEC_PER_DAY + tb->tm_hour * SEC_PER_HOUR
                + tb->tm_min * 60 + tb->tm_sec;

        if (check_time >= st_sec && check_time < end_sec)
                return 1;

        return 0;
}

/*
 *  _isskiptime - Tells whether the given time is skipped at the
 *      dst change.  For instance, we set our clocks forward one
 *      hour at 2am to 3am...  This function returns true for
 *      the times between 1:59:59 and 3:00:00.
 *
 *  ENTRY   tb - 'time' structure holding broken-down time value.
 *
 *  RETURN  1 if time represented is in the skipped period, 0
 *      otherwise.
 */

int __cdecl _isskiptime (
        struct tm *tb
        )
{
        int st_yday;
        int st_sec;
        int check_time;

        st_yday = GetDstStartOrEndYearDay(tb->tm_year, &_startdate);
        st_sec = st_yday * SEC_PER_DAY + _starttime;

        check_time = tb->tm_yday * SEC_PER_DAY + tb->tm_hour * SEC_PER_HOUR
                + tb->tm_min * 60 + tb->tm_sec;

        if (check_time >= st_sec && check_time < st_sec - _dstoffset) {
                return 1;
        }
        return 0;
}

#endif /* _POSIX_ */
