1 | /* Time zone functions such as tzalloc and localtime_rz
|
---|
2 |
|
---|
3 | Copyright 2015-2016 Free Software Foundation, Inc.
|
---|
4 |
|
---|
5 | This program is free software; you can redistribute it and/or modify
|
---|
6 | it under the terms of the GNU General Public License as published by
|
---|
7 | the Free Software Foundation; either version 3, or (at your option)
|
---|
8 | any later version.
|
---|
9 |
|
---|
10 | This program is distributed in the hope that it will be useful,
|
---|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | GNU General Public License for more details.
|
---|
14 |
|
---|
15 | You should have received a copy of the GNU General Public License along
|
---|
16 | with this program; if not, see <http://www.gnu.org/licenses/>. */
|
---|
17 |
|
---|
18 | /* Written by Paul Eggert. */
|
---|
19 |
|
---|
20 | /* Although this module is not thread-safe, any races should be fairly
|
---|
21 | rare and reasonably benign. For complete thread-safety, use a C
|
---|
22 | library with a working timezone_t type, so that this module is not
|
---|
23 | needed. */
|
---|
24 |
|
---|
25 | #include <config.h>
|
---|
26 |
|
---|
27 | #include <time.h>
|
---|
28 |
|
---|
29 | #include <errno.h>
|
---|
30 | #include <stdbool.h>
|
---|
31 | #include <stddef.h>
|
---|
32 | #include <stdlib.h>
|
---|
33 | #include <string.h>
|
---|
34 |
|
---|
35 | #include "flexmember.h"
|
---|
36 | #include "time-internal.h"
|
---|
37 |
|
---|
38 | #if !HAVE_TZSET
|
---|
39 | static void tzset (void) { }
|
---|
40 | #endif
|
---|
41 |
|
---|
42 | /* The approximate size to use for small allocation requests. This is
|
---|
43 | the largest "small" request for the GNU C library malloc. */
|
---|
44 | enum { DEFAULT_MXFAST = 64 * sizeof (size_t) / 4 };
|
---|
45 |
|
---|
46 | /* Minimum size of the ABBRS member of struct abbr. ABBRS is larger
|
---|
47 | only in the unlikely case where an abbreviation longer than this is
|
---|
48 | used. */
|
---|
49 | enum { ABBR_SIZE_MIN = DEFAULT_MXFAST - offsetof (struct tm_zone, abbrs) };
|
---|
50 |
|
---|
51 | /* Magic cookie timezone_t value, for local time. It differs from
|
---|
52 | NULL and from all other timezone_t values. Only the address
|
---|
53 | matters; the pointer is never dereferenced. */
|
---|
54 | static timezone_t const local_tz = (timezone_t) 1;
|
---|
55 |
|
---|
56 | #if HAVE_TM_ZONE || HAVE_TZNAME
|
---|
57 |
|
---|
58 | /* Return true if the values A and B differ according to the rules for
|
---|
59 | tm_isdst: A and B differ if one is zero and the other positive. */
|
---|
60 | static bool
|
---|
61 | isdst_differ (int a, int b)
|
---|
62 | {
|
---|
63 | return !a != !b && 0 <= a && 0 <= b;
|
---|
64 | }
|
---|
65 |
|
---|
66 | /* Return true if A and B are equal. */
|
---|
67 | static int
|
---|
68 | equal_tm (const struct tm *a, const struct tm *b)
|
---|
69 | {
|
---|
70 | return ! ((a->tm_sec ^ b->tm_sec)
|
---|
71 | | (a->tm_min ^ b->tm_min)
|
---|
72 | | (a->tm_hour ^ b->tm_hour)
|
---|
73 | | (a->tm_mday ^ b->tm_mday)
|
---|
74 | | (a->tm_mon ^ b->tm_mon)
|
---|
75 | | (a->tm_year ^ b->tm_year)
|
---|
76 | | isdst_differ (a->tm_isdst, b->tm_isdst));
|
---|
77 | }
|
---|
78 |
|
---|
79 | #endif
|
---|
80 |
|
---|
81 | /* Copy to ABBRS the abbreviation at ABBR with size ABBR_SIZE (this
|
---|
82 | includes its trailing null byte). Append an extra null byte to
|
---|
83 | mark the end of ABBRS. */
|
---|
84 | static void
|
---|
85 | extend_abbrs (char *abbrs, char const *abbr, size_t abbr_size)
|
---|
86 | {
|
---|
87 | memcpy (abbrs, abbr, abbr_size);
|
---|
88 | abbrs[abbr_size] = '\0';
|
---|
89 | }
|
---|
90 |
|
---|
91 | /* Return a newly allocated time zone for NAME, or NULL on failure.
|
---|
92 | A null NAME stands for wall clock time (which is like unset TZ). */
|
---|
93 | timezone_t
|
---|
94 | tzalloc (char const *name)
|
---|
95 | {
|
---|
96 | size_t name_size = name ? strlen (name) + 1 : 0;
|
---|
97 | size_t abbr_size = name_size < ABBR_SIZE_MIN ? ABBR_SIZE_MIN : name_size + 1;
|
---|
98 | timezone_t tz = malloc (FLEXSIZEOF (struct tm_zone, abbrs, abbr_size));
|
---|
99 | if (tz)
|
---|
100 | {
|
---|
101 | tz->next = NULL;
|
---|
102 | #if HAVE_TZNAME && !HAVE_TM_ZONE
|
---|
103 | tz->tzname_copy[0] = tz->tzname_copy[1] = NULL;
|
---|
104 | #endif
|
---|
105 | tz->tz_is_set = !!name;
|
---|
106 | tz->abbrs[0] = '\0';
|
---|
107 | if (name)
|
---|
108 | extend_abbrs (tz->abbrs, name, name_size);
|
---|
109 | }
|
---|
110 | return tz;
|
---|
111 | }
|
---|
112 |
|
---|
113 | /* Save into TZ any nontrivial time zone abbreviation used by TM, and
|
---|
114 | update *TM (if HAVE_TM_ZONE) or *TZ (if !HAVE_TM_ZONE &&
|
---|
115 | HAVE_TZNAME) if they use the abbreviation. Return true if
|
---|
116 | successful, false (setting errno) otherwise. */
|
---|
117 | static bool
|
---|
118 | save_abbr (timezone_t tz, struct tm *tm)
|
---|
119 | {
|
---|
120 | #if HAVE_TM_ZONE || HAVE_TZNAME
|
---|
121 | char const *zone = NULL;
|
---|
122 | char *zone_copy = (char *) "";
|
---|
123 |
|
---|
124 | # if HAVE_TZNAME
|
---|
125 | int tzname_index = -1;
|
---|
126 | # endif
|
---|
127 |
|
---|
128 | # if HAVE_TM_ZONE
|
---|
129 | zone = tm->tm_zone;
|
---|
130 | # endif
|
---|
131 |
|
---|
132 | # if HAVE_TZNAME
|
---|
133 | if (! (zone && *zone) && 0 <= tm->tm_isdst)
|
---|
134 | {
|
---|
135 | tzname_index = tm->tm_isdst != 0;
|
---|
136 | zone = tzname[tzname_index];
|
---|
137 | }
|
---|
138 | # endif
|
---|
139 |
|
---|
140 | /* No need to replace null zones, or zones within the struct tm. */
|
---|
141 | if (!zone || ((char *) tm <= zone && zone < (char *) (tm + 1)))
|
---|
142 | return true;
|
---|
143 |
|
---|
144 | if (*zone)
|
---|
145 | {
|
---|
146 | zone_copy = tz->abbrs;
|
---|
147 |
|
---|
148 | while (strcmp (zone_copy, zone) != 0)
|
---|
149 | {
|
---|
150 | if (! (*zone_copy || (zone_copy == tz->abbrs && tz->tz_is_set)))
|
---|
151 | {
|
---|
152 | size_t zone_size = strlen (zone) + 1;
|
---|
153 | if (zone_size < tz->abbrs + ABBR_SIZE_MIN - zone_copy)
|
---|
154 | extend_abbrs (zone_copy, zone, zone_size);
|
---|
155 | else
|
---|
156 | {
|
---|
157 | tz = tz->next = tzalloc (zone);
|
---|
158 | if (!tz)
|
---|
159 | return false;
|
---|
160 | tz->tz_is_set = 0;
|
---|
161 | zone_copy = tz->abbrs;
|
---|
162 | }
|
---|
163 | break;
|
---|
164 | }
|
---|
165 |
|
---|
166 | zone_copy += strlen (zone_copy) + 1;
|
---|
167 | if (!*zone_copy && tz->next)
|
---|
168 | {
|
---|
169 | tz = tz->next;
|
---|
170 | zone_copy = tz->abbrs;
|
---|
171 | }
|
---|
172 | }
|
---|
173 | }
|
---|
174 |
|
---|
175 | /* Replace the zone name so that its lifetime matches that of TZ. */
|
---|
176 | # if HAVE_TM_ZONE
|
---|
177 | tm->tm_zone = zone_copy;
|
---|
178 | # else
|
---|
179 | if (0 <= tzname_index)
|
---|
180 | tz->tzname_copy[tzname_index] = zone_copy;
|
---|
181 | # endif
|
---|
182 | #endif
|
---|
183 |
|
---|
184 | return true;
|
---|
185 | }
|
---|
186 |
|
---|
187 | /* Free a time zone. */
|
---|
188 | void
|
---|
189 | tzfree (timezone_t tz)
|
---|
190 | {
|
---|
191 | if (tz != local_tz)
|
---|
192 | while (tz)
|
---|
193 | {
|
---|
194 | timezone_t next = tz->next;
|
---|
195 | free (tz);
|
---|
196 | tz = next;
|
---|
197 | }
|
---|
198 | }
|
---|
199 |
|
---|
200 | /* Get and set the TZ environment variable. These functions can be
|
---|
201 | overridden by programs like Emacs that manage their own environment. */
|
---|
202 |
|
---|
203 | #ifndef getenv_TZ
|
---|
204 | static char *
|
---|
205 | getenv_TZ (void)
|
---|
206 | {
|
---|
207 | return getenv ("TZ");
|
---|
208 | }
|
---|
209 | #endif
|
---|
210 |
|
---|
211 | #ifndef setenv_TZ
|
---|
212 | static int
|
---|
213 | setenv_TZ (char const *tz)
|
---|
214 | {
|
---|
215 | return tz ? setenv ("TZ", tz, 1) : unsetenv ("TZ");
|
---|
216 | }
|
---|
217 | #endif
|
---|
218 |
|
---|
219 | /* Change the environment to match the specified timezone_t value.
|
---|
220 | Return true if successful, false (setting errno) otherwise. */
|
---|
221 | static bool
|
---|
222 | change_env (timezone_t tz)
|
---|
223 | {
|
---|
224 | if (setenv_TZ (tz->tz_is_set ? tz->abbrs : NULL) != 0)
|
---|
225 | return false;
|
---|
226 | tzset ();
|
---|
227 | return true;
|
---|
228 | }
|
---|
229 |
|
---|
230 | /* Temporarily set the time zone to TZ, which must not be null.
|
---|
231 | Return LOCAL_TZ if the time zone setting is already correct.
|
---|
232 | Otherwise return a newly allocated time zone representing the old
|
---|
233 | setting, or NULL (setting errno) on failure. */
|
---|
234 | static timezone_t
|
---|
235 | set_tz (timezone_t tz)
|
---|
236 | {
|
---|
237 | char *env_tz = getenv_TZ ();
|
---|
238 | if (env_tz
|
---|
239 | ? tz->tz_is_set && strcmp (tz->abbrs, env_tz) == 0
|
---|
240 | : !tz->tz_is_set)
|
---|
241 | return local_tz;
|
---|
242 | else
|
---|
243 | {
|
---|
244 | timezone_t old_tz = tzalloc (env_tz);
|
---|
245 | if (!old_tz)
|
---|
246 | return old_tz;
|
---|
247 | if (! change_env (tz))
|
---|
248 | {
|
---|
249 | int saved_errno = errno;
|
---|
250 | tzfree (old_tz);
|
---|
251 | errno = saved_errno;
|
---|
252 | return NULL;
|
---|
253 | }
|
---|
254 | return old_tz;
|
---|
255 | }
|
---|
256 | }
|
---|
257 |
|
---|
258 | /* Restore an old setting returned by set_tz. It must not be null.
|
---|
259 | Return true (preserving errno) if successful, false (setting errno)
|
---|
260 | otherwise. */
|
---|
261 | static bool
|
---|
262 | revert_tz (timezone_t tz)
|
---|
263 | {
|
---|
264 | if (tz == local_tz)
|
---|
265 | return true;
|
---|
266 | else
|
---|
267 | {
|
---|
268 | int saved_errno = errno;
|
---|
269 | bool ok = change_env (tz);
|
---|
270 | if (!ok)
|
---|
271 | saved_errno = errno;
|
---|
272 | tzfree (tz);
|
---|
273 | errno = saved_errno;
|
---|
274 | return ok;
|
---|
275 | }
|
---|
276 | }
|
---|
277 |
|
---|
278 | /* Use time zone TZ to compute localtime_r (T, TM). */
|
---|
279 | struct tm *
|
---|
280 | localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
|
---|
281 | {
|
---|
282 | if (!tz)
|
---|
283 | return gmtime_r (t, tm);
|
---|
284 | else
|
---|
285 | {
|
---|
286 | timezone_t old_tz = set_tz (tz);
|
---|
287 | if (old_tz)
|
---|
288 | {
|
---|
289 | bool abbr_saved = localtime_r (t, tm) && save_abbr (tz, tm);
|
---|
290 | if (revert_tz (old_tz) && abbr_saved)
|
---|
291 | return tm;
|
---|
292 | }
|
---|
293 | return NULL;
|
---|
294 | }
|
---|
295 | }
|
---|
296 |
|
---|
297 | /* Use time zone TZ to compute mktime (TM). */
|
---|
298 | time_t
|
---|
299 | mktime_z (timezone_t tz, struct tm *tm)
|
---|
300 | {
|
---|
301 | if (!tz)
|
---|
302 | return timegm (tm);
|
---|
303 | else
|
---|
304 | {
|
---|
305 | timezone_t old_tz = set_tz (tz);
|
---|
306 | if (old_tz)
|
---|
307 | {
|
---|
308 | time_t t = mktime (tm);
|
---|
309 | #if HAVE_TM_ZONE || HAVE_TZNAME
|
---|
310 | time_t badtime = -1;
|
---|
311 | struct tm tm_1;
|
---|
312 | if ((t != badtime
|
---|
313 | || (localtime_r (&t, &tm_1) && equal_tm (tm, &tm_1)))
|
---|
314 | && !save_abbr (tz, tm))
|
---|
315 | t = badtime;
|
---|
316 | #endif
|
---|
317 | if (revert_tz (old_tz))
|
---|
318 | return t;
|
---|
319 | }
|
---|
320 | return -1;
|
---|
321 | }
|
---|
322 | }
|
---|