GTimeZone: Parse POSIX format for TZ
[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 of the licence, 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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 /* Prologue {{{1 */
23
24 #include "config.h"
25
26 #include "gtimezone.h"
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <signal.h>
31
32 #include "gmappedfile.h"
33 #include "gtestutils.h"
34 #include "gfileutils.h"
35 #include "gstrfuncs.h"
36 #include "ghash.h"
37 #include "gthread.h"
38 #include "gbytes.h"
39 #include "gslice.h"
40 #include "gdatetime.h"
41 #include "gdate.h"
42
43 /**
44  * SECTION:timezone
45  * @title: GTimeZone
46  * @short_description: a structure representing a time zone
47  * @see_also: #GDateTime
48  *
49  * #GTimeZone is a structure that represents a time zone, at no
50  * particular point in time.  It is refcounted and immutable.
51  *
52  * A time zone contains a number of intervals.  Each interval has
53  * an abbreviation to describe it, an offet to UTC and a flag indicating
54  * if the daylight savings time is in effect during that interval.  A
55  * time zone always has at least one interval -- interval 0.
56  *
57  * Every UTC time is contained within exactly one interval, but a given
58  * local time may be contained within zero, one or two intervals (due to
59  * incontinuities associated with daylight savings time).
60  *
61  * An interval may refer to a specific period of time (eg: the duration
62  * of daylight savings time during 2010) or it may refer to many periods
63  * of time that share the same properties (eg: all periods of daylight
64  * savings time).  It is also possible (usually for political reasons)
65  * that some properties (like the abbreviation) change between intervals
66  * without other properties changing.
67  *
68  * #GTimeZone is available since GLib 2.26.
69  */
70
71 /**
72  * GTimeZone:
73  *
74  * #GDateTime is an opaque structure whose members cannot be accessed
75  * directly.
76  *
77  * Since: 2.26
78  **/
79
80 /* zoneinfo file format {{{1 */
81
82 /* unaligned */
83 typedef struct { gchar bytes[8]; } gint64_be;
84 typedef struct { gchar bytes[4]; } gint32_be;
85 typedef struct { gchar bytes[4]; } guint32_be;
86
87 static inline gint64 gint64_from_be (const gint64_be be) {
88   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
89 }
90
91 static inline gint32 gint32_from_be (const gint32_be be) {
92   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
93 }
94
95 static inline guint32 guint32_from_be (const guint32_be be) {
96   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
97 }
98
99 struct tzhead
100 {
101   gchar      tzh_magic[4];
102   gchar      tzh_version;
103   guchar     tzh_reserved[15];
104
105   guint32_be tzh_ttisgmtcnt;
106   guint32_be tzh_ttisstdcnt;
107   guint32_be tzh_leapcnt;
108   guint32_be tzh_timecnt;
109   guint32_be tzh_typecnt;
110   guint32_be tzh_charcnt;
111 };
112
113 struct ttinfo
114 {
115   gint32_be tt_gmtoff;
116   guint8    tt_isdst;
117   guint8    tt_abbrind;
118 };
119
120 typedef struct
121 {
122   gint32     gmt_offset;
123   gboolean   is_dst;
124   gboolean   is_standard;
125   gboolean   is_gmt;
126   gchar     *abbrev;
127 } TransitionInfo;
128
129 typedef struct
130 {
131   gint64 time;
132   gint   info_index;
133 } Transition;
134
135 typedef struct
136 {
137   gint     year;
138   gint     mon;
139   gint     mday;
140   gint     wday;
141   gint     week;
142   gint     hour;
143   gint     min;
144   gint     sec;
145   gboolean isstd;
146   gboolean isgmt;
147 } TimeZoneDate;
148
149 typedef struct
150 {
151   gint         start_year;
152   gint32       std_offset;
153   gint32       dlt_offset;
154   TimeZoneDate dlt_start;
155   TimeZoneDate dlt_end;
156   const gchar *std_name;
157   const gchar *dlt_name;
158 } TimeZoneRule;
159
160
161 /* GTimeZone structure and lifecycle {{{1 */
162 struct _GTimeZone
163 {
164   gchar   *name;
165   GArray  *t_info;
166   GArray  *transitions;
167   gint     ref_count;
168 };
169
170 G_LOCK_DEFINE_STATIC (time_zones);
171 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
172
173 #define MIN_TZYEAR 1900
174 #define MAX_TZYEAR 2038
175
176 /**
177  * g_time_zone_unref:
178  * @tz: a #GTimeZone
179  *
180  * Decreases the reference count on @tz.
181  *
182  * Since: 2.26
183  **/
184 void
185 g_time_zone_unref (GTimeZone *tz)
186 {
187   int ref_count;
188
189 again:
190   ref_count = g_atomic_int_get (&tz->ref_count);
191
192   g_assert (ref_count > 0);
193
194   if (ref_count == 1)
195     {
196       if (tz->name != NULL)
197         {
198           G_LOCK(time_zones);
199
200           /* someone else might have grabbed a ref in the meantime */
201           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
202             {
203               G_UNLOCK(time_zones);
204               goto again;
205             }
206
207           g_hash_table_remove (time_zones, tz->name);
208           G_UNLOCK(time_zones);
209         }
210
211       g_array_free (tz->t_info, TRUE);
212       if (tz->transitions != NULL)
213         g_array_free (tz->transitions, TRUE);
214       g_free (tz->name);
215
216       g_slice_free (GTimeZone, tz);
217     }
218
219   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
220                                                           ref_count,
221                                                           ref_count - 1))
222     goto again;
223 }
224
225 /**
226  * g_time_zone_ref:
227  * @tz: a #GTimeZone
228  *
229  * Increases the reference count on @tz.
230  *
231  * Returns: a new reference to @tz.
232  *
233  * Since: 2.26
234  **/
235 GTimeZone *
236 g_time_zone_ref (GTimeZone *tz)
237 {
238   g_assert (tz->ref_count > 0);
239
240   g_atomic_int_inc (&tz->ref_count);
241
242   return tz;
243 }
244
245 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
246 /*
247  * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
248  *  - h[h] is 0 to 23
249  *  - mm is 00 to 59
250  *  - ss is 00 to 59
251  */
252 static gboolean
253 parse_time (const gchar *time_,
254             gint32      *offset)
255 {
256   if (*time_ < '0' || '9' < *time_)
257     return FALSE;
258
259   *offset = 60 * 60 * (*time_++ - '0');
260
261   if (*time_ == '\0')
262     return TRUE;
263
264   if (*time_ != ':')
265     {
266       if (*time_ < '0' || '9' < *time_)
267         return FALSE;
268
269       *offset *= 10;
270       *offset += 60 * 60 * (*time_++ - '0');
271
272       if (*offset > 23 * 60 * 60)
273         return FALSE;
274
275       if (*time_ == '\0')
276         return TRUE;
277     }
278
279   if (*time_ == ':')
280     time_++;
281
282   if (*time_ < '0' || '5' < *time_)
283     return FALSE;
284
285   *offset += 10 * 60 * (*time_++ - '0');
286
287   if (*time_ < '0' || '9' < *time_)
288     return FALSE;
289
290   *offset += 60 * (*time_++ - '0');
291
292   if (*time_ == '\0')
293     return TRUE;
294
295   if (*time_ == ':')
296     time_++;
297
298   if (*time_ < '0' || '5' < *time_)
299     return FALSE;
300
301   *offset += 10 * (*time_++ - '0');
302
303   if (*time_ < '0' || '9' < *time_)
304     return FALSE;
305
306   *offset += *time_++ - '0';
307
308   return *time_ == '\0';
309 }
310
311 static gboolean
312 parse_constant_offset (const gchar *name,
313                        gint32      *offset)
314 {
315   if (g_strcmp0 (name, "UTC") == 0)
316     {
317       *offset = 0;
318       return TRUE;
319     }
320
321   if (*name >= '0' && '9' >= *name)
322     return parse_time (name, offset);
323
324   switch (*name++)
325     {
326     case 'Z':
327       *offset = 0;
328       return !*name;
329
330     case '+':
331       return parse_time (name, offset);
332
333     case '-':
334       if (parse_time (name, offset))
335         {
336           *offset = -*offset;
337           return TRUE;
338         }
339
340     default:
341       return FALSE;
342     }
343 }
344
345 static void
346 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
347 {
348   gint32 offset;
349   TransitionInfo info;
350
351   if (name == NULL || !parse_constant_offset (name, &offset))
352     return;
353
354   info.gmt_offset = offset;
355   info.is_dst = FALSE;
356   info.is_standard = TRUE;
357   info.is_gmt = TRUE;
358   info.abbrev =  g_strdup (name);
359
360
361   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
362   g_array_append_val (gtz->t_info, info);
363
364   /* Constant offset, no transitions */
365   gtz->transitions = NULL;
366 }
367
368 #ifdef G_OS_UNIX
369 static GBytes*
370 zone_info_unix (const gchar *identifier)
371 {
372   gchar *filename;
373   GMappedFile *file = NULL;
374   GBytes *zoneinfo = NULL;
375
376   /* identifier can be a relative or absolute path name;
377      if relative, it is interpreted starting from /usr/share/zoneinfo
378      while the POSIX standard says it should start with :,
379      glibc allows both syntaxes, so we should too */
380   if (identifier != NULL)
381     {
382       const gchar *tzdir;
383
384       tzdir = getenv ("TZDIR");
385       if (tzdir == NULL)
386         tzdir = "/usr/share/zoneinfo";
387
388       if (*identifier == ':')
389         identifier ++;
390
391       if (g_path_is_absolute (identifier))
392         filename = g_strdup (identifier);
393       else
394         filename = g_build_filename (tzdir, identifier, NULL);
395     }
396   else
397     filename = g_strdup ("/etc/localtime");
398
399   file = g_mapped_file_new (filename, FALSE, NULL);
400   if (file != NULL)
401     {
402       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
403                                              g_mapped_file_get_length (file),
404                                              (GDestroyNotify)g_mapped_file_unref,
405                                              g_mapped_file_ref (file));
406       g_mapped_file_unref (file);
407     }
408   g_free (filename);
409   return zoneinfo;
410 }
411
412 static void
413 init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
414 {
415   gsize size;
416   guint index;
417   guint32 time_count, type_count, leap_count, isgmt_count;
418   guint32  isstd_count, char_count ;
419   gpointer tz_transitions, tz_type_index, tz_ttinfo;
420   gpointer  tz_leaps, tz_isgmt, tz_isstd;
421   gchar* tz_abbrs;
422   guint timesize = sizeof (gint32), countsize = sizeof (gint32);
423   const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
424
425   g_return_if_fail (size >= sizeof (struct tzhead) &&
426                     memcmp (header, "TZif", 4) == 0);
427
428   if (header->tzh_version == '2')
429       {
430         /* Skip ahead to the newer 64-bit data if it's available. */
431         header = (const struct tzhead *)
432           (((const gchar *) (header + 1)) +
433            guint32_from_be(header->tzh_ttisgmtcnt) +
434            guint32_from_be(header->tzh_ttisstdcnt) +
435            8 * guint32_from_be(header->tzh_leapcnt) +
436            5 * guint32_from_be(header->tzh_timecnt) +
437            6 * guint32_from_be(header->tzh_typecnt) +
438            guint32_from_be(header->tzh_charcnt));
439         timesize = sizeof (gint64);
440       }
441   time_count = guint32_from_be(header->tzh_timecnt);
442   type_count = guint32_from_be(header->tzh_typecnt);
443   leap_count = guint32_from_be(header->tzh_leapcnt);
444   isgmt_count = guint32_from_be(header->tzh_ttisgmtcnt);
445   isstd_count = guint32_from_be(header->tzh_ttisstdcnt);
446   char_count = guint32_from_be(header->tzh_charcnt);
447
448   g_assert (type_count == isgmt_count);
449   g_assert (type_count == isstd_count);
450
451   tz_transitions = (gpointer)(header + 1);
452   tz_type_index = tz_transitions + timesize * time_count;
453   tz_ttinfo = tz_type_index + time_count;
454   tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
455   tz_leaps = tz_abbrs + char_count;
456   tz_isstd = tz_leaps + (timesize + countsize) * leap_count;
457   tz_isgmt = tz_isstd + isstd_count;
458
459   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
460                                    type_count);
461   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
462                                         time_count);
463
464   for (index = 0; index < type_count; index++)
465     {
466       TransitionInfo t_info;
467       struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
468       t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
469       t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
470       t_info.is_standard = ((guint8*)tz_isstd)[index] ? TRUE : FALSE;
471       t_info.is_gmt = ((guint8*)tz_isgmt)[index] ? TRUE : FALSE;
472       t_info.abbrev = g_strdup (&tz_abbrs[info.tt_abbrind]);
473       g_array_append_val (gtz->t_info, t_info);
474     }
475
476   for (index = 0; index < time_count; index++)
477     {
478       Transition trans;
479       if (header->tzh_version == '2')
480         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
481       else
482         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
483       trans.info_index = ((guint8*)tz_type_index)[index];
484       g_assert (trans.info_index >= 0);
485       g_assert (trans.info_index < gtz->t_info->len);
486       g_array_append_val (gtz->transitions, trans);
487     }
488   g_bytes_unref (zoneinfo);
489 }
490
491 #endif
492
493 static void
494 find_relative_date (TimeZoneDate *buffer,
495                     GTimeZone    *tz)
496 {
497   GDateTime *dt;
498   gint wday;
499
500   wday = buffer->wday;
501
502   /* Get last day if last is needed, first day otherwise */
503   dt = g_date_time_new (tz,
504                         buffer->year,
505                         buffer->mon + (buffer->week < 5? 0 : 1),
506                         buffer->week < 5? 1 : 0,
507                         buffer->hour, buffer->min, buffer->sec);
508
509   buffer->wday = g_date_time_get_day_of_week (dt);
510   buffer->mday = g_date_time_get_day_of_month (dt);
511
512   if (buffer->week < 5)
513     {
514       if (wday < buffer->wday)
515         buffer->wday -= 7;
516
517       buffer->mday += (buffer->week - 1) * 7;
518     }
519
520   else if (wday > buffer->wday)
521     buffer->wday += 7;
522
523   buffer->mday += wday - buffer->wday;
524   buffer->wday = wday;
525
526   g_date_time_unref (dt);
527 }
528
529 /* Offset is previous offset of local time */
530 static gint64
531 boundary_for_year (TimeZoneDate *boundary,
532                    gint          year,
533                    gint32        prev_offset,
534                    gint32        std_offset)
535 {
536   TimeZoneDate buffer;
537   GDateTime *dt;
538   GTimeZone *tz;
539   gint64 t;
540   gint32 offset;
541   gchar *identifier;
542
543   buffer = *boundary;
544
545   if (boundary->isgmt)
546     offset = 0;
547   else if (boundary->isstd)
548     offset = std_offset;
549   else
550     offset = prev_offset;
551
552   G_UNLOCK (time_zones);
553
554   identifier = g_strdup_printf ("%+03d:%02d:%02d",
555                                 (int) offset / 3600,
556                                 (int) abs (offset / 60) % 60,
557                                 (int) abs (offset) % 3600);
558   tz = g_time_zone_new (identifier);
559   g_free (identifier);
560
561   if (boundary->year == 0)
562     {
563       buffer.year = year;
564
565       if (buffer.wday)
566         find_relative_date (&buffer, tz);
567     }
568
569   g_assert (buffer.year == year);
570
571   dt = g_date_time_new (tz,
572                         buffer.year, buffer.mon, buffer.mday,
573                         buffer.hour, buffer.min, buffer.sec);
574   t = g_date_time_to_unix (dt);
575   g_date_time_unref (dt);
576
577   g_time_zone_unref (tz);
578
579   G_LOCK (time_zones);
580
581   return t;
582 }
583
584 static void
585 init_zone_from_rules (GTimeZone    *gtz,
586                       TimeZoneRule *rules,
587                       gint          rules_num)
588 {
589   TransitionInfo info[2];
590   Transition trans;
591   gint type_count, trans_count;
592   gint year, i, x, y;
593   gint32 last_offset;
594
595   type_count = 0;
596   trans_count = 0;
597
598   /* Last rule only contains max year */
599   for (i = 0; i < rules_num - 1; i++)
600     {
601       if (rules[i].dlt_start.mon)
602         {
603           type_count += 2;
604           trans_count += 2 * (rules[i+1].start_year - rules[i].start_year);
605         }
606       else
607         type_count++;
608     }
609
610   x = 0;
611   y = 0;
612
613   /* If standard time happens before daylight time in first rule
614    * with daylight, skip first transition so the minimum is in
615    * standard time and the first transition is in daylight time */
616   for (i = 0; i < rules_num - 1 && rules[0].dlt_start.mon == 0; i++);
617
618   if (i < rules_num -1 && rules[i].dlt_start.mon > 0 &&
619       rules[i].dlt_start.mon > rules[i].dlt_end.mon)
620     {
621       trans_count--;
622       x = -1;
623     }
624
625   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
626   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
627
628   last_offset = rules[0].std_offset;
629
630   for (i = 0; i < rules_num - 1; i++)
631     {
632       if (rules[i].dlt_start.mon)
633         {
634           /* Standard */
635           info[0].gmt_offset = rules[i].std_offset;
636           info[0].is_dst = FALSE;
637           info[0].is_standard = rules[i].dlt_end.isstd;
638           info[0].is_gmt = rules[i].dlt_end.isgmt;
639
640           if (rules[i].std_name)
641             info[0].abbrev = g_strdup (rules[i].std_name);
642
643           else
644             info[0].abbrev = g_strdup_printf ("%+03d%02d",
645                                               (int) rules[i].std_offset / 3600,
646                                               (int) abs (rules[i].std_offset / 60) % 60);
647
648
649           /* Daylight */
650           info[1].gmt_offset = rules[i].dlt_offset;
651           info[1].is_dst = TRUE;
652           info[1].is_standard = rules[i].dlt_start.isstd;
653           info[1].is_gmt = rules[i].dlt_start.isgmt;
654
655           if (rules[i].dlt_name)
656             info[1].abbrev = g_strdup (rules[i].dlt_name);
657
658           else
659             info[1].abbrev = g_strdup_printf ("%+03d%02d",
660                                               (int) rules[i].dlt_offset / 3600,
661                                               (int) abs (rules[i].dlt_offset / 60) % 60);
662
663           if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
664             {
665               g_array_append_val (gtz->t_info, info[1]);
666               g_array_append_val (gtz->t_info, info[0]);
667             }
668           else
669             {
670               g_array_append_val (gtz->t_info, info[0]);
671               g_array_append_val (gtz->t_info, info[1]);
672             }
673
674           /* Transition dates */
675           for (year = rules[i].start_year; year < rules[i+1].start_year; year++)
676             {
677               if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
678                 {
679                   /* Daylight Data */
680                   trans.info_index = y;
681                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
682                                                   last_offset, rules[i].std_offset);
683                   g_array_insert_val (gtz->transitions, x++, trans);
684                   last_offset = rules[i].dlt_offset;
685
686                   /* Standard Data */
687                   trans.info_index = y+1;
688                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
689                                                   last_offset, rules[i].std_offset);
690                   g_array_insert_val (gtz->transitions, x++, trans);
691                   last_offset = rules[i].std_offset;
692                 }
693               else
694                 {
695                   /* Standard Data */
696                   trans.info_index = y;
697                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
698                                                   last_offset, rules[i].std_offset);
699                   if (x >= 0)
700                     g_array_insert_val (gtz->transitions, x++, trans);
701                   else
702                     x++;
703                   last_offset = rules[i].std_offset;
704
705                   /* Daylight Data */
706                   trans.info_index = y+1;
707                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
708                                                   last_offset, rules[i].std_offset);
709                   g_array_insert_val (gtz->transitions, x++, trans);
710                   last_offset = rules[i].dlt_offset;
711                 }
712             }
713
714           y += 2;
715         }
716       else
717         {
718           /* Standard */
719           info[0].gmt_offset = rules[i].std_offset;
720           info[0].is_dst = FALSE;
721           info[0].is_standard = FALSE;
722           info[0].is_gmt = FALSE;
723
724           if (rules[i].std_name)
725             info[0].abbrev = g_strdup (rules[i].std_name);
726
727           else
728             info[0].abbrev = g_strdup_printf ("%+03d%02d",
729                                               (int) rules[i].std_offset / 3600,
730                                               (int) abs (rules[i].std_offset / 60) % 60);
731
732           g_array_append_val (gtz->t_info, info[0]);
733
734           last_offset = rules[i].std_offset;
735
736           y++;
737         }
738     }
739 }
740
741 /*
742  * parses date[/time] for parsing TZ environment variable
743  *
744  * date is either Mm.w.d, Jn or N
745  * - m is 1 to 12
746  * - w is 1 to 5
747  * - d is 0 to 6
748  * - n is 1 to 365
749  * - N is 0 to 365
750  *
751  * time is either h or hh[[:]mm[[[:]ss]]]
752  *  - h[h] is 0 to 23
753  *  - mm is 00 to 59
754  *  - ss is 00 to 59
755  */
756 static gboolean
757 parse_tz_boundary (const gchar  *identifier,
758                    TimeZoneDate *boundary)
759 {
760   const gchar *pos;
761   gint month, week, day;
762   GDate *date;
763
764   pos = identifier;
765
766   if (*pos == 'M')                      /* Relative date */
767     {
768       pos++;
769
770       if (*pos == '\0' || *pos < '0' || '9' < *pos)
771         return FALSE;
772
773       month = *pos++ - '0';
774
775       if ((month == 1 && *pos >= '0' && '2' >= *pos) ||
776           (month == 0 && *pos >= '0' && '9' >= *pos))
777         {
778           month *= 10;
779           month += *pos++ - '0';
780         }
781
782       if (*pos++ != '.' || month == 0)
783         return FALSE;
784
785       if (*pos == '\0' || *pos < '1' || '5' < *pos)
786         return FALSE;
787
788       week = *pos++ - '0';
789
790       if (*pos++ != '.')
791         return FALSE;
792
793       if (*pos == '\0' || *pos < '0' || '6' < *pos)
794         return FALSE;
795
796       day = *pos++ - '0';
797
798       if (!day)
799         day += 7;
800
801       boundary->year = 0;
802       boundary->mon = month;
803       boundary->week = week;
804       boundary->wday = day;
805     }
806
807   else if (*pos == 'J')                 /* Julian day */
808     {
809       pos++;
810
811       day = 0;
812       while (*pos >= '0' && '9' >= *pos)
813         {
814           day *= 10;
815           day += *pos++ - '0';
816         }
817
818       if (day < 1 || 365 < day)
819         return FALSE;
820
821       date = g_date_new_julian (day);
822       boundary->year = 0;
823       boundary->mon = (int) g_date_get_month (date);
824       boundary->mday = (int) g_date_get_day (date);
825       boundary->wday = 0;
826       g_date_free (date);
827     }
828
829   else if (*pos >= '0' && '9' >= *pos)  /* Zero-based Julian day */
830     {
831       day = 0;
832       while (*pos >= '0' && '9' >= *pos)
833         {
834           day *= 10;
835           day += *pos++ - '0';
836         }
837
838       if (day < 0 || 365 < day)
839         return FALSE;
840
841       date = g_date_new_julian (day >= 59? day : day + 1);
842       boundary->year = 0;
843       boundary->mon = (int) g_date_get_month (date);
844       boundary->mday = (int) g_date_get_day (date);
845       boundary->wday = 0;
846       g_date_free (date);
847
848       /* February 29 */
849       if (day == 59)
850         boundary->mday++;
851     }
852
853   else
854     return FALSE;
855
856   /* Time */
857   boundary->isstd = FALSE;
858   boundary->isgmt = FALSE;
859
860   if (*pos == '/')
861     {
862       gint32 offset;
863
864       if (!parse_time (++pos, &offset))
865         return FALSE;
866
867       boundary->hour = offset / 3600;
868       boundary->min = (offset / 60) % 60;
869       boundary->sec = offset % 3600;
870
871       return TRUE;
872     }
873
874   else
875     {
876       boundary->hour = 2;
877       boundary->min = 0;
878       boundary->sec = 0;
879
880       return *pos == '\0';
881     }
882 }
883
884 /*
885  * Creates an array of TimeZoneRule from a TZ environment variable
886  * type of identifier.  Should free rules, std_name and dlt_name
887  * afterwards
888  */
889 static gboolean
890 rules_from_identifier (const gchar   *identifier,
891                        TimeZoneRule **rules,
892                        gint          *rules_num,
893                        gchar        **std_name,
894                        gchar        **dlt_name)
895 {
896   const gchar *std_name_pos, *std_offset_pos;
897   const gchar *dlt_name_pos, *dlt_offset_pos;
898
899   const gchar *start_date_pos, *end_date_pos;
900
901   const gchar *pos;
902   gchar *buffer;
903   gboolean ret;
904
905   gint32 std_offset, dlt_offset;
906   TimeZoneDate dlt_start, dlt_end;
907
908   if (!identifier)
909     return FALSE;
910
911   pos = identifier;
912
913   std_name_pos = pos;
914
915   /* Name is alpha */
916   while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
917     pos++;
918
919   /* Offset for standard required (format 1) */
920   if (*pos == '\0')
921     return FALSE;
922
923   /* Name should be three or more alphabetic characters */
924   if (pos - identifier < 3)
925     return FALSE;
926
927   std_offset_pos = pos;
928
929   /* Standard offset */
930   while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
931     pos++;
932
933   buffer = g_strndup (std_offset_pos, pos - std_offset_pos);
934   ret = parse_constant_offset (buffer, &std_offset);
935   g_free (buffer);
936
937   if (!ret)
938     return FALSE;
939
940   dlt_name_pos = pos;
941
942   /* Format 2 */
943   if (*pos != '\0')
944     {
945       /* Name is alpha */
946       while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
947         pos++;
948
949       /* Name should be three or more alphabetic characters */
950       if (pos - identifier < 3)
951         return FALSE;
952
953       dlt_offset_pos = pos;
954
955       /* Start and end required (format 2) */
956       if (*pos == '\0')
957         return FALSE;
958
959       /* Default offset is 1 hour less from standard offset */
960       if (*pos++ == ',')
961         dlt_offset = std_offset - 60 * 60;
962
963       else
964         {
965           /* Daylight offset */
966           while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
967             pos++;
968
969           buffer = g_strndup (dlt_offset_pos, pos - dlt_offset_pos);
970           ret = parse_constant_offset (buffer, &dlt_offset);
971           g_free (buffer);
972
973           if (!ret)
974             return FALSE;
975
976           /* Start and end required (format 2) */
977           if (*pos++ != ',')
978             return FALSE;
979         }
980
981       /* Start date */
982       start_date_pos = pos;
983
984       while (*pos != ',' && *pos != '\0')
985         pos++;
986
987       /* End required (format 2) */
988       if (*pos == '\0')
989         return FALSE;
990
991       buffer = g_strndup (start_date_pos, pos++ - start_date_pos);
992       ret = parse_tz_boundary (buffer, &dlt_start);
993       g_free (buffer);
994
995       if (!ret)
996         return FALSE;
997
998       /* End date */
999       end_date_pos = pos;
1000
1001       while (*pos != '\0')
1002         pos++;
1003
1004       buffer = g_strndup (end_date_pos, pos - end_date_pos);
1005       ret = parse_tz_boundary (buffer, &dlt_end);
1006       g_free (buffer);
1007
1008       if (!ret)
1009         return FALSE;
1010     }
1011
1012
1013   *std_name = g_strndup (std_name_pos, std_offset_pos - std_name_pos);
1014
1015   if (dlt_name_pos != pos)
1016     *dlt_name = g_strndup (dlt_name_pos, dlt_offset_pos - dlt_name_pos);
1017   else
1018     {
1019       *dlt_name = NULL;
1020       dlt_start.mon = 0;
1021     }
1022
1023   *rules_num = 2;
1024   *rules = g_new0 (TimeZoneRule, 2);
1025
1026   (*rules)[0].start_year = MIN_TZYEAR;
1027   (*rules)[1].start_year = MAX_TZYEAR;
1028
1029   (*rules)[0].std_offset = -std_offset;
1030   (*rules)[0].dlt_offset = -dlt_offset;
1031   (*rules)[0].dlt_start  = dlt_start;
1032   (*rules)[0].dlt_end = dlt_end;
1033   (*rules)[0].std_name = *std_name;
1034   (*rules)[0].dlt_name = *dlt_name;
1035
1036   return TRUE;
1037 }
1038
1039 /* Construction {{{1 */
1040 /**
1041  * g_time_zone_new:
1042  * @identifier: (allow-none): a timezone identifier
1043  *
1044  * Creates a #GTimeZone corresponding to @identifier.
1045  *
1046  * @identifier can either be an RFC3339/ISO 8601 time offset or
1047  * something that would pass as a valid value for the
1048  * <varname>TZ</varname> environment variable (including %NULL).
1049  *
1050  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
1051  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
1052  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.  Offsets are
1053  * time values to be added to Coordinated Universal Time (UTC) to get
1054  * the local time.
1055  *
1056  * In Unix, the <varname>TZ</varname> environment variable typically
1057  * corresponds to the name of a file in the zoneinfo database, or
1058  * string in "std offset [dst [offset],start[/time],end[/time]]"
1059  * (POSIX) format.  There  are  no spaces in the specification.  The
1060  * name of standard and daylight savings time zone must be three or more
1061  * alphabetic characters.  Offsets are time values to be added to local
1062  * time to get Coordinated Universal Time (UTC) and should be
1063  * <literal>"[±]hh[[:]mm[:ss]]"</literal>.  Dates are either
1064  * <literal>"Jn"</literal> (Julian day with n between 1 and 365, leap
1065  * years not counted), <literal>"n"</literal> (zero-based Julian day
1066  * with n between 0 and 365) or <literal>"Mm.w.d"</literal> (day d
1067  * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
1068  * 0 is a Sunday).  Times are in local wall clock time, the default is
1069  * 02:00:00.
1070  *
1071  * g_time_zone_new_local() calls this function with the value of the
1072  * <varname>TZ</varname> environment variable.  This function itself is
1073  * independent of the value of <varname>TZ</varname>, but if @identifier
1074  * is %NULL then <filename>/etc/localtime</filename> will be consulted
1075  * to discover the correct timezone.
1076  *
1077  * If intervals are not available, only time zone rules from
1078  * <varname>TZ</varname> environment variable or other means, then they
1079  * will be computed from year 1900 to 2037.  If the maximum year for the
1080  * rules is available and it is greater than 2037, then it will followed
1081  * instead.
1082  *
1083  * See <ulink
1084  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
1085  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
1086  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
1087  * full list of valid time offsets.  See <ulink
1088  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
1089  * GNU C Library manual</ulink> for an explanation of the possible
1090  * values of the <varname>TZ</varname> environment variable.
1091  *
1092  * You should release the return value by calling g_time_zone_unref()
1093  * when you are done with it.
1094  *
1095  * Returns: the requested timezone
1096  *
1097  * Since: 2.26
1098  **/
1099 GTimeZone *
1100 g_time_zone_new (const gchar *identifier)
1101 {
1102   GTimeZone *tz = NULL;
1103   TimeZoneRule *rules;
1104   gint rules_num;
1105   gchar *std_name, *dlt_name;
1106
1107   G_LOCK (time_zones);
1108   if (time_zones == NULL)
1109     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
1110
1111   if (identifier)
1112     {
1113       tz = g_hash_table_lookup (time_zones, identifier);
1114       if (tz)
1115         {
1116           g_atomic_int_inc (&tz->ref_count);
1117           G_UNLOCK (time_zones);
1118           return tz;
1119         }
1120     }
1121
1122   tz = g_slice_new0 (GTimeZone);
1123   tz->name = g_strdup (identifier);
1124   tz->ref_count = 0;
1125
1126   zone_for_constant_offset (tz, identifier);
1127
1128   if (tz->t_info == NULL &&
1129       rules_from_identifier (identifier,
1130                              &rules, &rules_num,
1131                              &std_name, &dlt_name))
1132     {
1133       init_zone_from_rules (tz, rules, rules_num);
1134       g_free (rules);
1135       g_free (std_name);
1136       g_free (dlt_name);
1137     }
1138
1139   if (tz->t_info == NULL)
1140     {
1141 #ifdef G_OS_UNIX
1142       GBytes *zoneinfo = zone_info_unix (identifier);
1143       if (!zoneinfo)
1144         zone_for_constant_offset (tz, "UTC");
1145       else
1146         {
1147           init_zone_from_iana_info (tz, zoneinfo);
1148           g_bytes_unref (zoneinfo);
1149         }
1150 #elif defined G_OS_WIN32
1151 #endif
1152     }
1153
1154   if (tz->t_info != NULL)
1155     {
1156       if (identifier)
1157         g_hash_table_insert (time_zones, tz->name, tz);
1158     }
1159   g_atomic_int_inc (&tz->ref_count);
1160   G_UNLOCK (time_zones);
1161
1162   return tz;
1163 }
1164
1165 /**
1166  * g_time_zone_new_utc:
1167  *
1168  * Creates a #GTimeZone corresponding to UTC.
1169  *
1170  * This is equivalent to calling g_time_zone_new() with a value like
1171  * "Z", "UTC", "+00", etc.
1172  *
1173  * You should release the return value by calling g_time_zone_unref()
1174  * when you are done with it.
1175  *
1176  * Returns: the universal timezone
1177  *
1178  * Since: 2.26
1179  **/
1180 GTimeZone *
1181 g_time_zone_new_utc (void)
1182 {
1183   return g_time_zone_new ("UTC");
1184 }
1185
1186 /**
1187  * g_time_zone_new_local:
1188  *
1189  * Creates a #GTimeZone corresponding to local time.  The local time
1190  * zone may change between invocations to this function; for example,
1191  * if the system administrator changes it.
1192  *
1193  * This is equivalent to calling g_time_zone_new() with the value of the
1194  * <varname>TZ</varname> environment variable (including the possibility
1195  * of %NULL).
1196  *
1197  * You should release the return value by calling g_time_zone_unref()
1198  * when you are done with it.
1199  *
1200  * Returns: the local timezone
1201  *
1202  * Since: 2.26
1203  **/
1204 GTimeZone *
1205 g_time_zone_new_local (void)
1206 {
1207   return g_time_zone_new (getenv ("TZ"));
1208 }
1209
1210 #define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
1211 #define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
1212
1213 /* Internal helpers {{{1 */
1214 /* Note that interval 0 is *before* the first transition time, so
1215  * interval 1 gets transitions[0].
1216  */
1217 inline static const TransitionInfo*
1218 interval_info (GTimeZone *tz,
1219                guint      interval)
1220 {
1221   guint index;
1222   g_return_val_if_fail (tz->t_info != NULL, NULL);
1223   if (interval && tz->transitions && interval <= tz->transitions->len)
1224     index = (TRANSITION(interval - 1)).info_index;
1225   else
1226     index = 0;
1227   return &(TRANSITION_INFO(index));
1228 }
1229
1230 inline static gint64
1231 interval_start (GTimeZone *tz,
1232                 guint      interval)
1233 {
1234   if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
1235     return G_MININT64;
1236   if (interval > tz->transitions->len)
1237     interval = tz->transitions->len;
1238   return (TRANSITION(interval - 1)).time;
1239 }
1240
1241 inline static gint64
1242 interval_end (GTimeZone *tz,
1243               guint      interval)
1244 {
1245   if (tz->transitions && interval < tz->transitions->len)
1246     return (TRANSITION(interval)).time - 1;
1247   return G_MAXINT64;
1248 }
1249
1250 inline static gint32
1251 interval_offset (GTimeZone *tz,
1252                  guint      interval)
1253 {
1254   g_return_val_if_fail (tz->t_info != NULL, 0);
1255   return interval_info (tz, interval)->gmt_offset;
1256 }
1257
1258 inline static gboolean
1259 interval_isdst (GTimeZone *tz,
1260                 guint      interval)
1261 {
1262   g_return_val_if_fail (tz->t_info != NULL, 0);
1263   return interval_info (tz, interval)->is_dst;
1264 }
1265
1266
1267 inline static gboolean
1268 interval_isgmt (GTimeZone *tz,
1269                 guint      interval)
1270 {
1271   g_return_val_if_fail (tz->t_info != NULL, 0);
1272   return interval_info (tz, interval)->is_gmt;
1273 }
1274
1275 inline static gboolean
1276 interval_isstandard (GTimeZone *tz,
1277                 guint      interval)
1278 {
1279   return interval_info (tz, interval)->is_standard;
1280 }
1281
1282 inline static gchar*
1283 interval_abbrev (GTimeZone *tz,
1284                   guint      interval)
1285 {
1286   g_return_val_if_fail (tz->t_info != NULL, 0);
1287   return interval_info (tz, interval)->abbrev;
1288 }
1289
1290 inline static gint64
1291 interval_local_start (GTimeZone *tz,
1292                       guint      interval)
1293 {
1294   if (interval)
1295     return interval_start (tz, interval) + interval_offset (tz, interval);
1296
1297   return G_MININT64;
1298 }
1299
1300 inline static gint64
1301 interval_local_end (GTimeZone *tz,
1302                     guint      interval)
1303 {
1304   if (tz->transitions && interval < tz->transitions->len)
1305     return interval_end (tz, interval) + interval_offset (tz, interval);
1306
1307   return G_MAXINT64;
1308 }
1309
1310 static gboolean
1311 interval_valid (GTimeZone *tz,
1312                 guint      interval)
1313 {
1314   if ( tz->transitions == NULL)
1315     return interval == 0;
1316   return interval <= tz->transitions->len;
1317 }
1318
1319 /* g_time_zone_find_interval() {{{1 */
1320
1321 /**
1322  * g_time_zone_adjust_time:
1323  * @tz: a #GTimeZone
1324  * @type: the #GTimeType of @time_
1325  * @time_: a pointer to a number of seconds since January 1, 1970
1326  *
1327  * Finds an interval within @tz that corresponds to the given @time_,
1328  * possibly adjusting @time_ if required to fit into an interval.
1329  * The meaning of @time_ depends on @type.
1330  *
1331  * This function is similar to g_time_zone_find_interval(), with the
1332  * difference that it always succeeds (by making the adjustments
1333  * described below).
1334  *
1335  * In any of the cases where g_time_zone_find_interval() succeeds then
1336  * this function returns the same value, without modifying @time_.
1337  *
1338  * This function may, however, modify @time_ in order to deal with
1339  * non-existent times.  If the non-existent local @time_ of 02:30 were
1340  * requested on March 14th 2010 in Toronto then this function would
1341  * adjust @time_ to be 03:00 and return the interval containing the
1342  * adjusted time.
1343  *
1344  * Returns: the interval containing @time_, never -1
1345  *
1346  * Since: 2.26
1347  **/
1348 gint
1349 g_time_zone_adjust_time (GTimeZone *tz,
1350                          GTimeType  type,
1351                          gint64    *time_)
1352 {
1353   gint i;
1354   guint intervals;
1355
1356   if (tz->transitions == NULL)
1357     return 0;
1358
1359   intervals = tz->transitions->len;
1360
1361   /* find the interval containing *time UTC
1362    * TODO: this could be binary searched (or better) */
1363   for (i = 0; i <= intervals; i++)
1364     if (*time_ <= interval_end (tz, i))
1365       break;
1366
1367   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
1368
1369   if (type != G_TIME_TYPE_UNIVERSAL)
1370     {
1371       if (*time_ < interval_local_start (tz, i))
1372         /* if time came before the start of this interval... */
1373         {
1374           i--;
1375
1376           /* if it's not in the previous interval... */
1377           if (*time_ > interval_local_end (tz, i))
1378             {
1379               /* it doesn't exist.  fast-forward it. */
1380               i++;
1381               *time_ = interval_local_start (tz, i);
1382             }
1383         }
1384
1385       else if (*time_ > interval_local_end (tz, i))
1386         /* if time came after the end of this interval... */
1387         {
1388           i++;
1389
1390           /* if it's not in the next interval... */
1391           if (*time_ < interval_local_start (tz, i))
1392             /* it doesn't exist.  fast-forward it. */
1393             *time_ = interval_local_start (tz, i);
1394         }
1395
1396       else if (interval_isdst (tz, i) != type)
1397         /* it's in this interval, but dst flag doesn't match.
1398          * check neighbours for a better fit. */
1399         {
1400           if (i && *time_ <= interval_local_end (tz, i - 1))
1401             i--;
1402
1403           else if (i < intervals &&
1404                    *time_ >= interval_local_start (tz, i + 1))
1405             i++;
1406         }
1407     }
1408
1409   return i;
1410 }
1411
1412 /**
1413  * g_time_zone_find_interval:
1414  * @tz: a #GTimeZone
1415  * @type: the #GTimeType of @time_
1416  * @time_: a number of seconds since January 1, 1970
1417  *
1418  * Finds an the interval within @tz that corresponds to the given @time_.
1419  * The meaning of @time_ depends on @type.
1420  *
1421  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
1422  * succeed (since universal time is monotonic and continuous).
1423  *
1424  * Otherwise @time_ is treated is local time.  The distinction between
1425  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
1426  * the case that the given @time_ is ambiguous.  In Toronto, for example,
1427  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
1428  * savings time and the next, an hour later, outside of daylight savings
1429  * time).  In this case, the different value of @type would result in a
1430  * different interval being returned.
1431  *
1432  * It is still possible for this function to fail.  In Toronto, for
1433  * example, 02:00 on March 14th 2010 does not exist (due to the leap
1434  * forward to begin daylight savings time).  -1 is returned in that
1435  * case.
1436  *
1437  * Returns: the interval containing @time_, or -1 in case of failure
1438  *
1439  * Since: 2.26
1440  */
1441 gint
1442 g_time_zone_find_interval (GTimeZone *tz,
1443                            GTimeType  type,
1444                            gint64     time_)
1445 {
1446   gint i;
1447   guint intervals;
1448
1449   if (tz->transitions == NULL)
1450     return 0;
1451   intervals = tz->transitions->len;
1452   for (i = 0; i <= intervals; i++)
1453     if (time_ <= interval_end (tz, i))
1454       break;
1455
1456   if (type == G_TIME_TYPE_UNIVERSAL)
1457     return i;
1458
1459   if (time_ < interval_local_start (tz, i))
1460     {
1461       if (time_ > interval_local_end (tz, --i))
1462         return -1;
1463     }
1464
1465   else if (time_ > interval_local_end (tz, i))
1466     {
1467       if (time_ < interval_local_start (tz, ++i))
1468         return -1;
1469     }
1470
1471   else if (interval_isdst (tz, i) != type)
1472     {
1473       if (i && time_ <= interval_local_end (tz, i - 1))
1474         i--;
1475
1476       else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
1477         i++;
1478     }
1479
1480   return i;
1481 }
1482
1483 /* Public API accessors {{{1 */
1484
1485 /**
1486  * g_time_zone_get_abbreviation:
1487  * @tz: a #GTimeZone
1488  * @interval: an interval within the timezone
1489  *
1490  * Determines the time zone abbreviation to be used during a particular
1491  * @interval of time in the time zone @tz.
1492  *
1493  * For example, in Toronto this is currently "EST" during the winter
1494  * months and "EDT" during the summer months when daylight savings time
1495  * is in effect.
1496  *
1497  * Returns: the time zone abbreviation, which belongs to @tz
1498  *
1499  * Since: 2.26
1500  **/
1501 const gchar *
1502 g_time_zone_get_abbreviation (GTimeZone *tz,
1503                               gint       interval)
1504 {
1505   g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
1506
1507   return interval_abbrev (tz, (guint)interval);
1508 }
1509
1510 /**
1511  * g_time_zone_get_offset:
1512  * @tz: a #GTimeZone
1513  * @interval: an interval within the timezone
1514  *
1515  * Determines the offset to UTC in effect during a particular @interval
1516  * of time in the time zone @tz.
1517  *
1518  * The offset is the number of seconds that you add to UTC time to
1519  * arrive at local time for @tz (ie: negative numbers for time zones
1520  * west of GMT, positive numbers for east).
1521  *
1522  * Returns: the number of seconds that should be added to UTC to get the
1523  *          local time in @tz
1524  *
1525  * Since: 2.26
1526  **/
1527 gint32
1528 g_time_zone_get_offset (GTimeZone *tz,
1529                         gint       interval)
1530 {
1531   g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
1532
1533   return interval_offset (tz, (guint)interval);
1534 }
1535
1536 /**
1537  * g_time_zone_is_dst:
1538  * @tz: a #GTimeZone
1539  * @interval: an interval within the timezone
1540  *
1541  * Determines if daylight savings time is in effect during a particular
1542  * @interval of time in the time zone @tz.
1543  *
1544  * Returns: %TRUE if daylight savings time is in effect
1545  *
1546  * Since: 2.26
1547  **/
1548 gboolean
1549 g_time_zone_is_dst (GTimeZone *tz,
1550                     gint       interval)
1551 {
1552   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
1553
1554   if (tz->transitions == NULL)
1555     return FALSE;
1556
1557   return interval_isdst (tz, (guint)interval);
1558 }
1559
1560 /* Epilogue {{{1 */
1561 /* vim:set foldmethod=marker: */