[BZ #5040]
[glibc.git] / time / tzset.c
blob0d5420218537bf84d66a435428ff44d7293b40bf
1 /* Copyright (C) 1991-2002,2003,2004,2007 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, write to the Free
16 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17 02111-1307 USA. */
19 #include <ctype.h>
20 #include <errno.h>
21 #include <bits/libc-lock.h>
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
29 #define NOID
30 #include <timezone/tzfile.h>
32 char *__tzname[2] = { (char *) "GMT", (char *) "GMT" };
33 int __daylight = 0;
34 long int __timezone = 0L;
36 weak_alias (__tzname, tzname)
37 weak_alias (__daylight, daylight)
38 weak_alias (__timezone, timezone)
40 /* This locks all the state variables in tzfile.c and this file. */
41 __libc_lock_define_initialized (static, tzset_lock)
44 #define min(a, b) ((a) < (b) ? (a) : (b))
45 #define max(a, b) ((a) > (b) ? (a) : (b))
46 #define sign(x) ((x) < 0 ? -1 : 1)
49 /* This structure contains all the information about a
50 timezone given in the POSIX standard TZ envariable. */
51 typedef struct
53 const char *name;
55 /* When to change. */
56 enum { J0, J1, M } type; /* Interpretation of: */
57 unsigned short int m, n, d; /* Month, week, day. */
58 unsigned int secs; /* Time of day. */
60 long int offset; /* Seconds east of GMT (west if < 0). */
62 /* We cache the computed time of change for a
63 given year so we don't have to recompute it. */
64 time_t change; /* When to change to this zone. */
65 int computed_for; /* Year above is computed for. */
66 } tz_rule;
68 /* tz_rules[0] is standard, tz_rules[1] is daylight. */
69 static tz_rule tz_rules[2];
72 static void compute_change (tz_rule *rule, int year) __THROW internal_function;
73 static void tzset_internal (int always, int explicit)
74 __THROW internal_function;
76 /* List of buffers containing time zone strings. */
77 struct tzstring_l
79 struct tzstring_l *next;
80 size_t len; /* strlen(data) - doesn't count terminating NUL! */
81 char data[0];
84 static struct tzstring_l *tzstring_list;
86 /* Allocate a permanent home for S. It will never be moved or deallocated,
87 but may share space with other strings.
88 Don't modify the returned string. */
89 char *
90 __tzstring (const char *s)
92 char *p;
93 struct tzstring_l *t, *u, *new;
94 size_t len = strlen (s);
96 /* Walk the list and look for a match. If this string is the same
97 as the end of an already-allocated string, it can share space. */
98 for (u = t = tzstring_list; t; u = t, t = t->next)
99 if (len <= t->len)
101 p = &t->data[t->len - len];
102 if (strcmp (s, p) == 0)
103 return p;
106 /* Not found; allocate a new buffer. */
107 new = malloc (sizeof (struct tzstring_l) + len + 1);
108 if (!new)
109 return NULL;
111 new->next = NULL;
112 new->len = len;
113 strcpy (new->data, s);
115 if (u)
116 u->next = new;
117 else
118 tzstring_list = new;
120 return new->data;
123 /* Maximum length of a timezone name. tzset_internal keeps this up to date
124 (never decreasing it) when ! __use_tzfile.
125 tzfile.c keeps it up to date when __use_tzfile. */
126 size_t __tzname_cur_max;
128 long int
129 __tzname_max ()
131 __libc_lock_lock (tzset_lock);
133 tzset_internal (0, 0);
135 __libc_lock_unlock (tzset_lock);
137 return __tzname_cur_max;
140 static char *old_tz;
142 static void
143 internal_function
144 update_vars (void)
146 __daylight = tz_rules[0].offset != tz_rules[1].offset;
147 __timezone = -tz_rules[0].offset;
148 __tzname[0] = (char *) tz_rules[0].name;
149 __tzname[1] = (char *) tz_rules[1].name;
151 /* Keep __tzname_cur_max up to date. */
152 size_t len0 = strlen (__tzname[0]);
153 size_t len1 = strlen (__tzname[1]);
154 if (len0 > __tzname_cur_max)
155 __tzname_cur_max = len0;
156 if (len1 > __tzname_cur_max)
157 __tzname_cur_max = len1;
160 /* Parse the POSIX TZ-style string. */
161 void
162 __tzset_parse_tz (tz)
163 const char *tz;
165 register size_t l;
166 char *tzbuf;
167 unsigned short int hh, mm, ss;
168 unsigned short int whichrule;
170 /* Clear out old state and reset to unnamed UTC. */
171 memset (tz_rules, 0, sizeof tz_rules);
172 tz_rules[0].name = tz_rules[1].name = "";
174 /* Get the standard timezone name. */
175 tzbuf = strdupa (tz);
177 if (sscanf (tz, "%[^0-9,+-]", tzbuf) != 1 ||
178 (l = strlen (tzbuf)) < 3)
179 goto out;
181 tz_rules[0].name = __tzstring (tzbuf);
183 tz += l;
185 /* Figure out the standard offset from UTC. */
186 if (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz)))
187 goto out;
189 if (*tz == '-' || *tz == '+')
190 tz_rules[0].offset = *tz++ == '-' ? 1L : -1L;
191 else
192 tz_rules[0].offset = -1L;
193 switch (sscanf (tz, "%hu:%hu:%hu", &hh, &mm, &ss))
195 default:
196 tz_rules[0].offset = 0;
197 goto out;
198 case 1:
199 mm = 0;
200 case 2:
201 ss = 0;
202 case 3:
203 break;
205 tz_rules[0].offset *= (min (ss, 59) + (min (mm, 59) * 60) +
206 (min (hh, 24) * 60 * 60));
208 for (l = 0; l < 3; ++l)
210 while (isdigit(*tz))
211 ++tz;
212 if (l < 2 && *tz == ':')
213 ++tz;
216 /* Get the DST timezone name (if any). */
217 if (*tz != '\0')
219 char *n = tzbuf + strlen (tzbuf) + 1;
220 if (sscanf (tz, "%[^0-9,+-]", n) != 1 ||
221 (l = strlen (n)) < 3)
222 goto done_names; /* Punt on name, set up the offsets. */
224 tz_rules[1].name = __tzstring (n);
226 tz += l;
228 /* Figure out the DST offset from GMT. */
229 if (*tz == '-' || *tz == '+')
230 tz_rules[1].offset = *tz++ == '-' ? 1L : -1L;
231 else
232 tz_rules[1].offset = -1L;
234 switch (sscanf (tz, "%hu:%hu:%hu", &hh, &mm, &ss))
236 default:
237 /* Default to one hour later than standard time. */
238 tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
239 break;
241 case 1:
242 mm = 0;
243 case 2:
244 ss = 0;
245 case 3:
246 tz_rules[1].offset *= (min (ss, 59) + (min (mm, 59) * 60) +
247 (min (hh, 23) * (60 * 60)));
248 break;
250 for (l = 0; l < 3; ++l)
252 while (isdigit (*tz))
253 ++tz;
254 if (l < 2 && *tz == ':')
255 ++tz;
257 if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
259 /* There is no rule. See if there is a default rule file. */
260 __tzfile_default (tz_rules[0].name, tz_rules[1].name,
261 tz_rules[0].offset, tz_rules[1].offset);
262 if (__use_tzfile)
264 free (old_tz);
265 old_tz = NULL;
266 return;
270 else
272 /* There is no DST. */
273 tz_rules[1].name = tz_rules[0].name;
274 tz_rules[1].offset = tz_rules[0].offset;
275 goto out;
278 done_names:
279 /* Figure out the standard <-> DST rules. */
280 for (whichrule = 0; whichrule < 2; ++whichrule)
282 register tz_rule *tzr = &tz_rules[whichrule];
284 /* Ignore comma to support string following the incorrect
285 specification in early POSIX.1 printings. */
286 tz += *tz == ',';
288 /* Get the date of the change. */
289 if (*tz == 'J' || isdigit (*tz))
291 char *end;
292 tzr->type = *tz == 'J' ? J1 : J0;
293 if (tzr->type == J1 && !isdigit (*++tz))
294 goto out;
295 tzr->d = (unsigned short int) strtoul (tz, &end, 10);
296 if (end == tz || tzr->d > 365)
297 goto out;
298 else if (tzr->type == J1 && tzr->d == 0)
299 goto out;
300 tz = end;
302 else if (*tz == 'M')
304 int n;
305 tzr->type = M;
306 if (sscanf (tz, "M%hu.%hu.%hu%n",
307 &tzr->m, &tzr->n, &tzr->d, &n) != 3 ||
308 tzr->m < 1 || tzr->m > 12 ||
309 tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
310 goto out;
311 tz += n;
313 else if (*tz == '\0')
315 /* United States Federal Law, the equivalent of "M4.1.0,M10.5.0". */
316 tzr->type = M;
317 if (tzr == &tz_rules[0])
319 tzr->m = 4;
320 tzr->n = 1;
321 tzr->d = 0;
323 else
325 tzr->m = 10;
326 tzr->n = 5;
327 tzr->d = 0;
330 else
331 goto out;
333 if (*tz != '\0' && *tz != '/' && *tz != ',')
334 goto out;
335 else if (*tz == '/')
337 /* Get the time of day of the change. */
338 ++tz;
339 if (*tz == '\0')
340 goto out;
341 switch (sscanf (tz, "%hu:%hu:%hu", &hh, &mm, &ss))
343 default:
344 hh = 2; /* Default to 2:00 AM. */
345 case 1:
346 mm = 0;
347 case 2:
348 ss = 0;
349 case 3:
350 break;
352 for (l = 0; l < 3; ++l)
354 while (isdigit (*tz))
355 ++tz;
356 if (l < 2 && *tz == ':')
357 ++tz;
359 tzr->secs = (hh * 60 * 60) + (mm * 60) + ss;
361 else
362 /* Default to 2:00 AM. */
363 tzr->secs = 2 * 60 * 60;
365 tzr->computed_for = -1;
368 out:
369 update_vars ();
372 /* Interpret the TZ envariable. */
373 static void
374 internal_function
375 tzset_internal (always, explicit)
376 int always;
377 int explicit;
379 static int is_initialized;
380 register const char *tz;
382 if (is_initialized && !always)
383 return;
384 is_initialized = 1;
386 /* Examine the TZ environment variable. */
387 tz = getenv ("TZ");
388 if (tz == NULL && !explicit)
389 /* Use the site-wide default. This is a file name which means we
390 would not see changes to the file if we compare only the file
391 name for change. We want to notice file changes if tzset() has
392 been called explicitly. Leave TZ as NULL in this case. */
393 tz = TZDEFAULT;
394 if (tz && *tz == '\0')
395 /* User specified the empty string; use UTC explicitly. */
396 tz = "Universal";
398 /* A leading colon means "implementation defined syntax".
399 We ignore the colon and always use the same algorithm:
400 try a data file, and if none exists parse the 1003.1 syntax. */
401 if (tz && *tz == ':')
402 ++tz;
404 /* Check whether the value changed since the last run. */
405 if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
406 /* No change, simply return. */
407 return;
409 if (tz == NULL)
410 /* No user specification; use the site-wide default. */
411 tz = TZDEFAULT;
413 tz_rules[0].name = NULL;
414 tz_rules[1].name = NULL;
416 /* Save the value of `tz'. */
417 if (old_tz != NULL)
418 free (old_tz);
419 old_tz = tz ? __strdup (tz) : NULL;
421 /* Try to read a data file. */
422 __tzfile_read (tz, 0, NULL);
423 if (__use_tzfile)
424 return;
426 /* No data file found. Default to UTC if nothing specified. */
428 if (tz == NULL || *tz == '\0'
429 || (TZDEFAULT != NULL && strcmp (tz, TZDEFAULT) == 0))
431 tz_rules[0].name = tz_rules[1].name = "UTC";
432 tz_rules[0].type = tz_rules[1].type = J0;
433 tz_rules[0].m = tz_rules[0].n = tz_rules[0].d = 0;
434 tz_rules[1].m = tz_rules[1].n = tz_rules[1].d = 0;
435 tz_rules[0].secs = tz_rules[1].secs = 0;
436 tz_rules[0].offset = tz_rules[1].offset = 0L;
437 tz_rules[0].change = tz_rules[1].change = (time_t) -1;
438 tz_rules[0].computed_for = tz_rules[1].computed_for = 0;
439 update_vars ();
440 return;
443 __tzset_parse_tz (tz);
446 /* Figure out the exact time (as a time_t) in YEAR
447 when the change described by RULE will occur and
448 put it in RULE->change, saving YEAR in RULE->computed_for. */
449 static void
450 internal_function
451 compute_change (rule, year)
452 tz_rule *rule;
453 int year;
455 register time_t t;
457 if (year != -1 && rule->computed_for == year)
458 /* Operations on times in 2 BC will be slower. Oh well. */
459 return;
461 /* First set T to January 1st, 0:00:00 GMT in YEAR. */
462 if (year > 1970)
463 t = ((year - 1970) * 365
464 + /* Compute the number of leapdays between 1970 and YEAR
465 (exclusive). There is a leapday every 4th year ... */
466 + ((year - 1) / 4 - 1970 / 4)
467 /* ... except every 100th year ... */
468 - ((year - 1) / 100 - 1970 / 100)
469 /* ... but still every 400th year. */
470 + ((year - 1) / 400 - 1970 / 400)) * SECSPERDAY;
471 else
472 t = 0;
474 switch (rule->type)
476 case J1:
477 /* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap years.
478 In non-leap years, or if the day number is 59 or less, just
479 add SECSPERDAY times the day number-1 to the time of
480 January 1, midnight, to get the day. */
481 t += (rule->d - 1) * SECSPERDAY;
482 if (rule->d >= 60 && __isleap (year))
483 t += SECSPERDAY;
484 break;
486 case J0:
487 /* n - Day of year.
488 Just add SECSPERDAY times the day number to the time of Jan 1st. */
489 t += rule->d * SECSPERDAY;
490 break;
492 case M:
493 /* Mm.n.d - Nth "Dth day" of month M. */
495 unsigned int i;
496 int d, m1, yy0, yy1, yy2, dow;
497 const unsigned short int *myday =
498 &__mon_yday[__isleap (year)][rule->m];
500 /* First add SECSPERDAY for each day in months before M. */
501 t += myday[-1] * SECSPERDAY;
503 /* Use Zeller's Congruence to get day-of-week of first day of month. */
504 m1 = (rule->m + 9) % 12 + 1;
505 yy0 = (rule->m <= 2) ? (year - 1) : year;
506 yy1 = yy0 / 100;
507 yy2 = yy0 % 100;
508 dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
509 if (dow < 0)
510 dow += 7;
512 /* DOW is the day-of-week of the first day of the month. Get the
513 day-of-month (zero-origin) of the first DOW day of the month. */
514 d = rule->d - dow;
515 if (d < 0)
516 d += 7;
517 for (i = 1; i < rule->n; ++i)
519 if (d + 7 >= (int) myday[0] - myday[-1])
520 break;
521 d += 7;
524 /* D is the day-of-month (zero-origin) of the day we want. */
525 t += d * SECSPERDAY;
527 break;
530 /* T is now the Epoch-relative time of 0:00:00 GMT on the day we want.
531 Just add the time of day and local offset from GMT, and we're done. */
533 rule->change = t - rule->offset + rule->secs;
534 rule->computed_for = year;
538 /* Figure out the correct timezone for TM and set `__tzname',
539 `__timezone', and `__daylight' accordingly. */
540 void
541 internal_function
542 __tz_compute (timer, tm, use_localtime)
543 time_t timer;
544 struct tm *tm;
545 int use_localtime;
547 compute_change (&tz_rules[0], 1900 + tm->tm_year);
548 compute_change (&tz_rules[1], 1900 + tm->tm_year);
550 if (use_localtime)
552 int isdst;
554 /* We have to distinguish between northern and southern
555 hemisphere. For the latter the daylight saving time
556 ends in the next year. */
557 if (__builtin_expect (tz_rules[0].change
558 > tz_rules[1].change, 0))
559 isdst = (timer < tz_rules[1].change
560 || timer >= tz_rules[0].change);
561 else
562 isdst = (timer >= tz_rules[0].change
563 && timer < tz_rules[1].change);
564 tm->tm_isdst = isdst;
565 tm->tm_zone = __tzname[isdst];
566 tm->tm_gmtoff = tz_rules[isdst].offset;
570 /* Reinterpret the TZ environment variable and set `tzname'. */
571 #undef tzset
573 void
574 __tzset (void)
576 __libc_lock_lock (tzset_lock);
578 tzset_internal (1, 1);
580 if (!__use_tzfile)
582 /* Set `tzname'. */
583 __tzname[0] = (char *) tz_rules[0].name;
584 __tzname[1] = (char *) tz_rules[1].name;
587 __libc_lock_unlock (tzset_lock);
589 weak_alias (__tzset, tzset)
591 /* Return the `struct tm' representation of *TIMER in the local timezone.
592 Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
593 struct tm *
594 __tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
596 long int leap_correction;
597 int leap_extra_secs;
599 if (timer == NULL)
601 __set_errno (EINVAL);
602 return NULL;
605 __libc_lock_lock (tzset_lock);
607 /* Update internal database according to current TZ setting.
608 POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
609 This is a good idea since this allows at least a bit more parallelism. */
610 tzset_internal (tp == &_tmbuf && use_localtime, 1);
612 if (__use_tzfile)
613 __tzfile_compute (*timer, use_localtime, &leap_correction,
614 &leap_extra_secs, tp);
615 else
617 if (! __offtime (timer, 0, tp))
618 tp = NULL;
619 else
620 __tz_compute (*timer, tp, use_localtime);
621 leap_correction = 0L;
622 leap_extra_secs = 0;
625 if (tp)
627 if (! use_localtime)
629 tp->tm_isdst = 0;
630 tp->tm_zone = "GMT";
631 tp->tm_gmtoff = 0L;
634 if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp))
635 tp->tm_sec += leap_extra_secs;
636 else
637 tp = NULL;
640 __libc_lock_unlock (tzset_lock);
642 return tp;
646 libc_freeres_fn (free_mem)
648 while (tzstring_list != NULL)
650 struct tzstring_l *old = tzstring_list;
652 tzstring_list = tzstring_list->next;
653 free (old);
655 free (old_tz);
656 old_tz = NULL;