Bug 631382 - GTimeZone support for zoneinfo version 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 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 "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
39 /**
40  * SECTION:timezone
41  * @title: GTimeZone
42  * @short_description: a structure representing a time zone
43  * @see_also: #GDateTime
44  *
45  * #GTimeZone is a structure that represents a time zone, at no
46  * particular point in time.  It is refcounted and immutable.
47  *
48  * A time zone contains a number of intervals.  Each interval has
49  * an abbreviation to describe it, an offet to UTC and a flag indicating
50  * if the daylight savings time is in effect during that interval.  A
51  * time zone always has at least one interval -- interval 0.
52  *
53  * Every UTC time is contained within exactly one interval, but a given
54  * local time may be contained within zero, one or two intervals (due to
55  * incontinuities associated with daylight savings time).
56  *
57  * An interval may refer to a specific period of time (eg: the duration
58  * of daylight savings time during 2010) or it may refer to many periods
59  * of time that share the same properties (eg: all periods of daylight
60  * savings time).  It is also possible (usually for political reasons)
61  * that some properties (like the abbreviation) change between intervals
62  * without other properties changing.
63  *
64  * #GTimeZone is available since GLib 2.26.
65  */
66
67 /**
68  * GTimeZone:
69  *
70  * #GDateTime is an opaque structure whose members cannot be accessed
71  * directly.
72  *
73  * Since: 2.26
74  **/
75
76 /* zoneinfo file format {{{1 */
77
78 /* unaligned */
79 typedef struct { gchar bytes[8]; } gint64_be;
80 typedef struct { gchar bytes[4]; } gint32_be;
81 typedef struct { gchar bytes[4]; } guint32_be;
82
83 static inline gint64 gint64_from_be (const gint64_be be) {
84   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
85 }
86
87 static inline gint32 gint32_from_be (const gint32_be be) {
88   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
89 }
90
91 static inline guint32 guint32_from_be (const guint32_be be) {
92   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
93 }
94
95 struct tzhead
96 {
97   gchar      tzh_magic[4];
98   gchar      tzh_version;
99   guchar     tzh_reserved[15];
100
101   guint32_be tzh_ttisgmtcnt;
102   guint32_be tzh_ttisstdcnt;
103   guint32_be tzh_leapcnt;
104   guint32_be tzh_timecnt;
105   guint32_be tzh_typecnt;
106   guint32_be tzh_charcnt;
107 };
108
109 struct ttinfo
110 {
111   gint32_be tt_gmtoff;
112   guint8    tt_isdst;
113   guint8    tt_abbrind;
114 };
115
116 /* GTimeZone structure and lifecycle {{{1 */
117 struct _GTimeZone
118 {
119   gchar   *name;
120   gchar    version;
121   GBytes  *zoneinfo;
122
123   const struct tzhead *header;
124   const struct ttinfo *infos;
125   union
126   {
127     const gint32_be     *one;
128     const gint64_be     *two;
129   } trans;
130   const guint8        *indices;
131   const gchar         *abbrs;
132   gint                 timecnt;
133
134   gint     ref_count;
135 };
136
137 G_LOCK_DEFINE_STATIC (time_zones);
138 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
139
140 /**
141  * g_time_zone_unref:
142  * @tz: a #GTimeZone
143  *
144  * Decreases the reference count on @tz.
145  *
146  * Since: 2.26
147  **/
148 void
149 g_time_zone_unref (GTimeZone *tz)
150 {
151   int ref_count;
152
153 again:
154   ref_count = g_atomic_int_get (&tz->ref_count);
155
156   g_assert (ref_count > 0);
157
158   if (ref_count == 1)
159     {
160       if (tz->name != NULL)
161         {
162           G_LOCK(time_zones);
163
164           /* someone else might have grabbed a ref in the meantime */
165           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
166             {
167               G_UNLOCK(time_zones);
168               goto again;
169             }
170
171           g_hash_table_remove (time_zones, tz->name);
172           G_UNLOCK(time_zones);
173         }
174
175       if (tz->zoneinfo)
176         g_bytes_unref (tz->zoneinfo);
177
178       g_free (tz->name);
179
180       g_slice_free (GTimeZone, tz);
181     }
182
183   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
184                                                           ref_count,
185                                                           ref_count - 1))
186     goto again;
187 }
188
189 /**
190  * g_time_zone_ref:
191  * @tz: a #GTimeZone
192  *
193  * Increases the reference count on @tz.
194  *
195  * Returns: a new reference to @tz.
196  *
197  * Since: 2.26
198  **/
199 GTimeZone *
200 g_time_zone_ref (GTimeZone *tz)
201 {
202   g_assert (tz->ref_count > 0);
203
204   g_atomic_int_inc (&tz->ref_count);
205
206   return tz;
207 }
208
209 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
210 /*
211  * parses strings of the form 'hh' 'hhmm' or 'hh:mm' where:
212  *  - hh is 00 to 23
213  *  - mm is 00 to 59
214  */
215 static gboolean
216 parse_time (const gchar *time_,
217             gint32      *offset)
218 {
219   if (*time_ < '0' || '2' < *time_)
220     return FALSE;
221
222   *offset = 10 * 60 * 60 * (*time_++ - '0');
223
224   if (*time_ < '0' || '9' < *time_)
225     return FALSE;
226
227   *offset += 60 * 60 * (*time_++ - '0');
228
229   if (*offset > 23 * 60 * 60)
230     return FALSE;
231
232   if (*time_ == '\0')
233     return TRUE;
234
235   if (*time_ == ':')
236     time_++;
237
238   if (*time_ < '0' || '5' < *time_)
239     return FALSE;
240
241   *offset += 10 * 60 * (*time_++ - '0');
242
243   if (*time_ < '0' || '9' < *time_)
244     return FALSE;
245
246   *offset += 60 * (*time_++ - '0');
247
248   return *time_ == '\0';
249 }
250
251 static gboolean
252 parse_constant_offset (const gchar *name,
253                        gint32      *offset)
254 {
255   switch (*name++)
256     {
257     case 'Z':
258       *offset = 0;
259       return !*name;
260
261     case '+':
262       return parse_time (name, offset);
263
264     case '-':
265       if (parse_time (name, offset))
266         {
267           *offset = -*offset;
268           return TRUE;
269         }
270
271     default:
272       return FALSE;
273     }
274 }
275
276 static GBytes *
277 zone_for_constant_offset (const gchar *name)
278 {
279   const gchar fake_zoneinfo_headers[] =
280     "TZif" "2..." "...." "...." "...."
281     "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0"
282     "TZif" "2..." "...." "...." "...."
283     "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\1" "\0\0\0\7";
284   struct {
285     struct tzhead headers[2];
286     struct ttinfo info;
287     gchar abbr[8];
288   } *fake;
289   gint32 offset;
290
291   if (name == NULL || !parse_constant_offset (name, &offset))
292     return NULL;
293
294   offset = GINT32_TO_BE (offset);
295
296   fake = g_malloc (sizeof *fake);
297   memcpy (fake, fake_zoneinfo_headers, sizeof fake_zoneinfo_headers);
298   memcpy (&fake->info.tt_gmtoff, &offset, sizeof offset);
299   fake->info.tt_isdst = FALSE;
300   fake->info.tt_abbrind = 0;
301   strcpy (fake->abbr, name);
302
303   return g_bytes_new_take (fake, sizeof *fake);
304 }
305
306 /* Construction {{{1 */
307 /**
308  * g_time_zone_new:
309  * @identifier: (allow-none): a timezone identifier
310  *
311  * Creates a #GTimeZone corresponding to @identifier.
312  *
313  * @identifier can either be an RFC3339/ISO 8601 time offset or
314  * something that would pass as a valid value for the
315  * <varname>TZ</varname> environment variable (including %NULL).
316  *
317  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
318  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
319  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.
320  *
321  * The <varname>TZ</varname> environment variable typically corresponds
322  * to the name of a file in the zoneinfo database, but there are many
323  * other possibilities.  Note that those other possibilities are not
324  * currently implemented, but are planned.
325  *
326  * g_time_zone_new_local() calls this function with the value of the
327  * <varname>TZ</varname> environment variable.  This function itself is
328  * independent of the value of <varname>TZ</varname>, but if @identifier
329  * is %NULL then <filename>/etc/localtime</filename> will be consulted
330  * to discover the correct timezone.
331  *
332  * See <ulink
333  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
334  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
335  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
336  * full list of valid time offsets.  See <ulink
337  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
338  * GNU C Library manual</ulink> for an explanation of the possible
339  * values of the <varname>TZ</varname> environment variable.
340  *
341  * You should release the return value by calling g_time_zone_unref()
342  * when you are done with it.
343  *
344  * Returns: the requested timezone
345  *
346  * Since: 2.26
347  **/
348 GTimeZone *
349 g_time_zone_new (const gchar *identifier)
350 {
351   GTimeZone *tz;
352   GMappedFile *file;
353
354   G_LOCK (time_zones);
355   if (time_zones == NULL)
356     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
357
358   if (identifier)
359     tz = g_hash_table_lookup (time_zones, identifier);
360   else
361     tz = NULL;
362
363   if (tz == NULL)
364     {
365       tz = g_slice_new0 (GTimeZone);
366       tz->name = g_strdup (identifier);
367       tz->ref_count = 0;
368
369       tz->zoneinfo = zone_for_constant_offset (identifier);
370
371       if (tz->zoneinfo == NULL)
372         {
373           gchar *filename;
374
375           /* identifier can be a relative or absolute path name;
376              if relative, it is interpreted starting from /usr/share/timezone
377              while the POSIX standard says it should start with :,
378              glibc allows both syntaxes, so we should too */
379           if (identifier != NULL)
380             {
381               const gchar *tzdir;
382
383               tzdir = getenv ("TZDIR");
384               if (tzdir == NULL)
385                 tzdir = "/usr/share/zoneinfo";
386
387               if (*identifier == ':')
388                 identifier ++;
389
390               if (g_path_is_absolute (identifier))
391                 filename = g_strdup (identifier);
392               else
393                 filename = g_build_filename (tzdir, identifier, NULL);
394             }
395           else
396             filename = g_strdup ("/etc/localtime");
397
398           file = g_mapped_file_new (filename, FALSE, NULL);
399           if (file != NULL)
400             {
401               tz->zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
402                                                          g_mapped_file_get_length (file),
403                                                          (GDestroyNotify)g_mapped_file_unref,
404                                                          g_mapped_file_ref (file));
405               g_mapped_file_unref (file);
406             }
407           g_free (filename);
408         }
409
410       if (tz->zoneinfo != NULL)
411         {
412           gsize size;
413           const struct tzhead *header = g_bytes_get_data (tz->zoneinfo, &size);
414
415           if (size < sizeof (struct tzhead) || memcmp (header, "TZif", 4))
416             {
417               g_bytes_unref (tz->zoneinfo);
418               tz->zoneinfo = NULL;
419             }
420           else
421             {
422               gint typecnt;
423               tz->version = header->tzh_version;
424               /* we trust the file completely. */
425               if (tz->version == '2')
426                 tz->header = (const struct tzhead *)
427                   (((const gchar *) (header + 1)) +
428                    guint32_from_be(header->tzh_ttisgmtcnt) +
429                    guint32_from_be(header->tzh_ttisstdcnt) +
430                    8 * guint32_from_be(header->tzh_leapcnt) +
431                    5 * guint32_from_be(header->tzh_timecnt) +
432                    6 * guint32_from_be(header->tzh_typecnt) +
433                    guint32_from_be(header->tzh_charcnt));
434               else
435                 tz->header = header;
436
437               typecnt     = guint32_from_be (tz->header->tzh_typecnt);
438               tz->timecnt = guint32_from_be (tz->header->tzh_timecnt);
439               if (tz->version == '2')
440                 {
441                   tz->trans.two = (gconstpointer) (tz->header + 1);
442                   tz->indices   = (gconstpointer) (tz->trans.two + tz->timecnt);
443                 }
444               else
445                 {
446                   tz->trans.one = (gconstpointer) (tz->header + 1);
447                   tz->indices   = (gconstpointer) (tz->trans.one + tz->timecnt);
448                 }
449               tz->infos   = (gconstpointer) (tz->indices + tz->timecnt);
450               tz->abbrs   = (gconstpointer) (tz->infos + typecnt);
451             }
452         }
453
454       if (identifier)
455         g_hash_table_insert (time_zones, tz->name, tz);
456     }
457   g_atomic_int_inc (&tz->ref_count);
458   G_UNLOCK (time_zones);
459
460   return tz;
461 }
462
463 /**
464  * g_time_zone_new_utc:
465  *
466  * Creates a #GTimeZone corresponding to UTC.
467  *
468  * This is equivalent to calling g_time_zone_new() with a value like
469  * "Z", "UTC", "+00", etc.
470  *
471  * You should release the return value by calling g_time_zone_unref()
472  * when you are done with it.
473  *
474  * Returns: the universal timezone
475  *
476  * Since: 2.26
477  **/
478 GTimeZone *
479 g_time_zone_new_utc (void)
480 {
481   return g_time_zone_new ("UTC");
482 }
483
484 /**
485  * g_time_zone_new_local:
486  *
487  * Creates a #GTimeZone corresponding to local time.  The local time
488  * zone may change between invocations to this function; for example,
489  * if the system administrator changes it.
490  *
491  * This is equivalent to calling g_time_zone_new() with the value of the
492  * <varname>TZ</varname> environment variable (including the possibility
493  * of %NULL).
494  *
495  * You should release the return value by calling g_time_zone_unref()
496  * when you are done with it.
497  *
498  * Returns: the local timezone
499  *
500  * Since: 2.26
501  **/
502 GTimeZone *
503 g_time_zone_new_local (void)
504 {
505   return g_time_zone_new (getenv ("TZ"));
506 }
507
508 /* Internal helpers {{{1 */
509 inline static const struct ttinfo *
510 interval_info (GTimeZone *tz,
511                gint       interval)
512 {
513   if (interval)
514     return tz->infos + tz->indices[interval - 1];
515
516   return tz->infos;
517 }
518
519 inline static gint64
520 interval_start (GTimeZone *tz,
521                 gint       interval)
522 {
523   if (interval)
524     {
525       if (tz->version == '2')
526         return gint64_from_be (tz->trans.two[interval - 1]);
527       else
528         return gint32_from_be (tz->trans.one[interval - 1]);
529     }
530   return G_MININT64;
531 }
532
533 inline static gint64
534 interval_end (GTimeZone *tz,
535               gint       interval)
536 {
537   if (interval < tz->timecnt)
538     {
539       if (tz->version == '2')
540         return gint64_from_be (tz->trans.two[interval]) - 1;
541       else
542         return gint32_from_be (tz->trans.one[interval]) - 1;
543     }
544   return G_MAXINT64;
545 }
546
547 inline static gint32
548 interval_offset (GTimeZone *tz,
549                  gint       interval)
550 {
551   return gint32_from_be (interval_info (tz, interval)->tt_gmtoff);
552 }
553
554 inline static gboolean
555 interval_isdst (GTimeZone *tz,
556                 gint       interval)
557 {
558   return interval_info (tz, interval)->tt_isdst;
559 }
560
561 inline static guint8
562 interval_abbrind (GTimeZone *tz,
563                   gint       interval)
564 {
565   return interval_info (tz, interval)->tt_abbrind;
566 }
567
568 inline static gint64
569 interval_local_start (GTimeZone *tz,
570                       gint       interval)
571 {
572   if (interval)
573     return interval_start (tz, interval) + interval_offset (tz, interval);
574
575   return G_MININT64;
576 }
577
578 inline static gint64
579 interval_local_end (GTimeZone *tz,
580                     gint       interval)
581 {
582   if (interval < tz->timecnt)
583     return interval_end (tz, interval) + interval_offset (tz, interval);
584
585   return G_MAXINT64;
586 }
587
588 static gboolean
589 interval_valid (GTimeZone *tz,
590                 gint       interval)
591 {
592   return interval <= tz->timecnt;
593 }
594
595 /* g_time_zone_find_interval() {{{1 */
596
597 /**
598  * g_time_zone_adjust_time:
599  * @tz: a #GTimeZone
600  * @type: the #GTimeType of @time_
601  * @time_: a pointer to a number of seconds since January 1, 1970
602  *
603  * Finds an interval within @tz that corresponds to the given @time_,
604  * possibly adjusting @time_ if required to fit into an interval.
605  * The meaning of @time_ depends on @type.
606  *
607  * This function is similar to g_time_zone_find_interval(), with the
608  * difference that it always succeeds (by making the adjustments
609  * described below).
610  *
611  * In any of the cases where g_time_zone_find_interval() succeeds then
612  * this function returns the same value, without modifying @time_.
613  *
614  * This function may, however, modify @time_ in order to deal with
615  * non-existent times.  If the non-existent local @time_ of 02:30 were
616  * requested on March 14th 2010 in Toronto then this function would
617  * adjust @time_ to be 03:00 and return the interval containing the
618  * adjusted time.
619  *
620  * Returns: the interval containing @time_, never -1
621  *
622  * Since: 2.26
623  **/
624 gint
625 g_time_zone_adjust_time (GTimeZone *tz,
626                          GTimeType  type,
627                          gint64    *time_)
628 {
629   gint i;
630
631   if (tz->zoneinfo == NULL)
632     return 0;
633
634   /* find the interval containing *time UTC
635    * TODO: this could be binary searched (or better) */
636   for (i = 0; i < tz->timecnt; i++)
637     if (*time_ <= interval_end (tz, i))
638       break;
639
640   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
641
642   if (type != G_TIME_TYPE_UNIVERSAL)
643     {
644       if (*time_ < interval_local_start (tz, i))
645         /* if time came before the start of this interval... */
646         {
647           i--;
648
649           /* if it's not in the previous interval... */
650           if (*time_ > interval_local_end (tz, i))
651             {
652               /* it doesn't exist.  fast-forward it. */
653               i++;
654               *time_ = interval_local_start (tz, i);
655             }
656         }
657
658       else if (*time_ > interval_local_end (tz, i))
659         /* if time came after the end of this interval... */
660         {
661           i++;
662
663           /* if it's not in the next interval... */
664           if (*time_ < interval_local_start (tz, i))
665             /* it doesn't exist.  fast-forward it. */
666             *time_ = interval_local_start (tz, i);
667         }
668
669       else if (interval_isdst (tz, i) != type)
670         /* it's in this interval, but dst flag doesn't match.
671          * check neighbours for a better fit. */
672         {
673           if (i && *time_ <= interval_local_end (tz, i - 1))
674             i--;
675
676           else if (i < tz->timecnt &&
677                    *time_ >= interval_local_start (tz, i + 1))
678             i++;
679         }
680     }
681
682   return i;
683 }
684
685 /**
686  * g_time_zone_find_interval:
687  * @tz: a #GTimeZone
688  * @type: the #GTimeType of @time_
689  * @time_: a number of seconds since January 1, 1970
690  *
691  * Finds an the interval within @tz that corresponds to the given @time_.
692  * The meaning of @time_ depends on @type.
693  *
694  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
695  * succeed (since universal time is monotonic and continuous).
696  *
697  * Otherwise @time_ is treated is local time.  The distinction between
698  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
699  * the case that the given @time_ is ambiguous.  In Toronto, for example,
700  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
701  * savings time and the next, an hour later, outside of daylight savings
702  * time).  In this case, the different value of @type would result in a
703  * different interval being returned.
704  *
705  * It is still possible for this function to fail.  In Toronto, for
706  * example, 02:00 on March 14th 2010 does not exist (due to the leap
707  * forward to begin daylight savings time).  -1 is returned in that
708  * case.
709  *
710  * Returns: the interval containing @time_, or -1 in case of failure
711  *
712  * Since: 2.26
713  */
714 gint
715 g_time_zone_find_interval (GTimeZone *tz,
716                            GTimeType  type,
717                            gint64     time_)
718 {
719   gint i;
720
721   if (tz->zoneinfo == NULL)
722     return 0;
723
724   for (i = 0; i < tz->timecnt; i++)
725     if (time_ <= interval_end (tz, i))
726       break;
727
728   if (type == G_TIME_TYPE_UNIVERSAL)
729     return i;
730
731   if (time_ < interval_local_start (tz, i))
732     {
733       if (time_ > interval_local_end (tz, --i))
734         return -1;
735     }
736
737   else if (time_ > interval_local_end (tz, i))
738     {
739       if (time_ < interval_local_start (tz, ++i))
740         return -1;
741     }
742
743   else if (interval_isdst (tz, i) != type)
744     {
745       if (i && time_ <= interval_local_end (tz, i - 1))
746         i--;
747
748       else if (i < tz->timecnt && time_ >= interval_local_start (tz, i + 1))
749         i++;
750     }
751
752   return i;
753 }
754
755 /* Public API accessors {{{1 */
756
757 /**
758  * g_time_zone_get_abbreviation:
759  * @tz: a #GTimeZone
760  * @interval: an interval within the timezone
761  *
762  * Determines the time zone abbreviation to be used during a particular
763  * @interval of time in the time zone @tz.
764  *
765  * For example, in Toronto this is currently "EST" during the winter
766  * months and "EDT" during the summer months when daylight savings time
767  * is in effect.
768  *
769  * Returns: the time zone abbreviation, which belongs to @tz
770  *
771  * Since: 2.26
772  **/
773 const gchar *
774 g_time_zone_get_abbreviation (GTimeZone *tz,
775                               gint       interval)
776 {
777   g_return_val_if_fail (interval_valid (tz, interval), NULL);
778
779   if (tz->header == NULL)
780     return "UTC";
781
782   return tz->abbrs + interval_abbrind (tz, interval);
783 }
784
785 /**
786  * g_time_zone_get_offset:
787  * @tz: a #GTimeZone
788  * @interval: an interval within the timezone
789  *
790  * Determines the offset to UTC in effect during a particular @interval
791  * of time in the time zone @tz.
792  *
793  * The offset is the number of seconds that you add to UTC time to
794  * arrive at local time for @tz (ie: negative numbers for time zones
795  * west of GMT, positive numbers for east).
796  *
797  * Returns: the number of seconds that should be added to UTC to get the
798  *          local time in @tz
799  *
800  * Since: 2.26
801  **/
802 gint32
803 g_time_zone_get_offset (GTimeZone *tz,
804                         gint       interval)
805 {
806   g_return_val_if_fail (interval_valid (tz, interval), 0);
807
808   if (tz->header == NULL)
809     return 0;
810
811   return interval_offset (tz, interval);
812 }
813
814 /**
815  * g_time_zone_is_dst:
816  * @tz: a #GTimeZone
817  * @interval: an interval within the timezone
818  *
819  * Determines if daylight savings time is in effect during a particular
820  * @interval of time in the time zone @tz.
821  *
822  * Returns: %TRUE if daylight savings time is in effect
823  *
824  * Since: 2.26
825  **/
826 gboolean
827 g_time_zone_is_dst (GTimeZone *tz,
828                     gint       interval)
829 {
830   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
831
832   if (tz->header == NULL)
833     return FALSE;
834
835   return interval_isdst (tz, interval);
836 }
837
838 /* Epilogue {{{1 */
839 /* vim:set foldmethod=marker: */