1 /*
2 		    Low Level Interface Library
3 
4             Copyright 1983-2008 Green Hills Software,Inc.
5 
6  *  This program is the property of Green Hills Software, Inc,
7  *  its contents are proprietary information and no part of it
8  *  is to be disclosed to anyone except employees of Green Hills
9  *  Software, Inc., or as agreed in writing signed by the President
10  *  of Green Hills Software, Inc.
11  *
12  * The routines in this file all deal with the concept of the local timezone.
13  *
14  * On Unix, times are always kept in terms of Greenwich Mean Time and adjusted
15  * according to the local timezone when displayed.
16  *
17  * Entry points are:
18  *	localtime()
19  *	tzset()
20  *	__gh_timezone()
21 */
22 /* ind_tmzn.c: ANSI time zone facilities, and internal library primitive. */
23 
24 #include "indos.h"
25 #include "ind_thrd.h"
26 
27 #if defined(CROSSUNIX) || !defined(ANYUNIX)
28 
29 /******************************************************************************/
30 /*  #include <time.h>							      */
31 /*  struct tm *localtime_r(const time_t *timer, struct tm *result);	      */
32 /*  struct tm *localtime(const time_t *timer);				      */
33 /*									      */
34 /*  localtime returns a structure containing the current local time broken    */
35 /*  down into tm_year (current year - 1900), tm_mon (current month January=0) */
36 /*  tm_mday (current day of month), tm_hour (current hour 24 hour time),      */
37 /*  tm_min (current minute), and tm_sec (current second).                     */
38 /*									      */
39 /*  Uses __gh_timezone to determine the current timezone.		      */
40 /*									      */
41 /*  timer is a pointer to a long containing the number of seconds since the */
42 /*  Epoch (as set by time()).						      */
43 /*									      */
44 /*  Return 0 if no time of day can be returned.				      */
45 /******************************************************************************/
46 
47 extern struct tm *gmtime_r(const time_t *timer, struct tm *result);
48 
localtime_r(const time_t * timer,struct tm * result)49 struct tm *localtime_r(const time_t *timer, struct tm *result)
50 {
51 /*
52     If no other implementation provided, assume Epoch is 00:00:00 January 1,1970
53     as is true in UNIX.
54 
55     The code here calculates daylight saving time according to the rules
56     for the USA for the years indicated.  We ignore the fact that in 1974
57     it began on Jan 6, and in 1975 it began on Feb 23.  We ignore the fact
58     that it was used in 1918, 1919, and 1942..1945 under the name "War Time."
59     In July 2005 a law was passed changing dst to begin on the 2nd Sunday
60     in March and end on the first Sunday in November.  This change takes
61     place until changed by another law, which is quite possible.
62 
63     The rules I follow are:
64 	before 1966
65 	    no DST
66 	    This is correct before 1918 and for 1920..1941 and 1946..1965.
67 	1966 and after
68 	    These rules are correct for 1966..1973 and 1976..1986
69 	    at 2am of the last sunday of april dst starts
70 	    at 2am of the last sunday of october dst ends
71 	1987 and after
72 	    at 2am of the first sunday of april dst starts
73 	    at 2am of the last sunday of october dst ends
74 	2007 and after
75 	    at 2am of the second sunday of march dst starts
76 	    at 2am of the first sunday of november dst ends
77 */
78     time_t time_v = *timer - 60 * __gh_timezone();
79     struct tm *temp;
80     int isdst, sunday_mday, month, after, day;
81     static const char mons[]={31,28,31,30,31,30,31,31,30,31,30,31};
82     if (*timer == (time_t)-1)
83 	return(NULL);
84     if ((temp = gmtime_r(&time_v, result))==NULL)
85 	return(NULL);
86     if (temp->tm_year < 66)
87 	return temp;
88 /* The following code is hand-optimized to reduce code size.
89    Conceptually, 2 boundary days are computed.  if the
90    current date is before the start or after the finish, dst is false.
91    If the current date is between them, dst is true.  If the current is
92    a boundary date, then we have to compare current time to 2am.
93    Depending on whether the date is the start boundary or end boundary,
94    being before or after 2m means dst is true or false.
95 
96     Specifically the logic does this:
97 	If current month is before start month or after end month,
98 	return immediately because gmtime_r set tm_isdst=0 and
99 	there is no other work for us.
100     If current month is start month assume we are before the
101 	boundary and set isdst=0.  Use 'after' variable to
102 	remember how to set isdst later if after the boundary.
103     If current month is end month assume we are before the
104 	boundary. Already have set isdst=1.  Use 'after' variable
105 	to remember how to set isdst later if after the boundary.
106     If current month is either boundary month, day is to the
107 	boundary day.  This is tricky.  See explanation below.
108     If current month is between the boundaries, isdst=after=1.
109 	Fall into the code for boundary months, but no matter
110 	what, isdst will be true.
111 
112     The variable sunday_mday is calculated to be the day of the
113 	month for the sunday which begins the current week.
114 	This is useful for figuring out whether current day is
115 	the nth sunday of the month, often without asking if the
116 	current day is even a sunday at all.
117     The variable day is initialized to be the highest day of the
118 	month which could be a boundary sunday.  If the boundary
119 	is the last sunday, day=<number of days in the month>.
120 	if the boundary is the nth sunday, day=7*n.
121 
122 
123    */
124 #if !defined(NO_2007_DST_RULES)
125     {
126     static const struct s_chart {
127 	char year;	/* actually years after 1900 */
128 	char start_month;	/* 0..11 */
129 	char start_day;	/* 1..31.  This is the largest value possible
130 			   for the sunday in question.  For 3rd sunday
131 			   use 21, for last sunday of april, use 30. */
132 	char end_month;	/* 0..11 */
133 	char end_day;	/* 1..31.  This is the largest value possible
134 			   for the sunday in question.  For 1st sunday
135 			   use 7, for last sunday of october, use 31. */
136     } chart[] = {
137 	{ 107,  2, 14, 10,  7 },
138 	{  87,  3,  7,  9, 31 },
139 	{   0,  3, 30,  9, 31 },
140     };
141     const struct s_chart *p;
142 
143     for (p=chart; p->year > temp->tm_year; p++)
144 	;
145     isdst = 1;		/* isdst=1 if before boundary date */
146     after = 1;		/* after=1 if after boundary date */
147     day = p->start_day;
148     month = temp->tm_mon;
149     if (month == p->start_month) {
150 	isdst = 0;
151     } else if (month == p->end_month) {
152 	after = 0;
153 	day = p->end_day;
154     } else if (month < p->start_month || month > p->end_month) {
155 	return temp;	/* isdst=0 */
156     } /* else		isdst = 1 */
157     }
158 #else
159     isdst = 1;		/* isdst=1 if before boundary date */
160     after = 1;		/* isdst=1 if after boundary date */
161     day = (temp->tm_year < 87) ? 30: 7;
162     month = temp->tm_mon;
163     if (month == 3) {
164 	isdst = 0;
165     } else if (month == 10) {
166 	after = 0;
167 	day = 31;
168     } else if (month < 3 || month > 10) {
169 	return temp;	/* isdst=0 */
170     } /* else		isdst = 1 */
171 #endif
172 
173     /* find mday for sunday of current week */
174     sunday_mday = (temp->tm_mday-temp->tm_wday);
175     if (sunday_mday <= day-7)	/* before boundary sunday */
176 	;
177     else if (sunday_mday > day)	/* after boundary sunday */
178 	isdst=after;
179     else if (temp->tm_wday)	/* sunday_mday is the boundary,
180 				   but current day is not a sunday,
181 				   therefore it is after boundary */
182 	isdst=after;
183     else if (temp->tm_hour>=2)	/* after 2am on a sunday */
184 	isdst=after;
185 
186     temp->tm_isdst = isdst;
187     if ( isdst ) {
188 	if ( ++(temp->tm_hour)==24 ) {
189 	    temp->tm_hour=0;
190 	    ++(temp->tm_yday);          /* can't overflow dst not on Dec. 31 */
191 	    if ( ++(temp->tm_wday)==7 ) temp->tm_wday=0;
192 	    if ( ++(temp->tm_mday)>mons[month] ) {
193 		temp->tm_mday=1;
194 		++(temp->tm_mon);       /* can't overflow dst not on Dec. 31 */
195 	    }
196 	}
197     }
198     return( temp );
199 }
200 
localtime(const time_t * timer)201 struct tm *localtime(const time_t *timer) {
202     struct tm *temp = __ghs_GetThreadLocalStorageItem(__ghs_TLS_gmtime_temp);
203     if (!temp)
204 	temp = &__ghs_static_gmtime_temp;
205     return(localtime_r(timer, temp));
206 }
207 #endif	/* CROSSUNIX or !ANYUNIX */
208 
209 /* Added the following code for better System V compatibility		*/
210 /* Actually, most systems provide tzset() in libc.a and that one should	*/
211 /* be used, but I wanted to add the global variables to <time.h>	*/
212 /* and it seemed reasonable that if the Green Hills Software <time.h>	*/
213 /* had the System V style timezone variables, we should also provide	*/
214 /* the appropriate library routine which sets them.  There are 2 	*/
215 /* problems with this implementation.  The global variable 'timezone'	*/
216 /* conflicts with the BSD type 'struct timezone', therefore it is not	*/
217 /* convenient to use gettimeofday() on BSD to set timezone.  Further,	*/
218 /* getenv() is needed here, and that is defined in libansi.a., which	*/
219 /* always preceeds libind.a.  For now getenv() is coded inline. 	*/
220 #if (defined(ANYSYSV) && defined(CROSSUNIX)) || \
221     defined(SIMULATE) || defined(EMBEDDED) || defined(MSW)
222 #define NEEDTZSET
223 #endif
224 #if defined(ANYSYSV)
225 long timezone, altzone;
226 int daylight;
227 #elif defined(NEEDTZSET)
228 /* Disable warning for defining an internal timezone variable */
229 #pragma ghs nowarning 172
230 static long timezone;
231 #pragma ghs endnowarning 172
232 #endif
233 #if defined(NEEDTZSET)
234 /******************************************************************************/
235 /* void tzset(void);							      */
236 /* System V compatible method of setting the current timezone and daylight    */
237 /* savings status.  Requires that the external variable, environ, be set to   */
238 /* a list of environment strings, including one of the form TZ=PST8PDT	      */
239 /* Explanation needed for the timezone, altzone, daylight and tzname variables*/
240 /******************************************************************************/
241 #define IsAlpha(c)	((c>='A'&&c<='Z')||(c>='a'&&c<='z'))
242 #define IsDigit(c)	(c>='0'&&c<='9')
243 
244 #ifdef __ghs_pid
245 char *tzname[2];
246 #else
247 static char tzname_default[] = "GMT\0   ";
248 char *tzname[2] = { tzname_default, tzname_default+4 } ;
249 #endif
tzset(void)250 void tzset(void) {
251 #if defined(ANYUNIX)||defined(UNIXSYSCALLS)||defined(MSW)
252     static char tzname_default[] = "GMT\0   ";
253 /* Ugly.  An inline copy of getenv() to avoid conflicts with C libraries. */
254 /* should be tz = getenv("TZ");						  */
255     char *tz = NULL;
256     extern char **environ;
257     char **envp = environ, *e;
258     if (envp)
259 	while (e = *envp++)
260 	    if (e[0] == 'T' && e[1] == 'Z' && e[2] == '=') {
261 		tz = e + 3;
262 		break;
263 	    }
264 /* Outside of Unix, don't know how to get the environment, so TZ will be NULL */
265     if (tz != NULL) {
266 	int hour = 0, minute = 0, second = 0,sign = 1;
267 	char *zone;
268 	int ahour = 0, aminute = 0, asecond = 0,asign = 1;
269 	char *azone = NULL;
270 
271 	/* 3 letter timezone required */
272 	if (!IsAlpha(tz[0]) || !IsAlpha(tz[1]) || !IsAlpha(tz[2]))
273 	    return;
274 	zone = tz; tz += 3;
275 
276 	/* the rest is optional */
277 	if ( *tz ) {
278 	    /* signed hour:min:sec difference from GMT */
279 	    if ( *tz == '+' || *tz == '-' ) {
280 		if (*tz == '-') sign = -1;
281 		tz++;
282 	    }
283 	    if (!IsDigit(tz[0]))
284 		return;
285 	    hour = *tz++ - '0';
286 	    if (IsDigit(tz[0]))
287 		hour = hour * 10 + *tz++ - '0';
288 	    hour *= sign;
289 	    if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) {
290 		minute = ((tz[1] - '0') * 10 + tz[2] - '0') * sign;
291 		tz += 3;
292 		if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) {
293 		    second = ((tz[1] - '0') * 10 + tz[2] - '0') * sign;
294 		    tz += 3;
295 		}
296 	    }
297 	    /* the rest is optional */
298 	    if ( *tz && IsAlpha(tz[0]) && IsAlpha(tz[1]) && IsAlpha(tz[2])) {
299 
300 		/* 3 letter daylight timezone */
301 		azone = tz; tz += 3;
302 
303 		/* signed hour:min:sec difference from GMT (may be skipped) */
304 		if ( *tz == '+' || *tz == '-' ) {
305 		    if (*tz == '-') asign = -1;
306 		    tz++;
307 		}
308 		if (IsDigit(tz[0])) {
309 		    ahour = *tz++ - '0';
310 		    if (IsDigit(tz[0]))
311 			ahour = ahour * 10 + *tz++ - '0';
312 		    ahour *= asign;
313 		    if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) {
314 			aminute = ((tz[1] - '0') * 10 + tz[2] - '0') * asign;
315 			tz += 3;
316 			if (tz[0] == ':' && IsDigit(tz[1]) && IsDigit(tz[2])) {
317 			    asecond = ((tz[1] - '0') * 10 + tz[2] - '0')*asign;
318 			    tz += 3;
319 			}
320 		    }
321 		} else if (*tz == '\0')
322 		    ahour = hour - 1;
323 	    }
324 	}
325 	/* the rest of TZ is ignored by this implementation */
326 
327 	timezone = hour * 60 * 60 + minute * 60 + second;
328 #if defined(ANYSYSV)
329 	daylight = 0;
330 #endif
331 #ifdef __ghs_pid
332 	tzname[0] = tzname_default;
333 	tzname[1] = tzname_default+4;
334 #endif
335 	tzname[0][0] = zone[0];
336 	tzname[0][1] = zone[1];
337 	tzname[0][2] = zone[2];
338 	tzname[0][3] = '\0';
339 	if (azone) {
340 	    tzname[1][0] = azone[0];
341 	    tzname[1][1] = azone[1];
342 	    tzname[1][2] = azone[2];
343 	    tzname[1][3] = '\0';
344 #if defined(ANYSYSV)
345 	    altzone = ahour * 60 * 60 + aminute * 60 + asecond;
346 	    daylight = 1;
347 #endif
348 	}
349 	return;
350     }
351 #endif
352 }
353 #endif /* NEEDTZSET */
354 
355 /******************************************************************************/
356 /* int __gh_timezone(void);						      */
357 /*  Return the number of minutes west of Greenwich Mean Time of the current   */
358 /*  time zone.  If the time() functions return the local time rather than     */
359 /*  Greenwich Mean Time then return 0 from __gh_timezone().		      */
360 /*  See also tzset() and localtime()					      */
361 /******************************************************************************/
__gh_timezone(void)362 int __gh_timezone(void) {
363 #if defined(ANYSYSV) || defined(NEEDTZSET)
364     tzset();
365     return(timezone/60);
366 #elif defined(ANYBSD)
367     struct timeval ignore;
368     struct timezone tz;
369     gettimeofday(&ignore,&tz);
370     return(tz.tz_minuteswest);
371 #elif defined(ANYUNIX)|| defined(UNIXSYSCALLS)|| defined(SIMULATE)
372 #  if defined(TIMEZONE)
373     return(TIMEZONE*60);
374 #  else
375     return(8*60);
376 #  endif	/* TIMEZONE */
377 #else
378     return 0;
379 #endif
380 }
381