/* Low Level Interface Library Copyright 1983-2008 Green Hills Software,Inc. * This program is the property of Green Hills Software, Inc, * its contents are proprietary information and no part of it * is to be disclosed to anyone except employees of Green Hills * Software, Inc., or as agreed in writing signed by the President * of Green Hills Software, Inc. * * The routines in this file all deal with the concept of the local timezone. * * On Unix, times are always kept in terms of Greenwich Mean Time and adjusted * according to the local timezone when displayed. * * Entry points are: * localtime() * tzset() * __gh_timezone() */ /* ind_tmzn.c: ANSI time zone facilities, and internal library primitive. */ #include "indos.h" #include "ind_thrd.h" #if defined(CROSSUNIX) || !defined(ANYUNIX) /******************************************************************************/ /* #include */ /* struct tm *localtime_r(const time_t *timer, struct tm *result); */ /* struct tm *localtime(const time_t *timer); */ /* */ /* localtime returns a structure containing the current local time broken */ /* down into tm_year (current year - 1900), tm_mon (current month January=0) */ /* tm_mday (current day of month), tm_hour (current hour 24 hour time), */ /* tm_min (current minute), and tm_sec (current second). */ /* */ /* Uses __gh_timezone to determine the current timezone. */ /* */ /* timer is a pointer to a long containing the number of seconds since the */ /* Epoch (as set by time()). */ /* */ /* Return 0 if no time of day can be returned. */ /******************************************************************************/ extern struct tm *gmtime_r(const time_t *timer, struct tm *result); struct tm *localtime_r(const time_t *timer, struct tm *result) { /* If no other implementation provided, assume Epoch is 00:00:00 January 1,1970 as is true in UNIX. The code here calculates daylight saving time according to the rules for the USA for the years indicated. We ignore the fact that in 1974 it began on Jan 6, and in 1975 it began on Feb 23. We ignore the fact that it was used in 1918, 1919, and 1942..1945 under the name "War Time." In July 2005 a law was passed changing dst to begin on the 2nd Sunday in March and end on the first Sunday in November. This change takes place until changed by another law, which is quite possible. The rules I follow are: before 1966 no DST This is correct before 1918 and for 1920..1941 and 1946..1965. 1966 and after These rules are correct for 1966..1973 and 1976..1986 at 2am of the last sunday of april dst starts at 2am of the last sunday of october dst ends 1987 and after at 2am of the first sunday of april dst starts at 2am of the last sunday of october dst ends 2007 and after at 2am of the second sunday of march dst starts at 2am of the first sunday of november dst ends */ time_t time_v = *timer - 60 * __gh_timezone(); struct tm *temp; int isdst, sunday_mday, month, after, day; static const char mons[]={31,28,31,30,31,30,31,31,30,31,30,31}; if (*timer == (time_t)-1) return(NULL); if ((temp = gmtime_r(&time_v, result))==NULL) return(NULL); if (temp->tm_year < 66) return temp; /* The following code is hand-optimized to reduce code size. Conceptually, 2 boundary days are computed. if the current date is before the start or after the finish, dst is false. If the current date is between them, dst is true. If the current is a boundary date, then we have to compare current time to 2am. Depending on whether the date is the start boundary or end boundary, being before or after 2m means dst is true or false. Specifically the logic does this: If current month is before start month or after end month, return immediately because gmtime_r set tm_isdst=0 and there is no other work for us. If current month is start month assume we are before the boundary and set isdst=0. Use 'after' variable to remember how to set isdst later if after the boundary. If current month is end month assume we are before the boundary. Already have set isdst=1. Use 'after' variable to remember how to set isdst later if after the boundary. If current month is either boundary month, day is to the boundary day. This is tricky. See explanation below. If current month is between the boundaries, isdst=after=1. Fall into the code for boundary months, but no matter what, isdst will be true. The variable sunday_mday is calculated to be the day of the month for the sunday which begins the current week. This is useful for figuring out whether current day is the nth sunday of the month, often without asking if the current day is even a sunday at all. The variable day is initialized to be the highest day of the month which could be a boundary sunday. If the boundary is the last sunday, day=. if the boundary is the nth sunday, day=7*n. */ #if !defined(NO_2007_DST_RULES) { static const struct s_chart { char year; /* actually years after 1900 */ char start_month; /* 0..11 */ char start_day; /* 1..31. This is the largest value possible for the sunday in question. For 3rd sunday use 21, for last sunday of april, use 30. */ char end_month; /* 0..11 */ char end_day; /* 1..31. This is the largest value possible for the sunday in question. For 1st sunday use 7, for last sunday of october, use 31. */ } chart[] = { { 107, 2, 14, 10, 7 }, { 87, 3, 7, 9, 31 }, { 0, 3, 30, 9, 31 }, }; const struct s_chart *p; for (p=chart; p->year > temp->tm_year; p++) ; isdst = 1; /* isdst=1 if before boundary date */ after = 1; /* after=1 if after boundary date */ day = p->start_day; month = temp->tm_mon; if (month == p->start_month) { isdst = 0; } else if (month == p->end_month) { after = 0; day = p->end_day; } else if (month < p->start_month || month > p->end_month) { return temp; /* isdst=0 */ } /* else isdst = 1 */ } #else isdst = 1; /* isdst=1 if before boundary date */ after = 1; /* isdst=1 if after boundary date */ day = (temp->tm_year < 87) ? 30: 7; month = temp->tm_mon; if (month == 3) { isdst = 0; } else if (month == 10) { after = 0; day = 31; } else if (month < 3 || month > 10) { return temp; /* isdst=0 */ } /* else isdst = 1 */ #endif /* find mday for sunday of current week */ sunday_mday = (temp->tm_mday-temp->tm_wday); if (sunday_mday <= day-7) /* before boundary sunday */ ; else if (sunday_mday > day) /* after boundary sunday */ isdst=after; else if (temp->tm_wday) /* sunday_mday is the boundary, but current day is not a sunday, therefore it is after boundary */ isdst=after; else if (temp->tm_hour>=2) /* after 2am on a sunday */ isdst=after; temp->tm_isdst = isdst; if ( isdst ) { if ( ++(temp->tm_hour)==24 ) { temp->tm_hour=0; ++(temp->tm_yday); /* can't overflow dst not on Dec. 31 */ if ( ++(temp->tm_wday)==7 ) temp->tm_wday=0; if ( ++(temp->tm_mday)>mons[month] ) { temp->tm_mday=1; ++(temp->tm_mon); /* can't overflow dst not on Dec. 31 */ } } } return( temp ); } struct tm *localtime(const time_t *timer) { struct tm *temp = __ghs_GetThreadLocalStorageItem(__ghs_TLS_gmtime_temp); if (!temp) temp = &__ghs_static_gmtime_temp; return(localtime_r(timer, temp)); } #endif /* CROSSUNIX or !ANYUNIX */ /* Added the following code for better System V compatibility */ /* Actually, most systems provide tzset() in libc.a and that one should */ /* be used, but I wanted to add the global variables to */ /* and it seemed reasonable that if the Green Hills Software */ /* had the System V style timezone variables, we should also provide */ /* the appropriate library routine which sets them. There are 2 */ /* problems with this implementation. The global variable 'timezone' */ /* conflicts with the BSD type 'struct timezone', therefore it is not */ /* convenient to use gettimeofday() on BSD to set timezone. Further, */ /* getenv() is needed here, and that is defined in libansi.a., which */ /* always preceeds libind.a. For now getenv() is coded inline. */ #if (defined(ANYSYSV) && defined(CROSSUNIX)) || \ defined(SIMULATE) || defined(EMBEDDED) || defined(MSW) #define NEEDTZSET #endif #if defined(ANYSYSV) long timezone, altzone; int daylight; #elif defined(NEEDTZSET) /* Disable warning for defining an internal timezone variable */ #pragma ghs nowarning 172 static long timezone; #pragma ghs endnowarning 172 #endif #if defined(NEEDTZSET) /******************************************************************************/ /* void tzset(void); */ /* System V compatible method of setting the current timezone and daylight */ /* savings status. Requires that the external variable, environ, be set to */ /* a list of environment strings, including one of the form TZ=PST8PDT */ /* Explanation needed for the timezone, altzone, daylight and tzname variables*/ /******************************************************************************/ #define IsAlpha(c) ((c>='A'&&c<='Z')||(c>='a'&&c<='z')) #define IsDigit(c) (c>='0'&&c<='9') #ifdef __ghs_pid char *tzname[2]; #else static char tzname_default[] = "GMT\0 "; char *tzname[2] = { tzname_default, tzname_default+4 } ; #endif void tzset(void) { #if defined(ANYUNIX)||defined(UNIXSYSCALLS)||defined(MSW) static char tzname_default[] = "GMT\0 "; /* Ugly. An inline copy of getenv() to avoid conflicts with C libraries. */ /* should be tz = getenv("TZ"); */ char *tz = NULL; extern char **environ; char **envp = environ, *e; if (envp) while (e = *envp++) if (e[0] == 'T' && e[1] == 'Z' && e[2] == '=') { tz = e + 3; break; } /* Outside of Unix, don't know how to get the environment, so TZ will be NULL */ if (tz != NULL) { int hour = 0, minute = 0, second = 0,sign = 1; char *zone; int ahour = 0, aminute = 0, asecond = 0,asign = 1; char *azone = NULL; /* 3 letter timezone required */ if (!IsAlpha(tz[0]) || !IsAlpha(tz[1]) || !IsAlpha(tz[2])) return; zone = tz; tz += 3; /* the rest is optional */ if ( *tz ) { /* signed hour:min:sec difference from GMT */ if ( *tz == '+' || *tz == '-' ) { if (*tz == '-') sign = -1; tz++; } if (!IsDigit(tz[0])) return; hour = *tz++ - '0'; if (IsDigit(tz[0])) hour = hour * 10 + *tz++ - '0'; hour *= sign; if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) { minute = ((tz[1] - '0') * 10 + tz[2] - '0') * sign; tz += 3; if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) { second = ((tz[1] - '0') * 10 + tz[2] - '0') * sign; tz += 3; } } /* the rest is optional */ if ( *tz && IsAlpha(tz[0]) && IsAlpha(tz[1]) && IsAlpha(tz[2])) { /* 3 letter daylight timezone */ azone = tz; tz += 3; /* signed hour:min:sec difference from GMT (may be skipped) */ if ( *tz == '+' || *tz == '-' ) { if (*tz == '-') asign = -1; tz++; } if (IsDigit(tz[0])) { ahour = *tz++ - '0'; if (IsDigit(tz[0])) ahour = ahour * 10 + *tz++ - '0'; ahour *= asign; if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) { aminute = ((tz[1] - '0') * 10 + tz[2] - '0') * asign; tz += 3; if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) { asecond = ((tz[1] - '0') * 10 + tz[2] - '0')*asign; tz += 3; } } } else if (*tz == '\0') ahour = hour - 1; } } /* the rest of TZ is ignored by this implementation */ timezone = hour * 60 * 60 + minute * 60 + second; #if defined(ANYSYSV) daylight = 0; #endif #ifdef __ghs_pid tzname[0] = tzname_default; tzname[1] = tzname_default+4; #endif tzname[0][0] = zone[0]; tzname[0][1] = zone[1]; tzname[0][2] = zone[2]; tzname[0][3] = '\0'; if (azone) { tzname[1][0] = azone[0]; tzname[1][1] = azone[1]; tzname[1][2] = azone[2]; tzname[1][3] = '\0'; #if defined(ANYSYSV) altzone = ahour * 60 * 60 + aminute * 60 + asecond; daylight = 1; #endif } return; } #endif } #endif /* NEEDTZSET */ /******************************************************************************/ /* int __gh_timezone(void); */ /* Return the number of minutes west of Greenwich Mean Time of the current */ /* time zone. If the time() functions return the local time rather than */ /* Greenwich Mean Time then return 0 from __gh_timezone(). */ /* See also tzset() and localtime() */ /******************************************************************************/ int __gh_timezone(void) { #if defined(ANYSYSV) || defined(NEEDTZSET) tzset(); return(timezone/60); #elif defined(ANYBSD) struct timeval ignore; struct timezone tz; gettimeofday(&ignore,&tz); return(tz.tz_minuteswest); #elif defined(ANYUNIX)|| defined(UNIXSYSCALLS)|| defined(SIMULATE) # if defined(TIMEZONE) return(TIMEZONE*60); # else return(8*60); # endif /* TIMEZONE */ #else return 0; #endif }