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