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