/*** *tzset.c - set timezone information and see if we're in daylight time * * Copyright (c) 1985-1993, 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 * , removed #include , 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. * 06-28-93 GJF Limited support for system's notion of time zone * in Windows NT. * 07-15-93 GJF Resurrected __tzset(). * *******************************************************************************/ #include #include #include #include #include #include #ifdef _POSIX_ #include #else #include #include #endif #include #ifndef _POSIX_ /* * Pointer to a saved copy of the TZ value obtained in the previous call * to tzset() set (if any). */ static char * lastTZ = NULL; #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_ #ifdef MTHREAD static void _CRTAPI3 _tzset_lk(void); #else #define _tzset_lk _tzset #endif void _CRTAPI1 __tzset(void) { static int first_time; if ( !first_time ) { _mlock( _TIME_LOCK ); if ( !first_time ) { _tzset_lk(); first_time++; } _munlock(_TIME_LOCK ); } } #ifdef MTHREAD /* multi-thread; define both tzset and _tzset_lk */ void _CRTAPI1 _tzset ( void ) { _mlock( _TIME_LOCK ); _tzset_lk(); _munlock( _TIME_LOCK ); } static void _CRTAPI3 _tzset_lk ( #else /* non multi-thread; only define tzset */ void _CRTAPI1 _tzset ( #endif /* rejoin common code */ void ) { REG1 char *TZ; REG2 int negdiff = 0; _mlock(_ENV_LOCK); /* * Fetch the value of the TZ environment variable. */ if ( (TZ = _getenv_lk("TZ")) == NULL ) { /* * If there is no TZ environment variable, try to use the * time zone information from the system. */ TIME_ZONE_INFORMATION tzinfo; if ( GetTimeZoneInformation( &tzinfo ) != 0xffffffff ) { /* * Derive _timezone value from Bias and StandardBias fields. */ _timezone = tzinfo.Bias * 60L; if ( tzinfo.StandardDate.wMonth != 0 ) _timezone += (tzinfo.StandardBias * 60L); /* * Check to see if there is a daylight time bias. If so, we * ASSUME it is USA daylight saving time (that is all we * support for rev. one). */ if ( (tzinfo.DaylightDate.wMonth != 0) && (tzinfo.DaylightBias != 0) ) _daylight = 1; else _daylight = 0; /* * Remove the default names, but don't try to determine * suitable replacements (NT does not enforce any standard * or convention for timezone naming). */ *_tzname[0] = '\0'; *_tzname[1] = '\0'; } /* * Time zone information is unavailable, just return. */ _munlock(_ENV_LOCK); return; } if ( (*TZ == '\0') || ((lastTZ != NULL) && (strcmp(TZ, lastTZ) == 0)) ) { /* * Either TZ is NULL, pointing to '\0', or is the unchanged * from a earlier call (to this function). In any case, there * is no work to do, so just return */ _munlock(_ENV_LOCK); return; } /* * Update lastTZ */ if (lastTZ != NULL) { free(lastTZ); } lastTZ = strdup(TZ); _munlock(_ENV_LOCK); /* * Process TZ value and update _tzname, _timezone and _daylight. */ strncpy(_tzname[0], 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) * 3600L; while ( (*TZ == '+') || ((*TZ >= '0') && (*TZ <= '9')) ) TZ++; /* * check if minutes were specified */ if ( *TZ == ':' ) { /* * process, then skip over, the minutes */ _timezone += atol(++TZ) * 60L; 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 ( _daylight = *TZ ) strncpy(_tzname[1], TZ, 3); else *_tzname[1] = '\0'; } /*** *int _isindst(tb) - determine if broken-down time falls in DST * *Purpose: * Determine if the given broken-down time falls within DST. Only * modern, summer-time DST is handled (USA, post 1967). * * This is the rule for years before 1987: * a time is in DST iff it is on or after 02:00:00 on the last Sunday * in April and before 01:00:00 on the last Sunday in October. * This is the rule for years starting with 1987: * a time is in DST iff it is on or after 02:00:00 on the first Sunday * in April and before 01:00: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 _CRTAPI1 _isindst ( REG1 struct tm *tb ) { int mdays; REG2 int yr; int critsun; /* * Handle easy cases. * * Modern DST was put into effect by Congress in 1967. Also, if the * month is before April or after October, it cannot be DST. */ if ( (tb->tm_year < 67) || (tb->tm_mon < 3) || (tb->tm_mon > 9) ) return(0); /* * If the month is after April and before October, it must be DST. */ if ( (tb->tm_mon > 3) && (tb->tm_mon < 9) ) return(1); /* * Now for the hard part. Month is April or October; see if date * falls between appropriate Sundays. */ /* * The objective for years before 1987 (after 1986) is to determine * if the day is on or after 2:00 am on the last (first) Sunday in * April, or before 1:00 am on the last Sunday in October. * * We know the year-day (0..365) of the current time structure. We must * determine the year-day of the last (first) Sunday in this month, * April or October, and then do the comparison. * * To determine the year-day of the last Sunday, we do the following: * 1. Get the year-day of the last day of the current month (Apr * or Oct) * 2. Determine the week-day number of #1, * which is defined as 0 = Sun, 1 = Mon, ... 6 = Sat * 3. Subtract #2 from #1 * * To determine the year-day of the first Sunday, we do the following: * 1. Get the year-day of the 7th day of the current month * (April) * 2. Determine the week-day number of #1, * which is defined as 0 = Sun, 1 = Mon, ... 6 = Sat * 3. Subtract #2 from #1 */ /* * First we get #1. The year-days for each month are stored in _days[] * they're all off by -1 */ if ( ((yr = tb->tm_year) > 86) && (tb->tm_mon == 3) ) mdays = 7 + _days[tb->tm_mon]; else mdays = _days[tb->tm_mon+1]; /* * if this is a leap-year, add an extra day */ if ( !(yr & 3) ) mdays++; /* * mdays now has #1 */ /* Now get #2. We know the week-day number of January 1, 1970, which is * defined as the constant _BASE_DOW. Add to this the number of elapsed * days since then, take the result mod 7, and we have our day number. * * To obtain #3, we just subtract this from mdays. */ critsun = mdays - ((mdays + 365 * (yr - 70) + ((yr - 1) >> 2) - _LEAP_YEAR_ADJUST + _BASE_DOW) % 7); /* Now we know 1 and 3; we're golden: */ return ( (tb->tm_mon == 3) ? ((tb->tm_yday > critsun) || ((tb->tm_yday == critsun) && (tb->tm_hour >= 2))) : ((tb->tm_yday < critsun) || ((tb->tm_yday == critsun) && (tb->tm_hour < 1))) ); } #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; /* * If we are only interested in years between 1901 and 2099, we could use this: * * #define IS_LEAP_YEAR(y) (y % 4 == 0) */ #define IS_LEAP_YEAR(y) ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) /* * 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 * _CRTAPI3 ParsePosixStdOrDst( REG1 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 * _CRTAPI3 ParsePosixOffset( REG1 char *pch, REG2 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 * _CRTAPI3 ParsePosixDate( REG1 char *pch, REG2 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 * _CRTAPI3 ParsePosixTime( REG1 char *pch, REG2 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 _CRTAPI1 tzset( void ) { /* pch points to the beginning of an element to be parsed. */ REG1 char *pch; /* pchCurr points to one position after the end of last element parsed. */ REG2 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 _CRTAPI3 GetDstStartOrEndYearDay( REG1 int tm_year, REG2 PDSTDATE pDstDate ) { REG1 int yday; /* year-day */ REG2 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 _CRTAPI1 _isindst ( REG1 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 _CRTAPI1 _isskiptime ( REG1 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_ */