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