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