Extract function init_zone_from_iana_info
[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 static GBytes*
309 zone_info_unix (const gchar *identifier)
310 {
311   gchar *filename;
312   GMappedFile *file = NULL;
313   GBytes *zoneinfo = NULL;
314
315   /* identifier can be a relative or absolute path name;
316      if relative, it is interpreted starting from /usr/share/zoneinfo
317      while the POSIX standard says it should start with :,
318      glibc allows both syntaxes, so we should too */
319   if (identifier != NULL)
320     {
321       const gchar *tzdir;
322
323       tzdir = getenv ("TZDIR");
324       if (tzdir == NULL)
325         tzdir = "/usr/share/zoneinfo";
326
327       if (*identifier == ':')
328         identifier ++;
329
330       if (g_path_is_absolute (identifier))
331         filename = g_strdup (identifier);
332       else
333         filename = g_build_filename (tzdir, identifier, NULL);
334     }
335   else
336     filename = g_strdup ("/etc/localtime");
337
338   file = g_mapped_file_new (filename, FALSE, NULL);
339   if (file != NULL)
340     {
341       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
342                                              g_mapped_file_get_length (file),
343                                              (GDestroyNotify)g_mapped_file_unref,
344                                              g_mapped_file_ref (file));
345       g_mapped_file_unref (file);
346     }
347   g_free (filename);
348   return zoneinfo;
349 }
350
351 static void
352 init_zone_from_iana_info (GTimeZone *tz)
353 {
354   gsize size;
355   const struct tzhead *header = g_bytes_get_data (tz->zoneinfo, &size);
356
357   if (size < sizeof (struct tzhead) || memcmp (header, "TZif", 4))
358     {
359       g_bytes_unref (tz->zoneinfo);
360       tz->zoneinfo = NULL;
361     }
362   else
363     {
364       gint typecnt;
365       tz->version = header->tzh_version;
366       /* we trust the file completely. */
367       if (tz->version == '2')
368         tz->header = (const struct tzhead *)
369           (((const gchar *) (header + 1)) +
370            guint32_from_be(header->tzh_ttisgmtcnt) +
371            guint32_from_be(header->tzh_ttisstdcnt) +
372            8 * guint32_from_be(header->tzh_leapcnt) +
373            5 * guint32_from_be(header->tzh_timecnt) +
374            6 * guint32_from_be(header->tzh_typecnt) +
375            guint32_from_be(header->tzh_charcnt));
376       else
377         tz->header = header;
378
379       typecnt     = guint32_from_be (tz->header->tzh_typecnt);
380       tz->timecnt = guint32_from_be (tz->header->tzh_timecnt);
381       if (tz->version == '2')
382         {
383           tz->trans.two = (gconstpointer) (tz->header + 1);
384           tz->indices   = (gconstpointer) (tz->trans.two + tz->timecnt);
385         }
386       else
387         {
388           tz->trans.one = (gconstpointer) (tz->header + 1);
389           tz->indices   = (gconstpointer) (tz->trans.one + tz->timecnt);
390         }
391       tz->infos   = (gconstpointer) (tz->indices + tz->timecnt);
392       tz->abbrs   = (gconstpointer) (tz->infos + typecnt);
393     }
394 }
395
396
397 /* Construction {{{1 */
398 /**
399  * g_time_zone_new:
400  * @identifier: (allow-none): a timezone identifier
401  *
402  * Creates a #GTimeZone corresponding to @identifier.
403  *
404  * @identifier can either be an RFC3339/ISO 8601 time offset or
405  * something that would pass as a valid value for the
406  * <varname>TZ</varname> environment variable (including %NULL).
407  *
408  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
409  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
410  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.
411  *
412  * The <varname>TZ</varname> environment variable typically corresponds
413  * to the name of a file in the zoneinfo database, but there are many
414  * other possibilities.  Note that those other possibilities are not
415  * currently implemented, but are planned.
416  *
417  * g_time_zone_new_local() calls this function with the value of the
418  * <varname>TZ</varname> environment variable.  This function itself is
419  * independent of the value of <varname>TZ</varname>, but if @identifier
420  * is %NULL then <filename>/etc/localtime</filename> will be consulted
421  * to discover the correct timezone.
422  *
423  * See <ulink
424  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
425  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
426  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
427  * full list of valid time offsets.  See <ulink
428  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
429  * GNU C Library manual</ulink> for an explanation of the possible
430  * values of the <varname>TZ</varname> environment variable.
431  *
432  * You should release the return value by calling g_time_zone_unref()
433  * when you are done with it.
434  *
435  * Returns: the requested timezone
436  *
437  * Since: 2.26
438  **/
439 GTimeZone *
440 g_time_zone_new (const gchar *identifier)
441 {
442   GTimeZone *tz;
443   GMappedFile *file;
444
445   G_LOCK (time_zones);
446   if (time_zones == NULL)
447     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
448
449   if (identifier)
450     tz = g_hash_table_lookup (time_zones, identifier);
451   else
452     tz = NULL;
453
454   if (tz == NULL)
455     {
456       tz = g_slice_new0 (GTimeZone);
457       tz->name = g_strdup (identifier);
458       tz->ref_count = 0;
459
460       tz->zoneinfo = zone_for_constant_offset (identifier);
461
462       if (tz->zoneinfo == NULL)
463         tz->zoneinfo = zone_info_unix (identifier);
464
465       if (tz->zoneinfo != NULL)
466         init_zone_from_iana_info (tz);
467       if (identifier)
468         g_hash_table_insert (time_zones, tz->name, tz);
469     }
470   g_atomic_int_inc (&tz->ref_count);
471   G_UNLOCK (time_zones);
472
473   return tz;
474 }
475
476 /**
477  * g_time_zone_new_utc:
478  *
479  * Creates a #GTimeZone corresponding to UTC.
480  *
481  * This is equivalent to calling g_time_zone_new() with a value like
482  * "Z", "UTC", "+00", etc.
483  *
484  * You should release the return value by calling g_time_zone_unref()
485  * when you are done with it.
486  *
487  * Returns: the universal timezone
488  *
489  * Since: 2.26
490  **/
491 GTimeZone *
492 g_time_zone_new_utc (void)
493 {
494   return g_time_zone_new ("UTC");
495 }
496
497 /**
498  * g_time_zone_new_local:
499  *
500  * Creates a #GTimeZone corresponding to local time.  The local time
501  * zone may change between invocations to this function; for example,
502  * if the system administrator changes it.
503  *
504  * This is equivalent to calling g_time_zone_new() with the value of the
505  * <varname>TZ</varname> environment variable (including the possibility
506  * of %NULL).
507  *
508  * You should release the return value by calling g_time_zone_unref()
509  * when you are done with it.
510  *
511  * Returns: the local timezone
512  *
513  * Since: 2.26
514  **/
515 GTimeZone *
516 g_time_zone_new_local (void)
517 {
518   return g_time_zone_new (getenv ("TZ"));
519 }
520
521 /* Internal helpers {{{1 */
522 inline static const struct ttinfo *
523 interval_info (GTimeZone *tz,
524                gint       interval)
525 {
526   if (interval)
527     return tz->infos + tz->indices[interval - 1];
528
529   return tz->infos;
530 }
531
532 inline static gint64
533 interval_start (GTimeZone *tz,
534                 gint       interval)
535 {
536   if (interval)
537     {
538       if (tz->version == '2')
539         return gint64_from_be (tz->trans.two[interval - 1]);
540       else
541         return gint32_from_be (tz->trans.one[interval - 1]);
542     }
543   return G_MININT64;
544 }
545
546 inline static gint64
547 interval_end (GTimeZone *tz,
548               gint       interval)
549 {
550   if (interval < tz->timecnt)
551     {
552       if (tz->version == '2')
553         return gint64_from_be (tz->trans.two[interval]) - 1;
554       else
555         return gint32_from_be (tz->trans.one[interval]) - 1;
556     }
557   return G_MAXINT64;
558 }
559
560 inline static gint32
561 interval_offset (GTimeZone *tz,
562                  gint       interval)
563 {
564   return gint32_from_be (interval_info (tz, interval)->tt_gmtoff);
565 }
566
567 inline static gboolean
568 interval_isdst (GTimeZone *tz,
569                 gint       interval)
570 {
571   return interval_info (tz, interval)->tt_isdst;
572 }
573
574 inline static guint8
575 interval_abbrind (GTimeZone *tz,
576                   gint       interval)
577 {
578   return interval_info (tz, interval)->tt_abbrind;
579 }
580
581 inline static gint64
582 interval_local_start (GTimeZone *tz,
583                       gint       interval)
584 {
585   if (interval)
586     return interval_start (tz, interval) + interval_offset (tz, interval);
587
588   return G_MININT64;
589 }
590
591 inline static gint64
592 interval_local_end (GTimeZone *tz,
593                     gint       interval)
594 {
595   if (interval < tz->timecnt)
596     return interval_end (tz, interval) + interval_offset (tz, interval);
597
598   return G_MAXINT64;
599 }
600
601 static gboolean
602 interval_valid (GTimeZone *tz,
603                 gint       interval)
604 {
605   return interval <= tz->timecnt;
606 }
607
608 /* g_time_zone_find_interval() {{{1 */
609
610 /**
611  * g_time_zone_adjust_time:
612  * @tz: a #GTimeZone
613  * @type: the #GTimeType of @time_
614  * @time_: a pointer to a number of seconds since January 1, 1970
615  *
616  * Finds an interval within @tz that corresponds to the given @time_,
617  * possibly adjusting @time_ if required to fit into an interval.
618  * The meaning of @time_ depends on @type.
619  *
620  * This function is similar to g_time_zone_find_interval(), with the
621  * difference that it always succeeds (by making the adjustments
622  * described below).
623  *
624  * In any of the cases where g_time_zone_find_interval() succeeds then
625  * this function returns the same value, without modifying @time_.
626  *
627  * This function may, however, modify @time_ in order to deal with
628  * non-existent times.  If the non-existent local @time_ of 02:30 were
629  * requested on March 14th 2010 in Toronto then this function would
630  * adjust @time_ to be 03:00 and return the interval containing the
631  * adjusted time.
632  *
633  * Returns: the interval containing @time_, never -1
634  *
635  * Since: 2.26
636  **/
637 gint
638 g_time_zone_adjust_time (GTimeZone *tz,
639                          GTimeType  type,
640                          gint64    *time_)
641 {
642   gint i;
643
644   if (tz->zoneinfo == NULL)
645     return 0;
646
647   /* find the interval containing *time UTC
648    * TODO: this could be binary searched (or better) */
649   for (i = 0; i < tz->timecnt; i++)
650     if (*time_ <= interval_end (tz, i))
651       break;
652
653   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
654
655   if (type != G_TIME_TYPE_UNIVERSAL)
656     {
657       if (*time_ < interval_local_start (tz, i))
658         /* if time came before the start of this interval... */
659         {
660           i--;
661
662           /* if it's not in the previous interval... */
663           if (*time_ > interval_local_end (tz, i))
664             {
665               /* it doesn't exist.  fast-forward it. */
666               i++;
667               *time_ = interval_local_start (tz, i);
668             }
669         }
670
671       else if (*time_ > interval_local_end (tz, i))
672         /* if time came after the end of this interval... */
673         {
674           i++;
675
676           /* if it's not in the next interval... */
677           if (*time_ < interval_local_start (tz, i))
678             /* it doesn't exist.  fast-forward it. */
679             *time_ = interval_local_start (tz, i);
680         }
681
682       else if (interval_isdst (tz, i) != type)
683         /* it's in this interval, but dst flag doesn't match.
684          * check neighbours for a better fit. */
685         {
686           if (i && *time_ <= interval_local_end (tz, i - 1))
687             i--;
688
689           else if (i < tz->timecnt &&
690                    *time_ >= interval_local_start (tz, i + 1))
691             i++;
692         }
693     }
694
695   return i;
696 }
697
698 /**
699  * g_time_zone_find_interval:
700  * @tz: a #GTimeZone
701  * @type: the #GTimeType of @time_
702  * @time_: a number of seconds since January 1, 1970
703  *
704  * Finds an the interval within @tz that corresponds to the given @time_.
705  * The meaning of @time_ depends on @type.
706  *
707  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
708  * succeed (since universal time is monotonic and continuous).
709  *
710  * Otherwise @time_ is treated is local time.  The distinction between
711  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
712  * the case that the given @time_ is ambiguous.  In Toronto, for example,
713  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
714  * savings time and the next, an hour later, outside of daylight savings
715  * time).  In this case, the different value of @type would result in a
716  * different interval being returned.
717  *
718  * It is still possible for this function to fail.  In Toronto, for
719  * example, 02:00 on March 14th 2010 does not exist (due to the leap
720  * forward to begin daylight savings time).  -1 is returned in that
721  * case.
722  *
723  * Returns: the interval containing @time_, or -1 in case of failure
724  *
725  * Since: 2.26
726  */
727 gint
728 g_time_zone_find_interval (GTimeZone *tz,
729                            GTimeType  type,
730                            gint64     time_)
731 {
732   gint i;
733
734   if (tz->zoneinfo == NULL)
735     return 0;
736
737   for (i = 0; i < tz->timecnt; i++)
738     if (time_ <= interval_end (tz, i))
739       break;
740
741   if (type == G_TIME_TYPE_UNIVERSAL)
742     return i;
743
744   if (time_ < interval_local_start (tz, i))
745     {
746       if (time_ > interval_local_end (tz, --i))
747         return -1;
748     }
749
750   else if (time_ > interval_local_end (tz, i))
751     {
752       if (time_ < interval_local_start (tz, ++i))
753         return -1;
754     }
755
756   else if (interval_isdst (tz, i) != type)
757     {
758       if (i && time_ <= interval_local_end (tz, i - 1))
759         i--;
760
761       else if (i < tz->timecnt && time_ >= interval_local_start (tz, i + 1))
762         i++;
763     }
764
765   return i;
766 }
767
768 /* Public API accessors {{{1 */
769
770 /**
771  * g_time_zone_get_abbreviation:
772  * @tz: a #GTimeZone
773  * @interval: an interval within the timezone
774  *
775  * Determines the time zone abbreviation to be used during a particular
776  * @interval of time in the time zone @tz.
777  *
778  * For example, in Toronto this is currently "EST" during the winter
779  * months and "EDT" during the summer months when daylight savings time
780  * is in effect.
781  *
782  * Returns: the time zone abbreviation, which belongs to @tz
783  *
784  * Since: 2.26
785  **/
786 const gchar *
787 g_time_zone_get_abbreviation (GTimeZone *tz,
788                               gint       interval)
789 {
790   g_return_val_if_fail (interval_valid (tz, interval), NULL);
791
792   if (tz->header == NULL)
793     return "UTC";
794
795   return tz->abbrs + interval_abbrind (tz, interval);
796 }
797
798 /**
799  * g_time_zone_get_offset:
800  * @tz: a #GTimeZone
801  * @interval: an interval within the timezone
802  *
803  * Determines the offset to UTC in effect during a particular @interval
804  * of time in the time zone @tz.
805  *
806  * The offset is the number of seconds that you add to UTC time to
807  * arrive at local time for @tz (ie: negative numbers for time zones
808  * west of GMT, positive numbers for east).
809  *
810  * Returns: the number of seconds that should be added to UTC to get the
811  *          local time in @tz
812  *
813  * Since: 2.26
814  **/
815 gint32
816 g_time_zone_get_offset (GTimeZone *tz,
817                         gint       interval)
818 {
819   g_return_val_if_fail (interval_valid (tz, interval), 0);
820
821   if (tz->header == NULL)
822     return 0;
823
824   return interval_offset (tz, interval);
825 }
826
827 /**
828  * g_time_zone_is_dst:
829  * @tz: a #GTimeZone
830  * @interval: an interval within the timezone
831  *
832  * Determines if daylight savings time is in effect during a particular
833  * @interval of time in the time zone @tz.
834  *
835  * Returns: %TRUE if daylight savings time is in effect
836  *
837  * Since: 2.26
838  **/
839 gboolean
840 g_time_zone_is_dst (GTimeZone *tz,
841                     gint       interval)
842 {
843   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
844
845   if (tz->header == NULL)
846     return FALSE;
847
848   return interval_isdst (tz, interval);
849 }
850
851 /* Epilogue {{{1 */
852 /* vim:set foldmethod=marker: */