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