1 /*
2 * Copyright © 2010 Codethink Limited
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Ryan Lortie <desrt@desrt.ca>
20 */
21
22 /* Prologue {{{1 */
23
24 #include "config.h"
25
26 #include "gtimezone.h"
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <signal.h>
31
32 #include "gmappedfile.h"
33 #include "gtestutils.h"
34 #include "gfileutils.h"
35 #include "gstrfuncs.h"
36 #include "ghash.h"
37 #include "gthread.h"
38 #include "gbytes.h"
39 #include "gslice.h"
40 #include "gdatetime.h"
41 #include "gdate.h"
42 #include "genviron.h"
43
44 #ifdef G_OS_UNIX
45 #include "gstdio.h"
46 #endif
47
48 #ifdef G_OS_WIN32
49
50 #define STRICT
51 #include <windows.h>
52 #include <wchar.h>
53 #endif
54
55 /**
56 * GTimeZone:
57 *
58 * A `GTimeZone` represents a time zone, at no particular point in time.
59 *
60 * The `GTimeZone` struct is refcounted and immutable.
61 *
62 * Each time zone has an identifier (for example, ‘Europe/London’) which is
63 * platform dependent. See [ctor@GLib.TimeZone.new] for information on the
64 * identifier formats. The identifier of a time zone can be retrieved using
65 * [method@GLib.TimeZone.get_identifier].
66 *
67 * A time zone contains a number of intervals. Each interval has an abbreviation
68 * to describe it (for example, ‘PDT’), an offset to UTC and a flag indicating
69 * if the daylight savings time is in effect during that interval. A time zone
70 * always has at least one interval — interval 0. Note that interval abbreviations
71 * are not the same as time zone identifiers (apart from ‘UTC’), and cannot be
72 * passed to [ctor@GLib.TimeZone.new].
73 *
74 * Every UTC time is contained within exactly one interval, but a given
75 * local time may be contained within zero, one or two intervals (due to
76 * incontinuities associated with daylight savings time).
77 *
78 * An interval may refer to a specific period of time (eg: the duration
79 * of daylight savings time during 2010) or it may refer to many periods
80 * of time that share the same properties (eg: all periods of daylight
81 * savings time). It is also possible (usually for political reasons)
82 * that some properties (like the abbreviation) change between intervals
83 * without other properties changing.
84 *
85 * Since: 2.26
86 */
87
88 /* IANA zoneinfo file format {{{1 */
89
90 /* unaligned */
91 typedef struct { gchar bytes[8]; } gint64_be;
92 typedef struct { gchar bytes[4]; } gint32_be;
93 typedef struct { gchar bytes[4]; } guint32_be;
94
95 #ifdef G_OS_UNIX
96
97 static inline gint64 gint64_from_be (const gint64_be be) {
98 gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
99 }
100
101 static inline gint32 gint32_from_be (const gint32_be be) {
102 gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
103 }
104
105 static inline guint32 guint32_from_be (const guint32_be be) {
106 guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
107 }
108
109 #endif
110
111 /* The layout of an IANA timezone file header */
112 struct tzhead
113 {
114 gchar tzh_magic[4];
115 gchar tzh_version;
116 guchar tzh_reserved[15];
117
118 guint32_be tzh_ttisgmtcnt;
119 guint32_be tzh_ttisstdcnt;
120 guint32_be tzh_leapcnt;
121 guint32_be tzh_timecnt;
122 guint32_be tzh_typecnt;
123 guint32_be tzh_charcnt;
124 };
125
126 struct ttinfo
127 {
128 gint32_be tt_gmtoff;
129 guint8 tt_isdst;
130 guint8 tt_abbrind;
131 };
132
133 /* A Transition Date structure for TZ Rules, an intermediate structure
134 for parsing MSWindows and Environment-variable time zones. It
135 Generalizes MSWindows's SYSTEMTIME struct.
136 */
137 typedef struct
138 {
139 gint year;
140 gint mon;
141 gint mday;
142 gint wday;
143 gint week;
144 gint32 offset; /* hour*3600 + min*60 + sec; can be negative. */
145 } TimeZoneDate;
146
147 /* POSIX Timezone abbreviations are typically 3 or 4 characters, but
148 Microsoft uses 32-character names. We'll use one larger to ensure
149 we have room for the terminating \0.
150 */
151 #define NAME_SIZE 33
152
153 /* A MSWindows-style time zone transition rule. Generalizes the
154 MSWindows TIME_ZONE_INFORMATION struct. Also used to compose time
155 zones from tzset-style identifiers.
156 */
157 typedef struct
158 {
159 guint start_year;
160 gint32 std_offset;
161 gint32 dlt_offset;
162 TimeZoneDate dlt_start;
163 TimeZoneDate dlt_end;
164 gchar std_name[NAME_SIZE];
165 gchar dlt_name[NAME_SIZE];
166 } TimeZoneRule;
167
168 /* GTimeZone's internal representation of a Daylight Savings (Summer)
169 time interval.
170 */
171 typedef struct
172 {
173 gint32 gmt_offset;
174 gboolean is_dst;
175 gchar *abbrev;
176 } TransitionInfo;
177
178 /* GTimeZone's representation of a transition time to or from Daylight
179 Savings (Summer) time and Standard time for the zone. */
180 typedef struct
181 {
182 gint64 time;
183 gint info_index;
184 } Transition;
185
186 /* GTimeZone structure */
187 struct _GTimeZone
188 {
189 gchar *name;
190 GArray *t_info; /* Array of TransitionInfo */
191 GArray *transitions; /* Array of Transition */
192 gint ref_count;
193 };
194
195 G_LOCK_DEFINE_STATIC (time_zones);
196 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
197 G_LOCK_DEFINE_STATIC (tz_default);
198 static GTimeZone *tz_default = NULL;
199 G_LOCK_DEFINE_STATIC (tz_local);
200 static GTimeZone *tz_local = NULL;
201
202 #define MIN_TZYEAR 1916 /* Daylight Savings started in WWI */
203 #define MAX_TZYEAR 2999 /* And it's not likely ever to go away, but
204 there's no point in getting carried
205 away. */
206
207 #ifdef G_OS_UNIX
208 static GTimeZone *parse_footertz (const gchar *, size_t);
209 #endif
210
211 /**
212 * g_time_zone_unref:
213 * @tz: a #GTimeZone
214 *
215 * Decreases the reference count on @tz.
216 *
217 * Since: 2.26
218 **/
219 void
220 g_time_zone_unref (GTimeZone *tz)
221 {
222 int ref_count;
223
224 again:
225 ref_count = g_atomic_int_get (&tz->ref_count);
226
227 g_assert (ref_count > 0);
228
229 if (ref_count == 1)
230 {
231 if (tz->name != NULL)
232 {
233 G_LOCK(time_zones);
234
235 /* someone else might have grabbed a ref in the meantime */
236 if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
237 {
238 G_UNLOCK(time_zones);
239 goto again;
240 }
241
242 if (time_zones != NULL)
243 g_hash_table_remove (time_zones, tz->name);
244 G_UNLOCK(time_zones);
245 }
246
247 if (tz->t_info != NULL)
248 {
249 guint idx;
250 for (idx = 0; idx < tz->t_info->len; idx++)
251 {
252 TransitionInfo *info = &g_array_index (tz->t_info, TransitionInfo, idx);
253 g_free (info->abbrev);
254 }
255 g_array_free (tz->t_info, TRUE);
256 }
257 if (tz->transitions != NULL)
258 g_array_free (tz->transitions, TRUE);
259 g_free (tz->name);
260
261 g_slice_free (GTimeZone, tz);
262 }
263
264 else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
265 ref_count,
266 ref_count - 1))
267 goto again;
268 }
269
270 /**
271 * g_time_zone_ref:
272 * @tz: a #GTimeZone
273 *
274 * Increases the reference count on @tz.
275 *
276 * Returns: a new reference to @tz.
277 *
278 * Since: 2.26
279 **/
280 GTimeZone *
281 g_time_zone_ref (GTimeZone *tz)
282 {
283 g_assert (tz->ref_count > 0);
284
285 g_atomic_int_inc (&tz->ref_count);
286
287 return tz;
288 }
289
290 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
291 /*
292 * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
293 * - h[h] is 0 to 24
294 * - mm is 00 to 59
295 * - ss is 00 to 59
296 * If RFC8536, TIME_ is a transition time sans sign,
297 * so colons are required before mm and ss, and hh can be up to 167.
298 * See Internet RFC 8536 section 3.3.1:
299 * https://tools.ietf.org/html/rfc8536#section-3.3.1
300 * and POSIX Base Definitions 8.3 TZ rule time:
301 * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
302 */
303 static gboolean
304 parse_time (const gchar *time_,
305 gint32 *offset,
306 gboolean rfc8536)
307 {
308 if (*time_ < '0' || '9' < *time_)
309 return FALSE;
310
311 *offset = 60 * 60 * (*time_++ - '0');
312
313 if (*time_ == '\0')
314 return TRUE;
315
316 if (*time_ != ':')
317 {
318 if (*time_ < '0' || '9' < *time_)
319 return FALSE;
320
321 *offset *= 10;
322 *offset += 60 * 60 * (*time_++ - '0');
323
324 if (rfc8536)
325 {
326 /* Internet RFC 8536 section 3.3.1 and POSIX 8.3 TZ together say
327 that a transition time must be of the form [+-]hh[:mm[:ss]] where
328 the hours part can range from -167 to 167. */
329 if ('0' <= *time_ && *time_ <= '9')
330 {
331 *offset *= 10;
332 *offset += 60 * 60 * (*time_++ - '0');
333 }
334 if (*offset > 167 * 60 * 60)
335 return FALSE;
336 }
337 else if (*offset > 24 * 60 * 60)
338 return FALSE;
339
340 if (*time_ == '\0')
341 return TRUE;
342 }
343
344 if (*time_ == ':')
345 time_++;
346 else if (rfc8536)
347 return FALSE;
348
349 if (*time_ < '0' || '5' < *time_)
350 return FALSE;
351
352 *offset += 10 * 60 * (*time_++ - '0');
353
354 if (*time_ < '0' || '9' < *time_)
355 return FALSE;
356
357 *offset += 60 * (*time_++ - '0');
358
359 if (*time_ == '\0')
360 return TRUE;
361
362 if (*time_ == ':')
363 time_++;
364 else if (rfc8536)
365 return FALSE;
366
367 if (*time_ < '0' || '5' < *time_)
368 return FALSE;
369
370 *offset += 10 * (*time_++ - '0');
371
372 if (*time_ < '0' || '9' < *time_)
373 return FALSE;
374
375 *offset += *time_++ - '0';
376
377 return *time_ == '\0';
378 }
379
380 static gboolean
381 parse_constant_offset (const gchar *name,
382 gint32 *offset,
383 gboolean rfc8536)
384 {
385 /* Internet RFC 8536 section 3.3.1 and POSIX 8.3 TZ together say
386 that a transition time must be numeric. */
387 if (!rfc8536 && g_strcmp0 (name, "UTC") == 0)
388 {
389 *offset = 0;
390 return TRUE;
391 }
392
393 if (*name >= '0' && '9' >= *name)
394 return parse_time (name, offset, rfc8536);
395
396 switch (*name++)
397 {
398 case 'Z':
399 *offset = 0;
400 /* Internet RFC 8536 section 3.3.1 requires a numeric zone. */
401 return !rfc8536 && !*name;
402
403 case '+':
404 return parse_time (name, offset, rfc8536);
405
406 case '-':
407 if (parse_time (name, offset, rfc8536))
408 {
409 *offset = -*offset;
410 return TRUE;
411 }
412 else
413 return FALSE;
414
415 default:
416 return FALSE;
417 }
418 }
419
420 static void
421 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
422 {
423 gint32 offset;
424 TransitionInfo info;
425
426 if (name == NULL || !parse_constant_offset (name, &offset, FALSE))
427 return;
428
429 info.gmt_offset = offset;
430 info.is_dst = FALSE;
431 info.abbrev = g_strdup (name);
432
433 gtz->name = g_strdup (name);
434 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
435 g_array_append_val (gtz->t_info, info);
436
437 /* Constant offset, no transitions */
438 gtz->transitions = NULL;
439 }
440
441 #if defined(G_OS_UNIX) && defined(__sun) && defined(__SVR4)
442 /*
443 * only used by Illumos distros or Solaris < 11: parse the /etc/default/init
444 * text file looking for TZ= followed by the timezone, possibly quoted
445 *
446 */
447 static gchar *
448 zone_identifier_illumos (void)
449 {
450 gchar *resolved_identifier = NULL;
451 gchar *contents = NULL;
452 const gchar *line_start = NULL;
453 gsize tz_len = 0;
454
455 if (!g_file_get_contents ("/etc/default/init", &contents, NULL, NULL) )
456 return NULL;
457
458 /* is TZ= the first/only line in the file? */
459 if (strncmp (contents, "TZ=", 3) == 0)
460 {
461 /* found TZ= on the first line, skip over the TZ= */
462 line_start = contents + 3;
463 }
464 else
465 {
466 /* find a newline followed by TZ= */
467 line_start = strstr (contents, "\nTZ=");
468 if (line_start != NULL)
469 line_start = line_start + 4; /* skip past the \nTZ= */
470 }
471
472 /*
473 * line_start is NULL if we didn't find TZ= at the start of any line,
474 * otherwise it points to what is after the '=' (possibly '\0')
475 */
476 if (line_start == NULL || *line_start == '\0')
477 return NULL;
478
479 /* skip past a possible opening " or ' */
480 if (*line_start == '"' || *line_start == '\'')
481 line_start++;
482
483 /*
484 * loop over the next few characters, building up the length of
485 * the timezone identifier, ending with end of string, newline or
486 * a " or ' character
487 */
488 while (*(line_start + tz_len) != '\0' &&
489 *(line_start + tz_len) != '\n' &&
490 *(line_start + tz_len) != '"' &&
491 *(line_start + tz_len) != '\'')
492 tz_len++;
493
494 if (tz_len > 0)
495 {
496 /* found it */
497 resolved_identifier = g_strndup (line_start, tz_len);
498 g_strchomp (resolved_identifier);
499 g_free (contents);
500 return g_steal_pointer (&resolved_identifier);
501 }
502 else
503 return NULL;
504 }
505 #endif /* defined(__sun) && defined(__SRVR) */
506
507 #ifdef G_OS_UNIX
508 /*
509 * returns the path to the top of the Olson zoneinfo timezone hierarchy.
510 */
511 static const gchar *
512 zone_info_base_dir (void)
513 {
514 if (g_file_test ("/usr/share/zoneinfo", G_FILE_TEST_IS_DIR))
515 return "/usr/share/zoneinfo"; /* Most distros */
516 else if (g_file_test ("/usr/share/lib/zoneinfo", G_FILE_TEST_IS_DIR))
517 return "/usr/share/lib/zoneinfo"; /* Illumos distros */
518
519 /* need a better fallback case */
520 return "/usr/share/zoneinfo";
521 }
522
523 static gchar *
524 zone_identifier_unix (void)
525 {
526 gchar *resolved_identifier = NULL;
527 gsize prefix_len = 0;
528 gchar *canonical_path = NULL;
529 GError *read_link_err = NULL;
530 const gchar *tzdir;
531 gboolean not_a_symlink_to_zoneinfo = FALSE;
532 struct stat file_status;
533
534 /* Resolve the actual timezone pointed to by /etc/localtime. */
535 resolved_identifier = g_file_read_link ("/etc/localtime", &read_link_err);
536
537 if (resolved_identifier != NULL)
538 {
539 if (!g_path_is_absolute (resolved_identifier))
540 {
541 gchar *absolute_resolved_identifier = g_build_filename ("/etc", resolved_identifier, NULL);
542 g_free (resolved_identifier);
543 resolved_identifier = g_steal_pointer (&absolute_resolved_identifier);
544 }
545
546 if (g_lstat (resolved_identifier, &file_status) == 0)
547 {
548 if ((file_status.st_mode & S_IFMT) != S_IFREG)
549 {
550 /* Some systems (e.g. toolbox containers) make /etc/localtime be a symlink
551 * to a symlink.
552 *
553 * Rather than try to cope with that, just ignore /etc/localtime and use
554 * the fallback code to read timezone from /etc/timezone
555 */
556 g_clear_pointer (&resolved_identifier, g_free);
557 not_a_symlink_to_zoneinfo = TRUE;
558 }
559 }
560 else
561 {
562 g_clear_pointer (&resolved_identifier, g_free);
563 }
564 }
565 else
566 {
567 not_a_symlink_to_zoneinfo = g_error_matches (read_link_err,
568 G_FILE_ERROR,
569 G_FILE_ERROR_INVAL);
570 g_clear_error (&read_link_err);
571 }
572
573 if (resolved_identifier == NULL)
574 {
575 /* if /etc/localtime is not a symlink, try:
576 * - /var/db/zoneinfo : 'tzsetup' program on FreeBSD and
577 * DragonflyBSD stores the timezone chosen by the user there.
578 * - /etc/timezone : Gentoo, OpenRC, and others store
579 * the user choice there.
580 * - call zone_identifier_illumos iff __sun and __SVR4 are defined,
581 * as a last-ditch effort to parse the TZ= setting from within
582 * /etc/default/init
583 */
584 if (not_a_symlink_to_zoneinfo && (g_file_get_contents ("/var/db/zoneinfo",
585 &resolved_identifier,
586 NULL, NULL) ||
587 g_file_get_contents ("/etc/timezone",
588 &resolved_identifier,
589 NULL, NULL)
590 #if defined(__sun) && defined(__SVR4)
591 ||
592 (resolved_identifier = zone_identifier_illumos ())
593 #endif
594 ))
595 g_strchomp (resolved_identifier);
596 else
597 {
598 /* Error */
599 g_assert (resolved_identifier == NULL);
600 goto out;
601 }
602 }
603 else
604 {
605 /* Resolve relative path */
606 canonical_path = g_canonicalize_filename (resolved_identifier, "/etc");
607 g_free (resolved_identifier);
608 resolved_identifier = g_steal_pointer (&canonical_path);
609 }
610
611 tzdir = g_getenv ("TZDIR");
612 if (tzdir == NULL)
613 tzdir = zone_info_base_dir ();
614
615 /* Strip the prefix and slashes if possible. */
616 if (g_str_has_prefix (resolved_identifier, tzdir))
617 {
618 prefix_len = strlen (tzdir);
619 while (*(resolved_identifier + prefix_len) == '/')
620 prefix_len++;
621 }
622
623 if (prefix_len > 0)
624 memmove (resolved_identifier, resolved_identifier + prefix_len,
625 strlen (resolved_identifier) - prefix_len + 1 /* nul terminator */);
626
627 g_assert (resolved_identifier != NULL);
628
629 out:
630 g_free (canonical_path);
631
632 return resolved_identifier;
633 }
634
635 static GBytes*
636 zone_info_unix (const gchar *identifier,
637 const gchar *resolved_identifier)
638 {
639 gchar *filename = NULL;
640 GMappedFile *file = NULL;
641 GBytes *zoneinfo = NULL;
642 const gchar *tzdir;
643
644 tzdir = g_getenv ("TZDIR");
645 if (tzdir == NULL)
646 tzdir = zone_info_base_dir ();
647
648 /* identifier can be a relative or absolute path name;
649 if relative, it is interpreted starting from /usr/share/zoneinfo
650 while the POSIX standard says it should start with :,
651 glibc allows both syntaxes, so we should too */
652 if (identifier != NULL)
653 {
654 if (*identifier == ':')
655 identifier ++;
656
657 if (g_path_is_absolute (identifier))
658 filename = g_strdup (identifier);
659 else
660 filename = g_build_filename (tzdir, identifier, NULL);
661 }
662 else
663 {
664 if (resolved_identifier == NULL)
665 goto out;
666
667 filename = g_strdup ("/etc/localtime");
668 }
669
670 file = g_mapped_file_new (filename, FALSE, NULL);
671 if (file != NULL)
672 {
673 zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
674 g_mapped_file_get_length (file),
675 (GDestroyNotify)g_mapped_file_unref,
676 g_mapped_file_ref (file));
677 g_mapped_file_unref (file);
678 }
679
680 g_assert (resolved_identifier != NULL);
681
682 out:
683 g_free (filename);
684
685 return zoneinfo;
686 }
687
688 static void
689 init_zone_from_iana_info (GTimeZone *gtz,
690 GBytes *zoneinfo,
691 gchar *identifier /* (transfer full) */)
692 {
693 gsize size;
694 guint index;
695 guint32 time_count, type_count;
696 guint8 *tz_transitions, *tz_type_index, *tz_ttinfo;
697 guint8 *tz_abbrs;
698 gsize timesize = sizeof (gint32);
699 gconstpointer header_data = g_bytes_get_data (zoneinfo, &size);
700 const gchar *data = header_data;
701 const struct tzhead *header = header_data;
702 GTimeZone *footertz = NULL;
703 guint extra_time_count = 0, extra_type_count = 0;
704 gint64 last_explicit_transition_time = 0;
705
706 g_return_if_fail (size >= sizeof (struct tzhead) &&
707 memcmp (header, "TZif", 4) == 0);
708
709 /* FIXME: Handle invalid TZif files better (Issue#1088). */
710
711 if (header->tzh_version >= '2')
712 {
713 /* Skip ahead to the newer 64-bit data if it's available. */
714 header = (const struct tzhead *)
715 (((const gchar *) (header + 1)) +
716 guint32_from_be(header->tzh_ttisgmtcnt) +
717 guint32_from_be(header->tzh_ttisstdcnt) +
718 8 * guint32_from_be(header->tzh_leapcnt) +
719 5 * guint32_from_be(header->tzh_timecnt) +
720 6 * guint32_from_be(header->tzh_typecnt) +
721 guint32_from_be(header->tzh_charcnt));
722 timesize = sizeof (gint64);
723 }
724 time_count = guint32_from_be(header->tzh_timecnt);
725 type_count = guint32_from_be(header->tzh_typecnt);
726
727 if (header->tzh_version >= '2')
728 {
729 const gchar *footer = (((const gchar *) (header + 1))
730 + guint32_from_be(header->tzh_ttisgmtcnt)
731 + guint32_from_be(header->tzh_ttisstdcnt)
732 + 12 * guint32_from_be(header->tzh_leapcnt)
733 + 9 * time_count
734 + 6 * type_count
735 + guint32_from_be(header->tzh_charcnt));
736 const gchar *footerlast;
737 size_t footerlen;
738 g_return_if_fail (footer <= data + size - 2 && footer[0] == '\n');
739 footerlast = memchr (footer + 1, '\n', data + size - (footer + 1));
740 g_return_if_fail (footerlast);
741 footerlen = footerlast + 1 - footer;
742 if (footerlen != 2)
743 {
744 footertz = parse_footertz (footer, footerlen);
745 g_return_if_fail (footertz);
746 extra_type_count = footertz->t_info->len;
747 extra_time_count = footertz->transitions->len;
748 }
749 }
750
751 tz_transitions = ((guint8 *) (header) + sizeof (*header));
752 tz_type_index = tz_transitions + timesize * time_count;
753 tz_ttinfo = tz_type_index + time_count;
754 tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
755
756 gtz->name = g_steal_pointer (&identifier);
757 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
758 type_count + extra_type_count);
759 gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
760 time_count + extra_time_count);
761
762 for (index = 0; index < type_count; index++)
763 {
764 TransitionInfo t_info;
765 struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
766 t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
767 t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
768 t_info.abbrev = g_strdup ((gchar *) &tz_abbrs[info.tt_abbrind]);
769 g_array_append_val (gtz->t_info, t_info);
770 }
771
772 for (index = 0; index < time_count; index++)
773 {
774 Transition trans;
775 if (header->tzh_version >= '2')
776 trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
777 else
778 trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
779 last_explicit_transition_time = trans.time;
780 trans.info_index = tz_type_index[index];
781 g_assert (trans.info_index >= 0);
782 g_assert ((guint) trans.info_index < gtz->t_info->len);
783 g_array_append_val (gtz->transitions, trans);
784 }
785
786 if (footertz)
787 {
788 /* Append footer time types. Don't bother to coalesce
789 duplicates with existing time types. */
790 for (index = 0; index < extra_type_count; index++)
791 {
792 TransitionInfo t_info;
793 TransitionInfo *footer_t_info
794 = &g_array_index (footertz->t_info, TransitionInfo, index);
795 t_info.gmt_offset = footer_t_info->gmt_offset;
796 t_info.is_dst = footer_t_info->is_dst;
797 t_info.abbrev = g_steal_pointer (&footer_t_info->abbrev);
798 g_array_append_val (gtz->t_info, t_info);
799 }
800
801 /* Append footer transitions that follow the last explicit
802 transition. */
803 for (index = 0; index < extra_time_count; index++)
804 {
805 Transition *footer_transition
806 = &g_array_index (footertz->transitions, Transition, index);
807 if (time_count <= 0
808 || last_explicit_transition_time < footer_transition->time)
809 {
810 Transition trans;
811 trans.time = footer_transition->time;
812 trans.info_index = type_count + footer_transition->info_index;
813 g_array_append_val (gtz->transitions, trans);
814 }
815 }
816
817 g_time_zone_unref (footertz);
818 }
819 }
820
821 #elif defined (G_OS_WIN32)
822
823 static void
824 copy_windows_systemtime (SYSTEMTIME *s_time, TimeZoneDate *tzdate)
825 {
826 tzdate->offset
827 = s_time->wHour * 3600 + s_time->wMinute * 60 + s_time->wSecond;
828 tzdate->mon = s_time->wMonth;
829 tzdate->year = s_time->wYear;
830 tzdate->wday = s_time->wDayOfWeek ? s_time->wDayOfWeek : 7;
831
832 if (s_time->wYear)
833 {
834 tzdate->mday = s_time->wDay;
835 tzdate->wday = 0;
836 }
837 else
838 tzdate->week = s_time->wDay;
839 }
840
841 /* UTC = local time + bias while local time = UTC + offset */
842 static gboolean
843 rule_from_windows_time_zone_info (TimeZoneRule *rule,
844 TIME_ZONE_INFORMATION *tzi)
845 {
846 gchar *std_name, *dlt_name;
847
848 std_name = g_utf16_to_utf8 ((gunichar2 *)tzi->StandardName, -1, NULL, NULL, NULL);
849 if (std_name == NULL)
850 return FALSE;
851
852 dlt_name = g_utf16_to_utf8 ((gunichar2 *)tzi->DaylightName, -1, NULL, NULL, NULL);
853 if (dlt_name == NULL)
854 {
855 g_free (std_name);
856 return FALSE;
857 }
858
859 /* Set offset */
860 if (tzi->StandardDate.wMonth)
861 {
862 rule->std_offset = -(tzi->Bias + tzi->StandardBias) * 60;
863 rule->dlt_offset = -(tzi->Bias + tzi->DaylightBias) * 60;
864 copy_windows_systemtime (&(tzi->DaylightDate), &(rule->dlt_start));
865
866 copy_windows_systemtime (&(tzi->StandardDate), &(rule->dlt_end));
867 }
868
869 else
870 {
871 rule->std_offset = -tzi->Bias * 60;
872 rule->dlt_start.mon = 0;
873 }
874 strncpy (rule->std_name, std_name, NAME_SIZE - 1);
875 strncpy (rule->dlt_name, dlt_name, NAME_SIZE - 1);
876
877 g_free (std_name);
878 g_free (dlt_name);
879
880 return TRUE;
881 }
882
883 static gchar*
884 windows_default_tzname (void)
885 {
886 const gunichar2 *subkey =
887 L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
888 HKEY key;
889 gchar *key_name = NULL;
890 gunichar2 *key_name_w = NULL;
891 if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey, 0,
892 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
893 {
894 DWORD size = 0;
895 if (RegQueryValueExW (key, L"TimeZoneKeyName", NULL, NULL,
896 NULL, &size) == ERROR_SUCCESS)
897 {
898 key_name_w = g_malloc ((gint)size);
899
900 if (key_name_w == NULL ||
901 RegQueryValueExW (key, L"TimeZoneKeyName", NULL, NULL,
902 (LPBYTE)key_name_w, &size) != ERROR_SUCCESS)
903 {
904 g_free (key_name_w);
905 key_name = NULL;
906 }
907 else
908 key_name = g_utf16_to_utf8 (key_name_w, -1, NULL, NULL, NULL);
909 }
910 RegCloseKey (key);
911 }
912 return key_name;
913 }
914
915 typedef struct
916 {
917 LONG Bias;
918 LONG StandardBias;
919 LONG DaylightBias;
920 SYSTEMTIME StandardDate;
921 SYSTEMTIME DaylightDate;
922 } RegTZI;
923
924 static void
925 system_time_copy (SYSTEMTIME *orig, SYSTEMTIME *target)
926 {
927 g_return_if_fail (orig != NULL);
928 g_return_if_fail (target != NULL);
929
930 target->wYear = orig->wYear;
931 target->wMonth = orig->wMonth;
932 target->wDayOfWeek = orig->wDayOfWeek;
933 target->wDay = orig->wDay;
934 target->wHour = orig->wHour;
935 target->wMinute = orig->wMinute;
936 target->wSecond = orig->wSecond;
937 target->wMilliseconds = orig->wMilliseconds;
938 }
939
940 static void
941 register_tzi_to_tzi (RegTZI *reg, TIME_ZONE_INFORMATION *tzi)
942 {
943 g_return_if_fail (reg != NULL);
944 g_return_if_fail (tzi != NULL);
945 tzi->Bias = reg->Bias;
946 system_time_copy (&(reg->StandardDate), &(tzi->StandardDate));
947 tzi->StandardBias = reg->StandardBias;
948 system_time_copy (&(reg->DaylightDate), &(tzi->DaylightDate));
949 tzi->DaylightBias = reg->DaylightBias;
950 }
951
952 static guint
953 rules_from_windows_time_zone (const gchar *identifier,
954 const gchar *resolved_identifier,
955 TimeZoneRule **rules)
956 {
957 HKEY key;
958 gchar *subkey = NULL;
959 gchar *subkey_dynamic = NULL;
960 const gchar *key_name;
961 const gchar *reg_key =
962 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
963 TIME_ZONE_INFORMATION tzi;
964 DWORD size;
965 guint rules_num = 0;
966 RegTZI regtzi = { 0 }, regtzi_prev;
967 WCHAR winsyspath[MAX_PATH];
968 gunichar2 *subkey_w, *subkey_dynamic_w;
969
970 subkey_dynamic_w = NULL;
971
972 if (GetSystemDirectoryW (winsyspath, MAX_PATH) == 0)
973 return 0;
974
975 g_assert (rules != NULL);
976
977 *rules = NULL;
978 key_name = NULL;
979
980 if (!identifier)
981 key_name = resolved_identifier;
982 else
983 key_name = identifier;
984
985 if (!key_name)
986 return 0;
987
988 subkey = g_strconcat (reg_key, key_name, NULL);
989 subkey_w = g_utf8_to_utf16 (subkey, -1, NULL, NULL, NULL);
990 if (subkey_w == NULL)
991 goto utf16_conv_failed;
992
993 subkey_dynamic = g_strconcat (subkey, "\\Dynamic DST", NULL);
994 subkey_dynamic_w = g_utf8_to_utf16 (subkey_dynamic, -1, NULL, NULL, NULL);
995 if (subkey_dynamic_w == NULL)
996 goto utf16_conv_failed;
997
998 if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_w, 0,
999 KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
1000 goto utf16_conv_failed;
1001
1002 size = sizeof tzi.StandardName;
1003
1004 /* use RegLoadMUIStringW() to query MUI_Std from the registry if possible, otherwise
1005 fallback to querying Std */
1006 if (RegLoadMUIStringW (key, L"MUI_Std", tzi.StandardName,
1007 size, &size, 0, winsyspath) != ERROR_SUCCESS)
1008 {
1009 size = sizeof tzi.StandardName;
1010 if (RegQueryValueExW (key, L"Std", NULL, NULL,
1011 (LPBYTE)&(tzi.StandardName), &size) != ERROR_SUCCESS)
1012 goto registry_failed;
1013 }
1014
1015 size = sizeof tzi.DaylightName;
1016
1017 /* use RegLoadMUIStringW() to query MUI_Dlt from the registry if possible, otherwise
1018 fallback to querying Dlt */
1019 if (RegLoadMUIStringW (key, L"MUI_Dlt", tzi.DaylightName,
1020 size, &size, 0, winsyspath) != ERROR_SUCCESS)
1021 {
1022 size = sizeof tzi.DaylightName;
1023 if (RegQueryValueExW (key, L"Dlt", NULL, NULL,
1024 (LPBYTE)&(tzi.DaylightName), &size) != ERROR_SUCCESS)
1025 goto registry_failed;
1026 }
1027
1028 RegCloseKey (key);
1029 if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_dynamic_w, 0,
1030 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1031 {
1032 DWORD i, first, last, year;
1033 wchar_t s[12];
1034
1035 size = sizeof first;
1036 if (RegQueryValueExW (key, L"FirstEntry", NULL, NULL,
1037 (LPBYTE) &first, &size) != ERROR_SUCCESS)
1038 goto registry_failed;
1039
1040 size = sizeof last;
1041 if (RegQueryValueExW (key, L"LastEntry", NULL, NULL,
1042 (LPBYTE) &last, &size) != ERROR_SUCCESS)
1043 goto registry_failed;
1044
1045 rules_num = last - first + 2;
1046 *rules = g_new0 (TimeZoneRule, rules_num);
1047
1048 for (year = first, i = 0; *rules != NULL && year <= last; year++)
1049 {
1050 gboolean failed = FALSE;
1051 swprintf_s (s, 11, L"%d", year);
1052
1053 if (!failed)
1054 {
1055 size = sizeof regtzi;
1056 if (RegQueryValueExW (key, s, NULL, NULL,
1057 (LPBYTE) ®tzi, &size) != ERROR_SUCCESS)
1058 failed = TRUE;
1059 }
1060
1061 if (failed)
1062 {
1063 g_free (*rules);
1064 *rules = NULL;
1065 break;
1066 }
1067
1068 if (year > first && memcmp (®tzi_prev, ®tzi, sizeof regtzi) == 0)
1069 continue;
1070 else
1071 memcpy (®tzi_prev, ®tzi, sizeof regtzi);
1072
1073 register_tzi_to_tzi (®tzi, &tzi);
1074
1075 if (!rule_from_windows_time_zone_info (&(*rules)[i], &tzi))
1076 {
1077 g_free (*rules);
1078 *rules = NULL;
1079 break;
1080 }
1081
1082 (*rules)[i++].start_year = year;
1083 }
1084
1085 rules_num = i + 1;
1086
1087 registry_failed:
1088 RegCloseKey (key);
1089 }
1090 else if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_w, 0,
1091 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
1092 {
1093 size = sizeof regtzi;
1094 if (RegQueryValueExW (key, L"TZI", NULL, NULL,
1095 (LPBYTE) ®tzi, &size) == ERROR_SUCCESS)
1096 {
1097 rules_num = 2;
1098 *rules = g_new0 (TimeZoneRule, 2);
1099 register_tzi_to_tzi (®tzi, &tzi);
1100
1101 if (!rule_from_windows_time_zone_info (&(*rules)[0], &tzi))
1102 {
1103 g_free (*rules);
1104 *rules = NULL;
1105 }
1106 }
1107
1108 RegCloseKey (key);
1109 }
1110
1111 utf16_conv_failed:
1112 g_free (subkey_dynamic_w);
1113 g_free (subkey_dynamic);
1114 g_free (subkey_w);
1115 g_free (subkey);
1116
1117 if (*rules)
1118 {
1119 (*rules)[0].start_year = MIN_TZYEAR;
1120 if ((*rules)[rules_num - 2].start_year < MAX_TZYEAR)
1121 (*rules)[rules_num - 1].start_year = MAX_TZYEAR;
1122 else
1123 (*rules)[rules_num - 1].start_year = (*rules)[rules_num - 2].start_year + 1;
1124
1125 return rules_num;
1126 }
1127
1128 return 0;
1129 }
1130
1131 #endif
1132
1133 static void
1134 find_relative_date (TimeZoneDate *buffer)
1135 {
1136 guint wday;
1137 GDate date;
1138 g_date_clear (&date, 1);
1139 wday = buffer->wday;
1140
1141 /* Get last day if last is needed, first day otherwise */
1142 if (buffer->mon == 13 || buffer->mon == 14) /* Julian Date */
1143 {
1144 g_date_set_dmy (&date, 1, 1, buffer->year);
1145 if (wday >= 59 && buffer->mon == 13 && g_date_is_leap_year (buffer->year))
1146 g_date_add_days (&date, wday);
1147 else
1148 g_date_add_days (&date, wday - 1);
1149 buffer->mon = (int) g_date_get_month (&date);
1150 buffer->mday = (int) g_date_get_day (&date);
1151 buffer->wday = 0;
1152 }
1153 else /* M.W.D */
1154 {
1155 guint days;
1156 guint days_in_month = g_date_get_days_in_month (buffer->mon, buffer->year);
1157 GDateWeekday first_wday;
1158
1159 g_date_set_dmy (&date, 1, buffer->mon, buffer->year);
1160 first_wday = g_date_get_weekday (&date);
1161
1162 if ((guint) first_wday > wday)
1163 ++(buffer->week);
1164 /* week is 1 <= w <= 5, we need 0-based */
1165 days = 7 * (buffer->week - 1) + wday - first_wday;
1166
1167 /* "days" is a 0-based offset from the 1st of the month.
1168 * Adding days == days_in_month would bring us into the next month,
1169 * hence the ">=" instead of just ">".
1170 */
1171 while (days >= days_in_month)
1172 days -= 7;
1173
1174 g_date_add_days (&date, days);
1175
1176 buffer->mday = g_date_get_day (&date);
1177 }
1178 }
1179
1180 /* Offset is previous offset of local time. Returns 0 if month is 0 */
1181 static gint64
1182 boundary_for_year (TimeZoneDate *boundary,
1183 gint year,
1184 gint32 offset)
1185 {
1186 TimeZoneDate buffer;
1187 GDate date;
1188 const guint64 unix_epoch_start = 719163L;
1189 const guint64 seconds_per_day = 86400L;
1190
1191 if (!boundary->mon)
1192 return 0;
1193 buffer = *boundary;
1194
1195 if (boundary->year == 0)
1196 {
1197 buffer.year = year;
1198
1199 if (buffer.wday)
1200 find_relative_date (&buffer);
1201 }
1202
1203 g_assert (buffer.year == year);
1204 g_date_clear (&date, 1);
1205 g_date_set_dmy (&date, buffer.mday, buffer.mon, buffer.year);
1206 return ((g_date_get_julian (&date) - unix_epoch_start) * seconds_per_day +
1207 buffer.offset - offset);
1208 }
1209
1210 static void
1211 fill_transition_info_from_rule (TransitionInfo *info,
1212 TimeZoneRule *rule,
1213 gboolean is_dst)
1214 {
1215 gint offset = is_dst ? rule->dlt_offset : rule->std_offset;
1216 gchar *name = is_dst ? rule->dlt_name : rule->std_name;
1217
1218 info->gmt_offset = offset;
1219 info->is_dst = is_dst;
1220
1221 if (name)
1222 info->abbrev = g_strdup (name);
1223
1224 else
1225 info->abbrev = g_strdup_printf ("%+03d%02d",
1226 (int) offset / 3600,
1227 (int) abs (offset / 60) % 60);
1228 }
1229
1230 static void
1231 init_zone_from_rules (GTimeZone *gtz,
1232 TimeZoneRule *rules,
1233 guint rules_num,
1234 gchar *identifier /* (transfer full) */)
1235 {
1236 guint type_count = 0, trans_count = 0, info_index = 0;
1237 guint ri; /* rule index */
1238 gboolean skip_first_std_trans = TRUE;
1239 gint32 last_offset;
1240
1241 type_count = 0;
1242 trans_count = 0;
1243
1244 /* Last rule only contains max year */
1245 for (ri = 0; ri < rules_num - 1; ri++)
1246 {
1247 if (rules[ri].dlt_start.mon || rules[ri].dlt_end.mon)
1248 {
1249 guint rulespan = (rules[ri + 1].start_year - rules[ri].start_year);
1250 guint transitions = rules[ri].dlt_start.mon > 0 ? 1 : 0;
1251 transitions += rules[ri].dlt_end.mon > 0 ? 1 : 0;
1252 type_count += rules[ri].dlt_start.mon > 0 ? 2 : 1;
1253 trans_count += transitions * rulespan;
1254 }
1255 else
1256 type_count++;
1257 }
1258
1259 gtz->name = g_steal_pointer (&identifier);
1260 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
1261 gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
1262
1263 last_offset = rules[0].std_offset;
1264
1265 for (ri = 0; ri < rules_num - 1; ri++)
1266 {
1267 if ((rules[ri].std_offset || rules[ri].dlt_offset) &&
1268 rules[ri].dlt_start.mon == 0 && rules[ri].dlt_end.mon == 0)
1269 {
1270 TransitionInfo std_info;
1271 /* Standard */
1272 fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
1273 g_array_append_val (gtz->t_info, std_info);
1274
1275 if (ri > 0 &&
1276 ((rules[ri - 1].dlt_start.mon > 12 &&
1277 rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
1278 rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
1279 {
1280 /* The previous rule was a southern hemisphere rule that
1281 starts the year with DST, so we need to add a
1282 transition to return to standard time */
1283 guint year = rules[ri].start_year;
1284 gint64 std_time = boundary_for_year (&rules[ri].dlt_end,
1285 year, last_offset);
1286 Transition std_trans = {std_time, info_index};
1287 g_array_append_val (gtz->transitions, std_trans);
1288
1289 }
1290 last_offset = rules[ri].std_offset;
1291 ++info_index;
1292 skip_first_std_trans = TRUE;
1293 }
1294 else
1295 {
1296 const guint start_year = rules[ri].start_year;
1297 const guint end_year = rules[ri + 1].start_year;
1298 gboolean dlt_first;
1299 guint year;
1300 TransitionInfo std_info, dlt_info;
1301 if (rules[ri].dlt_start.mon > 12)
1302 dlt_first = rules[ri].dlt_start.wday > rules[ri].dlt_end.wday;
1303 else
1304 dlt_first = rules[ri].dlt_start.mon > rules[ri].dlt_end.mon;
1305 /* Standard rules are always even, because before the first
1306 transition is always standard time, and 0 is even. */
1307 fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
1308 fill_transition_info_from_rule (&dlt_info, &(rules[ri]), TRUE);
1309
1310 g_array_append_val (gtz->t_info, std_info);
1311 g_array_append_val (gtz->t_info, dlt_info);
1312
1313 /* Transition dates. We hope that a year which ends daylight
1314 time in a southern-hemisphere country (i.e., one that
1315 begins the year in daylight time) will include a rule
1316 which has only a dlt_end. */
1317 for (year = start_year; year < end_year; year++)
1318 {
1319 gint32 dlt_offset = (dlt_first ? last_offset :
1320 rules[ri].dlt_offset);
1321 gint32 std_offset = (dlt_first ? rules[ri].std_offset :
1322 last_offset);
1323 /* NB: boundary_for_year returns 0 if mon == 0 */
1324 gint64 std_time = boundary_for_year (&rules[ri].dlt_end,
1325 year, dlt_offset);
1326 gint64 dlt_time = boundary_for_year (&rules[ri].dlt_start,
1327 year, std_offset);
1328 Transition std_trans = {std_time, info_index};
1329 Transition dlt_trans = {dlt_time, info_index + 1};
1330 last_offset = (dlt_first ? rules[ri].dlt_offset :
1331 rules[ri].std_offset);
1332 if (dlt_first)
1333 {
1334 if (skip_first_std_trans)
1335 skip_first_std_trans = FALSE;
1336 else if (std_time)
1337 g_array_append_val (gtz->transitions, std_trans);
1338 if (dlt_time)
1339 g_array_append_val (gtz->transitions, dlt_trans);
1340 }
1341 else
1342 {
1343 if (dlt_time)
1344 g_array_append_val (gtz->transitions, dlt_trans);
1345 if (std_time)
1346 g_array_append_val (gtz->transitions, std_trans);
1347 }
1348 }
1349
1350 info_index += 2;
1351 }
1352 }
1353 if (ri > 0 &&
1354 ((rules[ri - 1].dlt_start.mon > 12 &&
1355 rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
1356 rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
1357 {
1358 /* The previous rule was a southern hemisphere rule that
1359 starts the year with DST, so we need to add a
1360 transition to return to standard time */
1361 TransitionInfo info;
1362 guint year = rules[ri].start_year;
1363 Transition trans;
1364 fill_transition_info_from_rule (&info, &(rules[ri - 1]), FALSE);
1365 g_array_append_val (gtz->t_info, info);
1366 trans.time = boundary_for_year (&rules[ri - 1].dlt_end,
1367 year, last_offset);
1368 trans.info_index = info_index;
1369 g_array_append_val (gtz->transitions, trans);
1370 }
1371 }
1372
1373 /*
1374 * parses date[/time] for parsing TZ environment variable
1375 *
1376 * date is either Mm.w.d, Jn or N
1377 * - m is 1 to 12
1378 * - w is 1 to 5
1379 * - d is 0 to 6
1380 * - n is 1 to 365
1381 * - N is 0 to 365
1382 *
1383 * time is either h or hh[[:]mm[[[:]ss]]]
1384 * - h[h] is 0 to 24
1385 * - mm is 00 to 59
1386 * - ss is 00 to 59
1387 */
1388 static gboolean
1389 parse_mwd_boundary (gchar **pos, TimeZoneDate *boundary)
1390 {
1391 gint month, week, day;
1392
1393 if (**pos == '\0' || **pos < '0' || '9' < **pos)
1394 return FALSE;
1395
1396 month = *(*pos)++ - '0';
1397
1398 if ((month == 1 && **pos >= '0' && '2' >= **pos) ||
1399 (month == 0 && **pos >= '0' && '9' >= **pos))
1400 {
1401 month *= 10;
1402 month += *(*pos)++ - '0';
1403 }
1404
1405 if (*(*pos)++ != '.' || month == 0)
1406 return FALSE;
1407
1408 if (**pos == '\0' || **pos < '1' || '5' < **pos)
1409 return FALSE;
1410
1411 week = *(*pos)++ - '0';
1412
1413 if (*(*pos)++ != '.')
1414 return FALSE;
1415
1416 if (**pos == '\0' || **pos < '0' || '6' < **pos)
1417 return FALSE;
1418
1419 day = *(*pos)++ - '0';
1420
1421 if (!day)
1422 day += 7;
1423
1424 boundary->year = 0;
1425 boundary->mon = month;
1426 boundary->week = week;
1427 boundary->wday = day;
1428 return TRUE;
1429 }
1430
1431 /*
1432 * This parses two slightly different ways of specifying
1433 * the Julian day:
1434 *
1435 * - ignore_leap == TRUE
1436 *
1437 * Jn This specifies the Julian day with n between 1 and 365. Leap days
1438 * are not counted. In this format, February 29 can't be represented;
1439 * February 28 is day 59, and March 1 is always day 60.
1440 *
1441 * - ignore_leap == FALSE
1442 *
1443 * n This specifies the zero-based Julian day with n between 0 and 365.
1444 * February 29 is counted in leap years.
1445 */
1446 static gboolean
1447 parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
1448 gboolean ignore_leap)
1449 {
1450 gint day = 0;
1451 GDate date;
1452
1453 while (**pos >= '0' && '9' >= **pos)
1454 {
1455 day *= 10;
1456 day += *(*pos)++ - '0';
1457 }
1458
1459 if (ignore_leap)
1460 {
1461 if (day < 1 || 365 < day)
1462 return FALSE;
1463 if (day >= 59)
1464 day++;
1465 }
1466 else
1467 {
1468 if (day < 0 || 365 < day)
1469 return FALSE;
1470 /* GDate wants day in range 1->366 */
1471 day++;
1472 }
1473
1474 g_date_clear (&date, 1);
1475 g_date_set_julian (&date, day);
1476 boundary->year = 0;
1477 boundary->mon = (int) g_date_get_month (&date);
1478 boundary->mday = (int) g_date_get_day (&date);
1479 boundary->wday = 0;
1480
1481 return TRUE;
1482 }
1483
1484 static gboolean
1485 parse_tz_boundary (const gchar *identifier,
1486 TimeZoneDate *boundary)
1487 {
1488 gchar *pos;
1489
1490 pos = (gchar*)identifier;
1491 /* Month-week-weekday */
1492 if (*pos == 'M')
1493 {
1494 ++pos;
1495 if (!parse_mwd_boundary (&pos, boundary))
1496 return FALSE;
1497 }
1498 /* Julian date which ignores Feb 29 in leap years */
1499 else if (*pos == 'J')
1500 {
1501 ++pos;
1502 if (!parse_julian_boundary (&pos, boundary, TRUE))
1503 return FALSE ;
1504 }
1505 /* Julian date which counts Feb 29 in leap years */
1506 else if (*pos >= '0' && '9' >= *pos)
1507 {
1508 if (!parse_julian_boundary (&pos, boundary, FALSE))
1509 return FALSE;
1510 }
1511 else
1512 return FALSE;
1513
1514 /* Time */
1515
1516 if (*pos == '/')
1517 return parse_constant_offset (pos + 1, &boundary->offset, TRUE);
1518 else
1519 {
1520 boundary->offset = 2 * 60 * 60;
1521 return *pos == '\0';
1522 }
1523 }
1524
1525 static guint
1526 create_ruleset_from_rule (TimeZoneRule **rules, TimeZoneRule *rule)
1527 {
1528 *rules = g_new0 (TimeZoneRule, 2);
1529
1530 (*rules)[0].start_year = MIN_TZYEAR;
1531 (*rules)[1].start_year = MAX_TZYEAR;
1532
1533 (*rules)[0].std_offset = -rule->std_offset;
1534 (*rules)[0].dlt_offset = -rule->dlt_offset;
1535 (*rules)[0].dlt_start = rule->dlt_start;
1536 (*rules)[0].dlt_end = rule->dlt_end;
1537 strcpy ((*rules)[0].std_name, rule->std_name);
1538 strcpy ((*rules)[0].dlt_name, rule->dlt_name);
1539 return 2;
1540 }
1541
1542 static gboolean
1543 parse_offset (gchar **pos, gint32 *target)
1544 {
1545 gchar *buffer;
1546 gchar *target_pos = *pos;
1547 gboolean ret;
1548
1549 while (**pos == '+' || **pos == '-' || **pos == ':' ||
1550 (**pos >= '0' && '9' >= **pos))
1551 ++(*pos);
1552
1553 buffer = g_strndup (target_pos, *pos - target_pos);
1554 ret = parse_constant_offset (buffer, target, FALSE);
1555 g_free (buffer);
1556
1557 return ret;
1558 }
1559
1560 static gboolean
1561 parse_identifier_boundary (gchar **pos, TimeZoneDate *target)
1562 {
1563 gchar *buffer;
1564 gchar *target_pos = *pos;
1565 gboolean ret;
1566
1567 while (**pos != ',' && **pos != '\0')
1568 ++(*pos);
1569 buffer = g_strndup (target_pos, *pos - target_pos);
1570 ret = parse_tz_boundary (buffer, target);
1571 g_free (buffer);
1572
1573 return ret;
1574 }
1575
1576 static gboolean
1577 set_tz_name (gchar **pos, gchar *buffer, guint size)
1578 {
1579 gboolean quoted = **pos == '<';
1580 gchar *name_pos = *pos;
1581 guint len;
1582
1583 g_assert (size != 0);
1584
1585 if (quoted)
1586 {
1587 name_pos++;
1588 do
1589 ++(*pos);
1590 while (g_ascii_isalnum (**pos) || **pos == '-' || **pos == '+');
1591 if (**pos != '>')
1592 return FALSE;
1593 }
1594 else
1595 while (g_ascii_isalpha (**pos))
1596 ++(*pos);
1597
1598 /* Name should be three or more characters */
1599 /* FIXME: Should return FALSE if the name is too long.
1600 This should simplify code later in this function. */
1601 if (*pos - name_pos < 3)
1602 return FALSE;
1603
1604 memset (buffer, 0, size);
1605 /* name_pos isn't 0-terminated, so we have to limit the length expressly */
1606 len = (guint) (*pos - name_pos) > size - 1 ? size - 1 : (guint) (*pos - name_pos);
1607 strncpy (buffer, name_pos, len);
1608 *pos += quoted;
1609 return TRUE;
1610 }
1611
1612 static gboolean
1613 parse_identifier_boundaries (gchar **pos, TimeZoneRule *tzr)
1614 {
1615 if (*(*pos)++ != ',')
1616 return FALSE;
1617
1618 /* Start date */
1619 if (!parse_identifier_boundary (pos, &(tzr->dlt_start)) || *(*pos)++ != ',')
1620 return FALSE;
1621
1622 /* End date */
1623 if (!parse_identifier_boundary (pos, &(tzr->dlt_end)))
1624 return FALSE;
1625 return TRUE;
1626 }
1627
1628 /*
1629 * Creates an array of TimeZoneRule from a TZ environment variable
1630 * type of identifier. Should free rules afterwards
1631 */
1632 static guint
1633 rules_from_identifier (const gchar *identifier,
1634 TimeZoneRule **rules)
1635 {
1636 gchar *pos;
1637 TimeZoneRule tzr;
1638
1639 g_assert (rules != NULL);
1640
1641 *rules = NULL;
1642
1643 if (!identifier)
1644 return 0;
1645
1646 pos = (gchar*)identifier;
1647 memset (&tzr, 0, sizeof (tzr));
1648 /* Standard offset */
1649 if (!(set_tz_name (&pos, tzr.std_name, NAME_SIZE)) ||
1650 !parse_offset (&pos, &(tzr.std_offset)))
1651 return 0;
1652
1653 if (*pos == 0)
1654 {
1655 return create_ruleset_from_rule (rules, &tzr);
1656 }
1657
1658 /* Format 2 */
1659 if (!(set_tz_name (&pos, tzr.dlt_name, NAME_SIZE)))
1660 return 0;
1661 parse_offset (&pos, &(tzr.dlt_offset));
1662 if (tzr.dlt_offset == 0) /* No daylight offset given, assume it's 1
1663 hour earlier that standard */
1664 tzr.dlt_offset = tzr.std_offset - 3600;
1665 if (*pos == '\0')
1666 #ifdef G_OS_WIN32
1667 /* Windows allows us to use the US DST boundaries if they're not given */
1668 {
1669 guint i, rules_num = 0;
1670
1671 /* Use US rules, Windows' default is Pacific Standard Time */
1672 if ((rules_num = rules_from_windows_time_zone ("Pacific Standard Time",
1673 NULL,
1674 rules)))
1675 {
1676 for (i = 0; i < rules_num - 1; i++)
1677 {
1678 (*rules)[i].std_offset = - tzr.std_offset;
1679 (*rules)[i].dlt_offset = - tzr.dlt_offset;
1680 strcpy ((*rules)[i].std_name, tzr.std_name);
1681 strcpy ((*rules)[i].dlt_name, tzr.dlt_name);
1682 }
1683
1684 return rules_num;
1685 }
1686 else
1687 return 0;
1688 }
1689 #else
1690 return 0;
1691 #endif
1692 /* Start and end required (format 2) */
1693 if (!parse_identifier_boundaries (&pos, &tzr))
1694 return 0;
1695
1696 return create_ruleset_from_rule (rules, &tzr);
1697 }
1698
1699 #ifdef G_OS_UNIX
1700 static GTimeZone *
1701 parse_footertz (const gchar *footer, size_t footerlen)
1702 {
1703 gchar *tzstring = g_strndup (footer + 1, footerlen - 2);
1704 GTimeZone *footertz = NULL;
1705
1706 /* FIXME: The allocation for tzstring could be avoided by
1707 passing a gsize identifier_len argument to rules_from_identifier
1708 and changing the code in that function to stop assuming that
1709 identifier is nul-terminated. */
1710 TimeZoneRule *rules;
1711 guint rules_num = rules_from_identifier (tzstring, &rules);
1712
1713 g_free (tzstring);
1714 if (rules_num > 1)
1715 {
1716 footertz = g_slice_new0 (GTimeZone);
1717 init_zone_from_rules (footertz, rules, rules_num, NULL);
1718 footertz->ref_count++;
1719 }
1720 g_free (rules);
1721 return footertz;
1722 }
1723 #endif
1724
1725 /* Construction {{{1 */
1726 /**
1727 * g_time_zone_new:
1728 * @identifier: (nullable): a timezone identifier
1729 *
1730 * A version of g_time_zone_new_identifier() which returns the UTC time zone
1731 * if @identifier could not be parsed or loaded.
1732 *
1733 * If you need to check whether @identifier was loaded successfully, use
1734 * g_time_zone_new_identifier().
1735 *
1736 * Returns: (transfer full) (not nullable): the requested timezone
1737 * Deprecated: 2.68: Use g_time_zone_new_identifier() instead, as it provides
1738 * error reporting. Change your code to handle a potentially %NULL return
1739 * value.
1740 *
1741 * Since: 2.26
1742 **/
1743 GTimeZone *
1744 g_time_zone_new (const gchar *identifier)
1745 {
1746 GTimeZone *tz = g_time_zone_new_identifier (identifier);
1747
1748 /* Always fall back to UTC. */
1749 if (tz == NULL)
1750 tz = g_time_zone_new_utc ();
1751
1752 g_assert (tz != NULL);
1753
1754 return g_steal_pointer (&tz);
1755 }
1756
1757 /**
1758 * g_time_zone_new_identifier:
1759 * @identifier: (nullable): a timezone identifier
1760 *
1761 * Creates a #GTimeZone corresponding to @identifier. If @identifier cannot be
1762 * parsed or loaded, %NULL is returned.
1763 *
1764 * @identifier can either be an RFC3339/ISO 8601 time offset or
1765 * something that would pass as a valid value for the `TZ` environment
1766 * variable (including %NULL).
1767 *
1768 * In Windows, @identifier can also be the unlocalized name of a time
1769 * zone for standard time, for example "Pacific Standard Time".
1770 *
1771 * Valid RFC3339 time offsets are `"Z"` (for UTC) or
1772 * `"±hh:mm"`. ISO 8601 additionally specifies
1773 * `"±hhmm"` and `"±hh"`. Offsets are
1774 * time values to be added to Coordinated Universal Time (UTC) to get
1775 * the local time.
1776 *
1777 * In UNIX, the `TZ` environment variable typically corresponds
1778 * to the name of a file in the zoneinfo database, an absolute path to a file
1779 * somewhere else, or a string in
1780 * "std offset [dst [offset],start[/time],end[/time]]" (POSIX) format.
1781 * There are no spaces in the specification. The name of standard
1782 * and daylight savings time zone must be three or more alphabetic
1783 * characters. Offsets are time values to be added to local time to
1784 * get Coordinated Universal Time (UTC) and should be
1785 * `"[±]hh[[:]mm[:ss]]"`. Dates are either
1786 * `"Jn"` (Julian day with n between 1 and 365, leap
1787 * years not counted), `"n"` (zero-based Julian day
1788 * with n between 0 and 365) or `"Mm.w.d"` (day d
1789 * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
1790 * 0 is a Sunday). Times are in local wall clock time, the default is
1791 * 02:00:00.
1792 *
1793 * In Windows, the "tzn[+|–]hh[:mm[:ss]][dzn]" format is used, but also
1794 * accepts POSIX format. The Windows format uses US rules for all time
1795 * zones; daylight savings time is 60 minutes behind the standard time
1796 * with date and time of change taken from Pacific Standard Time.
1797 * Offsets are time values to be added to the local time to get
1798 * Coordinated Universal Time (UTC).
1799 *
1800 * g_time_zone_new_local() calls this function with the value of the
1801 * `TZ` environment variable. This function itself is independent of
1802 * the value of `TZ`, but if @identifier is %NULL then `/etc/localtime`
1803 * will be consulted to discover the correct time zone on UNIX and the
1804 * registry will be consulted or GetTimeZoneInformation() will be used
1805 * to get the local time zone on Windows.
1806 *
1807 * If intervals are not available, only time zone rules from `TZ`
1808 * environment variable or other means, then they will be computed
1809 * from year 1900 to 2037. If the maximum year for the rules is
1810 * available and it is greater than 2037, then it will followed
1811 * instead.
1812 *
1813 * See
1814 * [RFC3339 §5.6](http://tools.ietf.org/html/rfc3339#section-5.6)
1815 * for a precise definition of valid RFC3339 time offsets
1816 * (the `time-offset` expansion) and ISO 8601 for the
1817 * full list of valid time offsets. See
1818 * [The GNU C Library manual](http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html)
1819 * for an explanation of the possible
1820 * values of the `TZ` environment variable. See
1821 * [Microsoft Time Zone Index Values](http://msdn.microsoft.com/en-us/library/ms912391%28v=winembedded.11%29.aspx)
1822 * for the list of time zones on Windows.
1823 *
1824 * You should release the return value by calling g_time_zone_unref()
1825 * when you are done with it.
1826 *
1827 * Returns: (transfer full) (nullable): the requested timezone, or %NULL on
1828 * failure
1829 * Since: 2.68
1830 */
1831 GTimeZone *
1832 g_time_zone_new_identifier (const gchar *identifier)
1833 {
1834 GTimeZone *tz = NULL;
1835 TimeZoneRule *rules;
1836 gint rules_num;
1837 gchar *resolved_identifier = NULL;
1838
1839 if (identifier)
1840 {
1841 G_LOCK (time_zones);
1842 if (time_zones == NULL)
1843 time_zones = g_hash_table_new (g_str_hash, g_str_equal);
1844
1845 tz = g_hash_table_lookup (time_zones, identifier);
1846 if (tz)
1847 {
1848 g_atomic_int_inc (&tz->ref_count);
1849 G_UNLOCK (time_zones);
1850 return tz;
1851 }
1852 else
1853 resolved_identifier = g_strdup (identifier);
1854 }
1855 else
1856 {
1857 G_LOCK (tz_default);
1858 #ifdef G_OS_UNIX
1859 resolved_identifier = zone_identifier_unix ();
1860 #elif defined (G_OS_WIN32)
1861 resolved_identifier = windows_default_tzname ();
1862 #endif
1863 if (tz_default)
1864 {
1865 /* Flush default if changed. If the identifier couldn’t be resolved,
1866 * we’re going to fall back to UTC eventually, so don’t clear out the
1867 * cache if it’s already UTC. */
1868 if (!(resolved_identifier == NULL && g_str_equal (tz_default->name, "UTC")) &&
1869 g_strcmp0 (tz_default->name, resolved_identifier) != 0)
1870 {
1871 g_clear_pointer (&tz_default, g_time_zone_unref);
1872 }
1873 else
1874 {
1875 tz = g_time_zone_ref (tz_default);
1876 G_UNLOCK (tz_default);
1877
1878 g_free (resolved_identifier);
1879 return tz;
1880 }
1881 }
1882 }
1883
1884 tz = g_slice_new0 (GTimeZone);
1885 tz->ref_count = 0;
1886
1887 zone_for_constant_offset (tz, identifier);
1888
1889 if (tz->t_info == NULL &&
1890 (rules_num = rules_from_identifier (identifier, &rules)))
1891 {
1892 init_zone_from_rules (tz, rules, rules_num, g_steal_pointer (&resolved_identifier));
1893 g_free (rules);
1894 }
1895
1896 if (tz->t_info == NULL)
1897 {
1898 #ifdef G_OS_UNIX
1899 GBytes *zoneinfo = zone_info_unix (identifier, resolved_identifier);
1900 if (zoneinfo != NULL)
1901 {
1902 init_zone_from_iana_info (tz, zoneinfo, g_steal_pointer (&resolved_identifier));
1903 g_bytes_unref (zoneinfo);
1904 }
1905 #elif defined (G_OS_WIN32)
1906 if ((rules_num = rules_from_windows_time_zone (identifier,
1907 resolved_identifier,
1908 &rules)))
1909 {
1910 init_zone_from_rules (tz, rules, rules_num, g_steal_pointer (&resolved_identifier));
1911 g_free (rules);
1912 }
1913 #endif
1914 }
1915
1916 #if defined (G_OS_WIN32)
1917 if (tz->t_info == NULL)
1918 {
1919 if (identifier == NULL)
1920 {
1921 TIME_ZONE_INFORMATION tzi;
1922
1923 if (GetTimeZoneInformation (&tzi) != TIME_ZONE_ID_INVALID)
1924 {
1925 rules = g_new0 (TimeZoneRule, 2);
1926
1927 if (rule_from_windows_time_zone_info (&rules[0], &tzi))
1928 {
1929 memset (rules[0].std_name, 0, NAME_SIZE);
1930 memset (rules[0].dlt_name, 0, NAME_SIZE);
1931
1932 rules[0].start_year = MIN_TZYEAR;
1933 rules[1].start_year = MAX_TZYEAR;
1934
1935 init_zone_from_rules (tz, rules, 2, g_steal_pointer (&resolved_identifier));
1936 }
1937
1938 g_free (rules);
1939 }
1940 }
1941 }
1942 #endif
1943
1944 g_free (resolved_identifier);
1945
1946 /* Failed to load the timezone. */
1947 if (tz->t_info == NULL)
1948 {
1949 g_slice_free (GTimeZone, tz);
1950
1951 if (identifier)
1952 G_UNLOCK (time_zones);
1953 else
1954 G_UNLOCK (tz_default);
1955
1956 return NULL;
1957 }
1958
1959 g_assert (tz->name != NULL);
1960 g_assert (tz->t_info != NULL);
1961
1962 if (identifier)
1963 g_hash_table_insert (time_zones, tz->name, tz);
1964 else if (tz->name)
1965 {
1966 /* Caching reference */
1967 g_atomic_int_inc (&tz->ref_count);
1968 tz_default = tz;
1969 }
1970
1971 g_atomic_int_inc (&tz->ref_count);
1972
1973 if (identifier)
1974 G_UNLOCK (time_zones);
1975 else
1976 G_UNLOCK (tz_default);
1977
1978 return tz;
1979 }
1980
1981 /**
1982 * g_time_zone_new_utc:
1983 *
1984 * Creates a #GTimeZone corresponding to UTC.
1985 *
1986 * This is equivalent to calling g_time_zone_new() with a value like
1987 * "Z", "UTC", "+00", etc.
1988 *
1989 * You should release the return value by calling g_time_zone_unref()
1990 * when you are done with it.
1991 *
1992 * Returns: the universal timezone
1993 *
1994 * Since: 2.26
1995 **/
1996 GTimeZone *
1997 g_time_zone_new_utc (void)
1998 {
1999 static GTimeZone *utc = NULL;
2000 static gsize initialised;
2001
2002 if (g_once_init_enter (&initialised))
2003 {
2004 utc = g_time_zone_new_identifier ("UTC");
2005 g_assert (utc != NULL);
2006 g_once_init_leave (&initialised, TRUE);
2007 }
2008
2009 return g_time_zone_ref (utc);
2010 }
2011
2012 /**
2013 * g_time_zone_new_local:
2014 *
2015 * Creates a #GTimeZone corresponding to local time. The local time
2016 * zone may change between invocations to this function; for example,
2017 * if the system administrator changes it.
2018 *
2019 * This is equivalent to calling g_time_zone_new() with the value of
2020 * the `TZ` environment variable (including the possibility of %NULL).
2021 *
2022 * You should release the return value by calling g_time_zone_unref()
2023 * when you are done with it.
2024 *
2025 * Returns: the local timezone
2026 *
2027 * Since: 2.26
2028 **/
2029 GTimeZone *
2030 g_time_zone_new_local (void)
2031 {
2032 const gchar *tzenv = g_getenv ("TZ");
2033 GTimeZone *tz;
2034
2035 G_LOCK (tz_local);
2036
2037 /* Is time zone changed and must be flushed? */
2038 if (tz_local && g_strcmp0 (g_time_zone_get_identifier (tz_local), tzenv))
2039 g_clear_pointer (&tz_local, g_time_zone_unref);
2040
2041 if (tz_local == NULL)
2042 tz_local = g_time_zone_new_identifier (tzenv);
2043 if (tz_local == NULL)
2044 tz_local = g_time_zone_new_utc ();
2045
2046 tz = g_time_zone_ref (tz_local);
2047
2048 G_UNLOCK (tz_local);
2049
2050 return tz;
2051 }
2052
2053 /**
2054 * g_time_zone_new_offset:
2055 * @seconds: offset to UTC, in seconds
2056 *
2057 * Creates a #GTimeZone corresponding to the given constant offset from UTC,
2058 * in seconds.
2059 *
2060 * This is equivalent to calling g_time_zone_new() with a string in the form
2061 * `[+|-]hh[:mm[:ss]]`.
2062 *
2063 * It is possible for this function to fail if @seconds is too big (greater than
2064 * 24 hours), in which case this function will return the UTC timezone for
2065 * backwards compatibility. To detect failures like this, use
2066 * g_time_zone_new_identifier() directly.
2067 *
2068 * Returns: (transfer full): a timezone at the given offset from UTC, or UTC on
2069 * failure
2070 * Since: 2.58
2071 */
2072 GTimeZone *
2073 g_time_zone_new_offset (gint32 seconds)
2074 {
2075 GTimeZone *tz = NULL;
2076 gchar *identifier = NULL;
2077
2078 /* Seemingly, we should be using @seconds directly to set the
2079 * #TransitionInfo.gmt_offset to avoid all this string building and parsing.
2080 * However, we always need to set the #GTimeZone.name to a constructed
2081 * string anyway, so we might as well reuse its code.
2082 * g_time_zone_new_identifier() should never fail in this situation. */
2083 identifier = g_strdup_printf ("%c%02u:%02u:%02u",
2084 (seconds >= 0) ? '+' : '-',
2085 (ABS (seconds) / 60) / 60,
2086 (ABS (seconds) / 60) % 60,
2087 ABS (seconds) % 60);
2088 tz = g_time_zone_new_identifier (identifier);
2089
2090 if (tz == NULL)
2091 tz = g_time_zone_new_utc ();
2092 else
2093 g_assert (g_time_zone_get_offset (tz, 0) == seconds);
2094
2095 g_assert (tz != NULL);
2096 g_free (identifier);
2097
2098 return tz;
2099 }
2100
2101 #define TRANSITION(n) g_array_index (tz->transitions, Transition, n)
2102 #define TRANSITION_INFO(n) g_array_index (tz->t_info, TransitionInfo, n)
2103
2104 /* Internal helpers {{{1 */
2105 /* NB: Interval 0 is before the first transition, so there's no
2106 * transition structure to point to which TransitionInfo to
2107 * use. Rule-based zones are set up so that TI 0 is always standard
2108 * time (which is what's in effect before Daylight time got started
2109 * in the early 20th century), but IANA tzfiles don't follow that
2110 * convention. The tzfile documentation says to use the first
2111 * standard-time (i.e., non-DST) tinfo, so that's what we do.
2112 */
2113 inline static const TransitionInfo*
2114 interval_info (GTimeZone *tz,
2115 guint interval)
2116 {
2117 guint index;
2118 g_return_val_if_fail (tz->t_info != NULL, NULL);
2119 if (interval && tz->transitions && interval <= tz->transitions->len)
2120 index = (TRANSITION(interval - 1)).info_index;
2121 else
2122 {
2123 for (index = 0; index < tz->t_info->len; index++)
2124 {
2125 TransitionInfo *tzinfo = &(TRANSITION_INFO(index));
2126 if (!tzinfo->is_dst)
2127 return tzinfo;
2128 }
2129 index = 0;
2130 }
2131
2132 return &(TRANSITION_INFO(index));
2133 }
2134
2135 inline static gint64
2136 interval_start (GTimeZone *tz,
2137 guint interval)
2138 {
2139 if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
2140 return G_MININT64;
2141 if (interval > tz->transitions->len)
2142 interval = tz->transitions->len;
2143 return (TRANSITION(interval - 1)).time;
2144 }
2145
2146 inline static gint64
2147 interval_end (GTimeZone *tz,
2148 guint interval)
2149 {
2150 if (tz->transitions && interval < tz->transitions->len)
2151 {
2152 gint64 lim = (TRANSITION(interval)).time;
2153 return lim - (lim != G_MININT64);
2154 }
2155 return G_MAXINT64;
2156 }
2157
2158 inline static gint32
2159 interval_offset (GTimeZone *tz,
2160 guint interval)
2161 {
2162 g_return_val_if_fail (tz->t_info != NULL, 0);
2163 return interval_info (tz, interval)->gmt_offset;
2164 }
2165
2166 inline static gboolean
2167 interval_isdst (GTimeZone *tz,
2168 guint interval)
2169 {
2170 g_return_val_if_fail (tz->t_info != NULL, 0);
2171 return interval_info (tz, interval)->is_dst;
2172 }
2173
2174
2175 inline static gchar*
2176 interval_abbrev (GTimeZone *tz,
2177 guint interval)
2178 {
2179 g_return_val_if_fail (tz->t_info != NULL, 0);
2180 return interval_info (tz, interval)->abbrev;
2181 }
2182
2183 inline static gint64
2184 interval_local_start (GTimeZone *tz,
2185 guint interval)
2186 {
2187 if (interval)
2188 return interval_start (tz, interval) + interval_offset (tz, interval);
2189
2190 return G_MININT64;
2191 }
2192
2193 inline static gint64
2194 interval_local_end (GTimeZone *tz,
2195 guint interval)
2196 {
2197 if (tz->transitions && interval < tz->transitions->len)
2198 return interval_end (tz, interval) + interval_offset (tz, interval);
2199
2200 return G_MAXINT64;
2201 }
2202
2203 static gboolean
2204 interval_valid (GTimeZone *tz,
2205 guint interval)
2206 {
2207 if ( tz->transitions == NULL)
2208 return interval == 0;
2209 return interval <= tz->transitions->len;
2210 }
2211
2212 /* g_time_zone_find_interval() {{{1 */
2213
2214 /**
2215 * g_time_zone_adjust_time:
2216 * @tz: a #GTimeZone
2217 * @type: the #GTimeType of @time_
2218 * @time_: (inout): a pointer to a number of seconds since January 1, 1970
2219 *
2220 * Finds an interval within @tz that corresponds to the given @time_,
2221 * possibly adjusting @time_ if required to fit into an interval.
2222 * The meaning of @time_ depends on @type.
2223 *
2224 * This function is similar to g_time_zone_find_interval(), with the
2225 * difference that it always succeeds (by making the adjustments
2226 * described below).
2227 *
2228 * In any of the cases where g_time_zone_find_interval() succeeds then
2229 * this function returns the same value, without modifying @time_.
2230 *
2231 * This function may, however, modify @time_ in order to deal with
2232 * non-existent times. If the non-existent local @time_ of 02:30 were
2233 * requested on March 14th 2010 in Toronto then this function would
2234 * adjust @time_ to be 03:00 and return the interval containing the
2235 * adjusted time.
2236 *
2237 * Returns: the interval containing @time_, never -1
2238 *
2239 * Since: 2.26
2240 **/
2241 gint
2242 g_time_zone_adjust_time (GTimeZone *tz,
2243 GTimeType type,
2244 gint64 *time_)
2245 {
2246 guint i, intervals;
2247 gboolean interval_is_dst;
2248
2249 if (tz->transitions == NULL)
2250 return 0;
2251
2252 intervals = tz->transitions->len;
2253
2254 /* find the interval containing *time UTC
2255 * TODO: this could be binary searched (or better) */
2256 for (i = 0; i <= intervals; i++)
2257 if (*time_ <= interval_end (tz, i))
2258 break;
2259
2260 g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
2261
2262 if (type != G_TIME_TYPE_UNIVERSAL)
2263 {
2264 if (*time_ < interval_local_start (tz, i))
2265 /* if time came before the start of this interval... */
2266 {
2267 i--;
2268
2269 /* if it's not in the previous interval... */
2270 if (*time_ > interval_local_end (tz, i))
2271 {
2272 /* it doesn't exist. fast-forward it. */
2273 i++;
2274 *time_ = interval_local_start (tz, i);
2275 }
2276 }
2277
2278 else if (*time_ > interval_local_end (tz, i))
2279 /* if time came after the end of this interval... */
2280 {
2281 i++;
2282
2283 /* if it's not in the next interval... */
2284 if (*time_ < interval_local_start (tz, i))
2285 /* it doesn't exist. fast-forward it. */
2286 *time_ = interval_local_start (tz, i);
2287 }
2288
2289 else
2290 {
2291 interval_is_dst = interval_isdst (tz, i);
2292 if ((interval_is_dst && type != G_TIME_TYPE_DAYLIGHT) ||
2293 (!interval_is_dst && type == G_TIME_TYPE_DAYLIGHT))
2294 {
2295 /* it's in this interval, but dst flag doesn't match.
2296 * check neighbours for a better fit. */
2297 if (i && *time_ <= interval_local_end (tz, i - 1))
2298 i--;
2299
2300 else if (i < intervals &&
2301 *time_ >= interval_local_start (tz, i + 1))
2302 i++;
2303 }
2304 }
2305 }
2306
2307 return i;
2308 }
2309
2310 /**
2311 * g_time_zone_find_interval:
2312 * @tz: a #GTimeZone
2313 * @type: the #GTimeType of @time_
2314 * @time_: a number of seconds since January 1, 1970
2315 *
2316 * Finds an interval within @tz that corresponds to the given @time_.
2317 * The meaning of @time_ depends on @type.
2318 *
2319 * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
2320 * succeed (since universal time is monotonic and continuous).
2321 *
2322 * Otherwise @time_ is treated as local time. The distinction between
2323 * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
2324 * the case that the given @time_ is ambiguous. In Toronto, for example,
2325 * 01:30 on November 7th 2010 occurred twice (once inside of daylight
2326 * savings time and the next, an hour later, outside of daylight savings
2327 * time). In this case, the different value of @type would result in a
2328 * different interval being returned.
2329 *
2330 * It is still possible for this function to fail. In Toronto, for
2331 * example, 02:00 on March 14th 2010 does not exist (due to the leap
2332 * forward to begin daylight savings time). -1 is returned in that
2333 * case.
2334 *
2335 * Returns: the interval containing @time_, or -1 in case of failure
2336 *
2337 * Since: 2.26
2338 */
2339 gint
2340 g_time_zone_find_interval (GTimeZone *tz,
2341 GTimeType type,
2342 gint64 time_)
2343 {
2344 guint i, intervals;
2345 gboolean interval_is_dst;
2346
2347 if (tz->transitions == NULL)
2348 return 0;
2349 intervals = tz->transitions->len;
2350 for (i = 0; i <= intervals; i++)
2351 if (time_ <= interval_end (tz, i))
2352 break;
2353
2354 if (type == G_TIME_TYPE_UNIVERSAL)
2355 return i;
2356
2357 if (time_ < interval_local_start (tz, i))
2358 {
2359 if (time_ > interval_local_end (tz, --i))
2360 return -1;
2361 }
2362
2363 else if (time_ > interval_local_end (tz, i))
2364 {
2365 if (time_ < interval_local_start (tz, ++i))
2366 return -1;
2367 }
2368
2369 else
2370 {
2371 interval_is_dst = interval_isdst (tz, i);
2372 if ((interval_is_dst && type != G_TIME_TYPE_DAYLIGHT) ||
2373 (!interval_is_dst && type == G_TIME_TYPE_DAYLIGHT))
2374 {
2375 if (i && time_ <= interval_local_end (tz, i - 1))
2376 i--;
2377
2378 else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
2379 i++;
2380 }
2381 }
2382
2383 return i;
2384 }
2385
2386 /* Public API accessors {{{1 */
2387
2388 /**
2389 * g_time_zone_get_abbreviation:
2390 * @tz: a #GTimeZone
2391 * @interval: an interval within the timezone
2392 *
2393 * Determines the time zone abbreviation to be used during a particular
2394 * @interval of time in the time zone @tz.
2395 *
2396 * For example, in Toronto this is currently "EST" during the winter
2397 * months and "EDT" during the summer months when daylight savings time
2398 * is in effect.
2399 *
2400 * Returns: the time zone abbreviation, which belongs to @tz
2401 *
2402 * Since: 2.26
2403 **/
2404 const gchar *
2405 g_time_zone_get_abbreviation (GTimeZone *tz,
2406 gint interval)
2407 {
2408 g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
2409
2410 return interval_abbrev (tz, (guint)interval);
2411 }
2412
2413 /**
2414 * g_time_zone_get_offset:
2415 * @tz: a #GTimeZone
2416 * @interval: an interval within the timezone
2417 *
2418 * Determines the offset to UTC in effect during a particular @interval
2419 * of time in the time zone @tz.
2420 *
2421 * The offset is the number of seconds that you add to UTC time to
2422 * arrive at local time for @tz (ie: negative numbers for time zones
2423 * west of GMT, positive numbers for east).
2424 *
2425 * Returns: the number of seconds that should be added to UTC to get the
2426 * local time in @tz
2427 *
2428 * Since: 2.26
2429 **/
2430 gint32
2431 g_time_zone_get_offset (GTimeZone *tz,
2432 gint interval)
2433 {
2434 g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
2435
2436 return interval_offset (tz, (guint)interval);
2437 }
2438
2439 /**
2440 * g_time_zone_is_dst:
2441 * @tz: a #GTimeZone
2442 * @interval: an interval within the timezone
2443 *
2444 * Determines if daylight savings time is in effect during a particular
2445 * @interval of time in the time zone @tz.
2446 *
2447 * Returns: %TRUE if daylight savings time is in effect
2448 *
2449 * Since: 2.26
2450 **/
2451 gboolean
2452 g_time_zone_is_dst (GTimeZone *tz,
2453 gint interval)
2454 {
2455 g_return_val_if_fail (interval_valid (tz, interval), FALSE);
2456
2457 if (tz->transitions == NULL)
2458 return FALSE;
2459
2460 return interval_isdst (tz, (guint)interval);
2461 }
2462
2463 /**
2464 * g_time_zone_get_identifier:
2465 * @tz: a #GTimeZone
2466 *
2467 * Get the identifier of this #GTimeZone, as passed to g_time_zone_new().
2468 * If the identifier passed at construction time was not recognised, `UTC` will
2469 * be returned. If it was %NULL, the identifier of the local timezone at
2470 * construction time will be returned.
2471 *
2472 * The identifier will be returned in the same format as provided at
2473 * construction time: if provided as a time offset, that will be returned by
2474 * this function.
2475 *
2476 * Returns: identifier for this timezone
2477 * Since: 2.58
2478 */
2479 const gchar *
2480 g_time_zone_get_identifier (GTimeZone *tz)
2481 {
2482 g_return_val_if_fail (tz != NULL, NULL);
2483
2484 return tz->name;
2485 }
2486
2487 /* Epilogue {{{1 */
2488 /* vim:set foldmethod=marker: */