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