Pristine Ack-5.5
[Ack-5.5.git] / lang / cem / libcc.ansi / time / misc.c
1 /*
2  * misc - data and miscellaneous routines
3  */
4 /* $Id: misc.c,v 1.13 1994/06/24 11:58:17 ceriel Exp $ */
5
6 #include        <ctype.h>
7 #include        <time.h>
8 #include        <stdlib.h>
9 #include        <string.h>
10
11 #if     defined(__BSD4_2)
12
13 struct timeval {
14         long    tv_sec;         /* seconds */
15         long    tv_usec;        /* and microseconds */
16 };
17
18 struct timezone {
19         int     tz_minuteswest; /* minutes west of Greenwich */
20         int     tz_dsttime;     /* type of dst correction */
21 };
22
23 int _gettimeofday(struct timeval *tp, struct timezone *tzp);
24
25 #elif   !defined(_POSIX_SOURCE) && !defined(__USG)
26 #if     !defined(_MINIX)                /* MINIX has no ftime() */
27 struct timeb {
28         long    time;
29         unsigned short millitm;
30         short   timezone;
31         short   dstflag;
32 };
33 void _ftime(struct timeb *bp);
34 #endif
35 #endif
36
37 #include        "loc_time.h"
38
39 #define RULE_LEN        120
40 #define TZ_LEN          10
41
42 /* Make sure that the strings do not end up in ROM.
43  * These strings probably contain the wrong value, and we cannot obtain the
44  * right value from the system. TZ is the only help.
45  */
46 static char ntstr[TZ_LEN + 1] = "GMT";  /* string for normal time */
47 static char dststr[TZ_LEN + 1] = "GDT"; /* string for daylight saving */
48
49 long    _timezone = 0;
50 long    _dst_off = 60 * 60;
51 int     _daylight = 0;
52 char    *_tzname[2] = {ntstr, dststr};
53
54 #if     defined(__USG) || defined(_POSIX_SOURCE)
55 char    *tzname[2] = {ntstr, dststr};
56
57 #if     defined(__USG)
58 long    timezone = 0;
59 int     daylight = 0;
60 #endif
61 #endif
62
63 static struct dsttype {
64         char ds_type;           /* Unknown, Julian, Zero-based or M */
65         int ds_date[3];         /* months, weeks, days */
66         long ds_sec;            /* usually 02:00:00 */
67 }       dststart = { 'U', { 0, 0, 0 }, 2 * 60 * 60 }
68         , dstend = { 'U', { 0, 0, 0 }, 2 * 60 * 60 };
69
70 const char *_days[] = {
71                         "Sunday", "Monday", "Tuesday", "Wednesday",
72                         "Thursday", "Friday", "Saturday"
73                 };
74
75 const char *_months[] = {
76                         "January", "February", "March",
77                         "April", "May", "June",
78                         "July", "August", "September",
79                         "October", "November", "December"
80                 };
81
82 const int _ytab[2][12] = {
83                 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
84                 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
85         };
86
87 #if     !defined(_POSIX_SOURCE) && !defined(__USG)
88 #define USE_TABLE       1
89 #endif
90
91 #if     USE_TABLE
92 static int usetable = 1;
93
94 typedef struct table {
95         const char *tz_name;
96         const int daylight;
97         const long zoneoffset;
98 } TABLE;
99
100 #define HOUR(x) ((x) * 60*60)
101
102 static TABLE TimezoneTable[] = {
103         {"GMT", 0,      HOUR(0) },      /* Greenwich Mean */
104         {"BST", 60*60,  HOUR(0) },      /* British Summer */
105         {"WAT", 0,      HOUR(1) },      /* West Africa */
106         {"AT", 0,       HOUR(2) },      /* Azores */
107         {"BST", 0,      HOUR(3) },      /* Brazil Standard */
108         {"NFT", 0,      HOUR(3.5) },    /* Newfoundland */
109         {"NDT", 60*60,  HOUR(3.5) },    /* Newfoundland Daylight */
110         {"AST", 0,      HOUR(4) },      /* Atlantic Standard */
111         {"ADT", 60*60,  HOUR(4) },      /* Atlantic Daylight */
112         {"EST", 0,      HOUR(5) },      /* Eastern Standard */
113         {"EDT", 60*60,  HOUR(5) },      /* Eastern Daylight */
114         {"CST", 0,      HOUR(6) },      /* Central Standard */
115         {"CDT", 60*60,  HOUR(6) },      /* Central Daylight */
116         {"MST", 0,      HOUR(7) },      /* Mountain Standard */
117         {"MDT", 60*60,  HOUR(7) },      /* Mountain Daylight */
118         {"PST", 0,      HOUR(8) },      /* Pacific Standard */
119         {"PDT", 60*60,  HOUR(8) },      /* Pacific Daylight */
120         {"YST", 0,      HOUR(9) },      /* Yukon Standard */
121         {"YDT", 60*60,  HOUR(9) },      /* Yukon Daylight */
122         {"HST", 0,      HOUR(10) },     /* Hawaii Standard */
123         {"HDT", 60*60,  HOUR(10) },     /* Hawaii Daylight */
124         {"NT", 0,       HOUR(11) },     /* Nome */
125         {"IDLW", 0,     HOUR(12) },     /* International Date Line West */
126         {"MET", 0,      -HOUR(1) },     /* Middle European */
127         {"MDT", 60*60,  -HOUR(1) },     /* Middle European Summer */
128         {"EET", 0,      -HOUR(2) },     /* Eastern Europe, USSR Zone 1 */
129         {"BT", 0,       -HOUR(3) },     /* Baghdad, USSR Zone 2 */
130         {"IT", 0,       -HOUR(3.5) },   /* Iran */
131         {"ZP4", 0,      -HOUR(4) },     /* USSR Zone 3 */
132         {"ZP5", 0,      -HOUR(5) },     /* USSR Zone 4 */
133         {"IST", 0,      -HOUR(5.5) },   /* Indian Standard */
134         {"ZP6", 0,      -HOUR(6) },     /* USSR Zone 5 */
135         {"NST", 0,      -HOUR(6.5) },   /* North Sumatra */
136         {"SST", 0,      -HOUR(7) },     /* South Sumatra, USSR Zone 6 */
137         {"WAST", 0,     -HOUR(7) },     /* West Australian Standard */
138         {"WADT", 60*60, -HOUR(7) },     /* West Australian Daylight */
139         {"JT", 0,       -HOUR(7.5) },   /* Java (3pm in Cronusland!) */
140         {"CCT", 0,      -HOUR(8) },     /* China Coast, USSR Zone 7 */
141         {"JST", 0,      -HOUR(9) },     /* Japan Standard, USSR Zone 8 */
142         {"CAST", 0,     -HOUR(9.5) },   /* Central Australian Standard */
143         {"CADT", 60*60, -HOUR(9.5) },   /* Central Australian Daylight */
144         {"EAST", 0,     -HOUR(10) },    /* Eastern Australian Standard */
145         {"EADT", 60*60, -HOUR(10) },    /* Eastern Australian Daylight */
146         {"NZT", 0,      -HOUR(12) },    /* New Zealand */
147         {"NZDT", 60*60, -HOUR(12) },    /* New Zealand Daylight */
148         {  NULL, 0, 0  }
149 };
150
151 /*
152  * The function ZoneFromTable() searches the table for the current
153  * timezone.  It saves the last one found in ntstr or dststr, depending on
154  * wheter the name is for daylight-saving-time or not.
155  * Both ntstr and dststr are TZ_LEN + 1 chars.
156  */
157 static void
158 ZoneFromTable(long timezone)
159 {
160         register TABLE *tptr = TimezoneTable;
161
162         while (tptr->tz_name != NULL) {
163                 if (tptr->zoneoffset == timezone) {
164                         if (tptr->daylight == 0) {
165                                 strncpy(ntstr,tptr->tz_name, TZ_LEN);
166                                 ntstr[TZ_LEN] = '\0';
167                         } else {
168                                 strncpy(dststr,tptr->tz_name, TZ_LEN);
169                                 dststr[TZ_LEN] = '\0';
170                         }
171                 }
172                 tptr++;
173         }
174 }
175 #endif  /* USE_TABLE */
176
177 static const char *
178 parseZoneName(register char *buf, register const char *p)
179 {
180         register int n = 0;
181
182         if (*p == ':') return NULL;
183         while (*p && !isdigit(*p) && *p != ',' && *p != '-' && *p != '+') {
184                 if (n < TZ_LEN)
185                         *buf++ = *p;
186                 p++;
187                 n++;
188         }
189         if (n < 3) return NULL;                         /* error */
190         *buf = '\0';
191         return p;
192 }
193
194 static const char *
195 parseTime(register long *tm, const char *p, register struct dsttype *dst)
196 {
197         register int n = 0;
198         register const char *q = p;
199         char ds_type = (dst ? dst->ds_type : '\0');
200
201         if (dst) dst->ds_type = 'U';
202
203         *tm = 0;
204         while(*p >= '0' && *p <= '9') {
205                 n = 10 * n + (*p++ - '0');
206         }
207         if (q == p) return NULL;        /* "The hour shall be required" */
208         if (n < 0 || n >= 24)   return NULL;
209         *tm = n * 60 * 60;
210         if (*p == ':') {
211                 p++;
212                 n = 0;
213                 while(*p >= '0' && *p <= '9') {
214                         n = 10 * n + (*p++ - '0');
215                 }
216                 if (q == p) return NULL;        /* format error */
217                 if (n < 0 || n >= 60)   return NULL;
218                 *tm += n * 60;
219                 if (*p == ':') {
220                         p++;
221                         n = 0;
222                         while(*p >= '0' && *p <= '9') {
223                                 n = 10 * n + (*p++ - '0');
224                         }
225                         if (q == p) return NULL;        /* format error */
226                         if (n < 0 || n >= 60)   return NULL;
227                         *tm += n;
228                 }
229         }
230         if (dst) {
231                 dst->ds_type = ds_type;
232                 dst->ds_sec = *tm;
233         }
234         return p;
235 }
236
237 static const char *
238 parseDate(register char *buf, register const char *p, struct dsttype *dstinfo)
239 {
240         register const char *q;
241         register int n = 0;
242         int cnt = 0;
243         const int bnds[3][2] =  {       { 1, 12 },
244                                         { 1, 5 },
245                                         { 0, 6}
246                                  };
247         char ds_type;
248
249         if (*p != 'M') {
250                 if (*p == 'J') {
251                         *buf++ = *p++;
252                         ds_type = 'J';
253                 }
254                 else    ds_type = 'Z';
255                 q = p;
256                 while(*p >= '0' && *p <= '9') {
257                         n = 10 * n + (*p - '0');
258                         *buf++ = *p++;
259                 }
260                 if (q == p) return NULL;        /* format error */
261                 if (n < (ds_type == 'J') || n > 365) return NULL;
262                 dstinfo->ds_type = ds_type;
263                 dstinfo->ds_date[0] = n;
264                 return p;
265         }
266         ds_type = 'M';
267         do {
268                 *buf++ = *p++;
269                 q = p;
270                 n = 0;
271                 while(*p >= '0' && *p <= '9') {
272                         n = 10 * n + (*p - '0');
273                         *buf++ = *p++;
274                 }
275                 if (q == p) return NULL;        /* format error */
276                 if (n < bnds[cnt][0] || n > bnds[cnt][1]) return NULL;
277                 dstinfo->ds_date[cnt] = n;
278                 cnt++;
279         } while (cnt < 3 && *p == '.');
280         if (cnt != 3) return NULL;
281         *buf = '\0';
282         dstinfo->ds_type = ds_type;
283         return p;
284 }
285
286 static const char *
287 parseRule(register char *buf, register const char *p)
288 {
289         long tim;
290         register const char *q;
291
292         if (!(p = parseDate(buf, p, &dststart))) return NULL;
293         buf += strlen(buf);
294         if (*p == '/') {
295                 q = ++p;
296                 if (!(p = parseTime(&tim, p, &dststart))) return NULL;
297                 while( p != q) *buf++ = *q++;
298         }
299         if (*p != ',') return NULL;
300         p++;
301         if (!(p = parseDate(buf, p, &dstend))) return NULL;
302         buf += strlen(buf);
303         if (*p == '/') {
304                 q = ++p;
305                 if (!(p = parseTime(&tim, p, &dstend))) return NULL;
306                 while(*buf++ = *q++);
307         }
308         if (*p) return NULL;
309         return p;
310 }
311
312 /* The following routine parses timezone information in POSIX-format. For
313  * the requirements, see IEEE Std 1003.1-1988 section 8.1.1.
314  * The function returns as soon as it spots an error.
315  */
316 static void
317 parseTZ(const char *p)
318 {
319         long tz, dst = 60 * 60, sign = 1;
320         static char lastTZ[2 * RULE_LEN];
321         static char buffer[RULE_LEN];
322
323         if (!p) return;
324
325 #if     USE_TABLE
326         usetable = 0;
327 #endif
328         if (*p == ':') {
329                 /*
330                  * According to POSIX, this is implementation defined.
331                  * Since it depends on the particular operating system, we
332                  * can do nothing.
333                  */
334                 return;
335         }
336
337         if (!strcmp(lastTZ, p)) return;         /* nothing changed */
338
339         *_tzname[0] = '\0';
340         *_tzname[1] = '\0';
341         dststart.ds_type = 'U';
342         dststart.ds_sec = 2 * 60 * 60;
343         dstend.ds_type = 'U';
344         dstend.ds_sec = 2 * 60 * 60;
345
346         if (strlen(p) > 2 * RULE_LEN) return;
347         strcpy(lastTZ, p);
348
349         if (!(p = parseZoneName(buffer, p))) return;
350
351         if (*p == '-') {
352                 sign = -1;
353                 p++;
354         } else if (*p == '+') p++;
355
356         if (!(p = parseTime(&tz, p, NULL))) return;
357         tz *= sign;
358         _timezone = tz;
359         strncpy(_tzname[0], buffer, TZ_LEN);
360
361         if (!(_daylight = (*p != '\0'))) return;
362
363         buffer[0] = '\0';
364         if (!(p = parseZoneName(buffer, p))) return;
365         strncpy(_tzname[1], buffer, TZ_LEN);
366
367         buffer[0] = '\0';
368         if (*p && (*p != ','))
369                 if (!(p = parseTime(&dst, p, NULL))) return;
370         _dst_off = dst;                 /* dst was initialized to 1 hour */
371         if (*p) {
372                 if (*p != ',') return;
373                 p++;
374                 if (strlen(p) > RULE_LEN) return;
375                 if (!(p = parseRule(buffer, p))) return;
376         }
377 }
378
379 void
380 _tzset(void)
381 {
382 #if     defined(__BSD4_2)
383
384         struct timeval tv;
385         struct timezone tz;
386
387         _gettimeofday(&tv, &tz);
388         _daylight = tz.tz_dsttime;
389         _timezone = tz.tz_minuteswest * 60L;
390
391 #elif   !defined(_POSIX_SOURCE) && !defined(__USG)
392
393 #if     !defined(_MINIX)                /* MINIX has no ftime() */
394         struct timeb tim;
395
396         _ftime(&tim);
397         _timezone = tim.timezone * 60L;
398         _daylight = tim.dstflag;
399 #endif
400
401 #endif  /* !_POSIX_SOURCE && !__USG */
402
403         parseTZ(getenv("TZ"));          /* should go inside #if */
404
405 #if     defined(__USG) || defined(_POSIX_SOURCE)
406         tzname[0] = _tzname[0];
407         tzname[1] = _tzname[1];
408 #if     defined(__USG)
409         timezone = _timezone;
410         daylight = _daylight;
411 #endif
412 #endif  /* __USG || _POSIX_SOURCE */
413 }
414
415 static int
416 last_sunday(register int day, register struct tm *timep)
417 {
418         int first = FIRSTSUNDAY(timep);
419
420         if (day >= 58 && LEAPYEAR(YEAR0 + timep->tm_year)) day++;
421         if (day < first) return first;
422         return day - (day - first) % 7;
423 }
424
425 static int
426 date_of(register struct dsttype *dst, struct tm *timep)
427 {
428         int leap = LEAPYEAR(YEAR0 + timep->tm_year);
429         int firstday, tmpday;
430         register int day, month;
431
432         if (dst->ds_type != 'M') {
433                 return dst->ds_date[0] -
434                             (dst->ds_type == 'J'
435                                 && leap
436                                 && dst->ds_date[0] < 58);
437         }
438         day = 0;
439         month = 1;
440         while (month < dst->ds_date[0]) {
441                 day += _ytab[leap][month - 1];
442                 month++;
443         }
444         firstday = (day + FIRSTDAYOF(timep)) % 7;
445         tmpday = day;
446         day += (dst->ds_date[2] - firstday + 7) % 7
447                 + 7 * (dst->ds_date[1] - 1);
448         if (day >= tmpday + _ytab[leap][month]) day -= 7;
449         return day;
450 }
451
452 /*
453  * The default dst transitions are those for Western Europe (except Great
454  * Britain). 
455  */
456 unsigned
457 _dstget(register struct tm *timep)
458 {
459         int begindst, enddst;
460         register struct dsttype *dsts = &dststart, *dste = &dstend;
461         int do_dst = 0;
462
463         if (_daylight == -1)
464                 _tzset();
465
466         timep->tm_isdst = _daylight;
467         if (!_daylight) return 0;
468
469         if (dsts->ds_type != 'U')
470                 begindst = date_of(dsts, timep);
471         else begindst = last_sunday(89, timep); /* last Sun before Apr */
472         if (dste->ds_type != 'U')
473                 enddst = date_of(dste, timep);
474         else enddst = last_sunday(272, timep);  /* last Sun in Sep */
475
476         /* assume begindst != enddst (otherwise it would be no use) */
477         if (begindst < enddst) {                /* northern hemisphere */
478                 if (timep->tm_yday > begindst && timep->tm_yday < enddst)
479                         do_dst = 1;
480         } else {                                /* southern hemisphere */
481                 if (timep->tm_yday > begindst || timep->tm_yday < enddst)
482                         do_dst = 1;
483         }
484
485         if (!do_dst
486             && (timep->tm_yday == begindst || timep->tm_yday == enddst)) {
487                 long dsttranssec;       /* transition when day is this old */
488                 long cursec;
489
490                 if (timep->tm_yday == begindst)
491                         dsttranssec = dsts->ds_sec;
492                 else    dsttranssec = dste->ds_sec;
493                 cursec = ((timep->tm_hour * 60) + timep->tm_min) * 60L
494                             + timep->tm_sec;
495
496                 if ((timep->tm_yday == begindst && cursec >= dsttranssec)
497                     || (timep->tm_yday == enddst && cursec < dsttranssec))
498                         do_dst = 1;
499         }
500 #if USE_TABLE
501         if (usetable) ZoneFromTable(_timezone);
502 #endif
503         if (do_dst) return _dst_off;
504         timep->tm_isdst = 0;
505         return 0;
506 }