gtimezone: consider a leading : in TZ environment variable
[platform/upstream/glib.git] / glib / gtimezone.c
1 /*
2  * Copyright © 2010 Codethink Limited
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 /* Prologue {{{1 */
23
24 #include "gtimezone.h"
25
26 #include <string.h>
27 #include <stdlib.h>
28 #include <signal.h>
29
30 #include "gmappedfile.h"
31 #include "gtestutils.h"
32 #include "gfileutils.h"
33 #include "gstrfuncs.h"
34 #include "ghash.h"
35 #include "gthread.h"
36 #include "gbytes.h"
37 #include "gslice.h"
38
39 /**
40  * SECTION:timezone
41  * @title: GTimeZone
42  * @short_description: a structure representing a time zone
43  * @see_also: #GDateTime
44  *
45  * #GTimeZone is a structure that represents a time zone, at no
46  * particular point in time.  It is refcounted and immutable.
47  *
48  * A time zone contains a number of intervals.  Each interval has
49  * an abbreviation to describe it, an offet to UTC and a flag indicating
50  * if the daylight savings time is in effect during that interval.  A
51  * time zone always has at least one interval -- interval 0.
52  *
53  * Every UTC time is contained within exactly one interval, but a given
54  * local time may be contained within zero, one or two intervals (due to
55  * incontinuities associated with daylight savings time).
56  *
57  * An interval may refer to a specific period of time (eg: the duration
58  * of daylight savings time during 2010) or it may refer to many periods
59  * of time that share the same properties (eg: all periods of daylight
60  * savings time).  It is also possible (usually for political reasons)
61  * that some properties (like the abbreviation) change between intervals
62  * without other properties changing.
63  *
64  * #GTimeZone is available since GLib 2.26.
65  */
66
67 /**
68  * GTimeZone:
69  *
70  * #GDateTime is an opaque structure whose members cannot be accessed
71  * directly.
72  *
73  * Since: 2.26
74  **/
75
76 /* zoneinfo file format {{{1 */
77
78 /* unaligned */
79 typedef struct { gchar bytes[8]; } gint64_be;
80 typedef struct { gchar bytes[4]; } gint32_be;
81 typedef struct { gchar bytes[4]; } guint32_be;
82
83 static inline gint64 gint64_from_be (const gint64_be be) {
84   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
85 }
86
87 static inline gint32 gint32_from_be (const gint32_be be) {
88   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
89 }
90
91 static inline guint32 guint32_from_be (const guint32_be be) {
92   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
93 }
94
95 struct tzhead
96 {
97   gchar      tzh_magic[4];
98   gchar      tzh_version;
99   guchar     tzh_reserved[15];
100
101   guint32_be tzh_ttisgmtcnt;
102   guint32_be tzh_ttisstdcnt;
103   guint32_be tzh_leapcnt;
104   guint32_be tzh_timecnt;
105   guint32_be tzh_typecnt;
106   guint32_be tzh_charcnt;
107 };
108
109 struct ttinfo
110 {
111   gint32_be tt_gmtoff;
112   guint8    tt_isdst;
113   guint8    tt_abbrind;
114 };
115
116 /* GTimeZone structure and lifecycle {{{1 */
117 struct _GTimeZone
118 {
119   gchar   *name;
120
121   GBytes *zoneinfo;
122
123   const struct tzhead *header;
124   const struct ttinfo *infos;
125   const gint64_be     *trans;
126   const guint8        *indices;
127   const gchar         *abbrs;
128   gint                 timecnt;
129
130   gint     ref_count;
131 };
132
133 G_LOCK_DEFINE_STATIC (time_zones);
134 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
135
136 /**
137  * g_time_zone_unref:
138  * @tz: a #GTimeZone
139  *
140  * Decreases the reference count on @tz.
141  *
142  * Since: 2.26
143  **/
144 void
145 g_time_zone_unref (GTimeZone *tz)
146 {
147   int ref_count;
148
149 again:
150   ref_count = g_atomic_int_get (&tz->ref_count);
151
152   g_assert (ref_count > 0);
153
154   if (ref_count == 1)
155     {
156       if (tz->name != NULL)
157         {
158           G_LOCK(time_zones);
159
160           /* someone else might have grabbed a ref in the meantime */
161           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
162             {
163               G_UNLOCK(time_zones);
164               goto again;
165             }
166
167           g_hash_table_remove (time_zones, tz->name);
168           G_UNLOCK(time_zones);
169         }
170
171       if (tz->zoneinfo)
172         g_bytes_unref (tz->zoneinfo);
173
174       g_free (tz->name);
175
176       g_slice_free (GTimeZone, tz);
177     }
178
179   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
180                                                           ref_count,
181                                                           ref_count - 1))
182     goto again;
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 GBytes *
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_bytes_new_take (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   GMappedFile *file;
349
350   G_LOCK (time_zones);
351   if (time_zones == NULL)
352     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
353
354   if (identifier)
355     tz = g_hash_table_lookup (time_zones, identifier);
356   else
357     tz = NULL;
358
359   if (tz == NULL)
360     {
361       tz = g_slice_new0 (GTimeZone);
362       tz->name = g_strdup (identifier);
363       tz->ref_count = 0;
364
365       tz->zoneinfo = zone_for_constant_offset (identifier);
366
367       if (tz->zoneinfo == NULL)
368         {
369           gchar *filename;
370
371           /* identifier can be a relative or absolute path name;
372              if relative, it is interpreted starting from /usr/share/timezone
373              while the POSIX standard says it should start with :,
374              glibc allows both syntaxes, so we should too */
375           if (identifier != NULL)
376             {
377               const gchar *tzdir;
378
379               tzdir = getenv ("TZDIR");
380               if (tzdir == NULL)
381                 tzdir = "/usr/share/zoneinfo";
382
383               if (*identifier == ':')
384                 identifier ++;
385
386               if (g_path_is_absolute (identifier))
387                 filename = g_strdup (identifier);
388               else
389                 filename = g_build_filename (tzdir, identifier, NULL);
390             }
391           else
392             filename = g_strdup ("/etc/localtime");
393
394           file = g_mapped_file_new (filename, FALSE, NULL);
395           if (file != NULL)
396             {
397               tz->zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
398                                                          g_mapped_file_get_length (file),
399                                                          (GDestroyNotify)g_mapped_file_unref,
400                                                          g_mapped_file_ref (file));
401               g_mapped_file_unref (file);
402             }
403           g_free (filename);
404         }
405
406       if (tz->zoneinfo != NULL)
407         {
408           gsize size;
409           const struct tzhead *header = g_bytes_get_data (tz->zoneinfo, &size);
410
411           /* we only bother to support version 2 */
412           if (size < sizeof (struct tzhead) || memcmp (header, "TZif2", 5))
413             {
414               g_bytes_unref (tz->zoneinfo);
415               tz->zoneinfo = NULL;
416             }
417           else
418             {
419               gint typecnt;
420
421               /* we trust the file completely. */
422               tz->header = (const struct tzhead *)
423                 (((const gchar *) (header + 1)) +
424                   guint32_from_be(header->tzh_ttisgmtcnt) +
425                   guint32_from_be(header->tzh_ttisstdcnt) +
426                   8 * guint32_from_be(header->tzh_leapcnt) +
427                   5 * guint32_from_be(header->tzh_timecnt) +
428                   6 * guint32_from_be(header->tzh_typecnt) +
429                   guint32_from_be(header->tzh_charcnt));
430
431               typecnt     = guint32_from_be (tz->header->tzh_typecnt);
432               tz->timecnt = guint32_from_be (tz->header->tzh_timecnt);
433               tz->trans   = (gconstpointer) (tz->header + 1);
434               tz->indices = (gconstpointer) (tz->trans + tz->timecnt);
435               tz->infos   = (gconstpointer) (tz->indices + tz->timecnt);
436               tz->abbrs   = (gconstpointer) (tz->infos + typecnt);
437             }
438         }
439
440       if (identifier)
441         g_hash_table_insert (time_zones, tz->name, tz);
442     }
443   g_atomic_int_inc (&tz->ref_count);
444   G_UNLOCK (time_zones);
445
446   return tz;
447 }
448
449 /**
450  * g_time_zone_new_utc:
451  *
452  * Creates a #GTimeZone corresponding to UTC.
453  *
454  * This is equivalent to calling g_time_zone_new() with a value like
455  * "Z", "UTC", "+00", etc.
456  *
457  * You should release the return value by calling g_time_zone_unref()
458  * when you are done with it.
459  *
460  * Returns: the universal timezone
461  *
462  * Since: 2.26
463  **/
464 GTimeZone *
465 g_time_zone_new_utc (void)
466 {
467   return g_time_zone_new ("UTC");
468 }
469
470 /**
471  * g_time_zone_new_local:
472  *
473  * Creates a #GTimeZone corresponding to local time.  The local time
474  * zone may change between invocations to this function; for example,
475  * if the system administrator changes it.
476  *
477  * This is equivalent to calling g_time_zone_new() with the value of the
478  * <varname>TZ</varname> environment variable (including the possibility
479  * of %NULL).
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 local timezone
485  *
486  * Since: 2.26
487  **/
488 GTimeZone *
489 g_time_zone_new_local (void)
490 {
491   return g_time_zone_new (getenv ("TZ"));
492 }
493
494 /* Internal helpers {{{1 */
495 inline static const struct ttinfo *
496 interval_info (GTimeZone *tz,
497                gint       interval)
498 {
499   if (interval)
500     return tz->infos + tz->indices[interval - 1];
501
502   return tz->infos;
503 }
504
505 inline static gint64
506 interval_start (GTimeZone *tz,
507                 gint       interval)
508 {
509   if (interval)
510     return gint64_from_be (tz->trans[interval - 1]);
511
512   return G_MININT64;
513 }
514
515 inline static gint64
516 interval_end (GTimeZone *tz,
517               gint       interval)
518 {
519   if (interval < tz->timecnt)
520     return gint64_from_be (tz->trans[interval]) - 1;
521
522   return G_MAXINT64;
523 }
524
525 inline static gint32
526 interval_offset (GTimeZone *tz,
527                  gint       interval)
528 {
529   return gint32_from_be (interval_info (tz, interval)->tt_gmtoff);
530 }
531
532 inline static gboolean
533 interval_isdst (GTimeZone *tz,
534                 gint       interval)
535 {
536   return interval_info (tz, interval)->tt_isdst;
537 }
538
539 inline static guint8
540 interval_abbrind (GTimeZone *tz,
541                   gint       interval)
542 {
543   return interval_info (tz, interval)->tt_abbrind;
544 }
545
546 inline static gint64
547 interval_local_start (GTimeZone *tz,
548                       gint       interval)
549 {
550   if (interval)
551     return interval_start (tz, interval) + interval_offset (tz, interval);
552
553   return G_MININT64;
554 }
555
556 inline static gint64
557 interval_local_end (GTimeZone *tz,
558                     gint       interval)
559 {
560   if (interval < tz->timecnt)
561     return interval_end (tz, interval) + interval_offset (tz, interval);
562
563   return G_MAXINT64;
564 }
565
566 static gboolean
567 interval_valid (GTimeZone *tz,
568                 gint       interval)
569 {
570   return interval <= tz->timecnt;
571 }
572
573 /* g_time_zone_find_interval() {{{1 */
574
575 /**
576  * g_time_zone_adjust_time:
577  * @tz: a #GTimeZone
578  * @type: the #GTimeType of @time_
579  * @time_: a pointer to a number of seconds since January 1, 1970
580  *
581  * Finds an interval within @tz that corresponds to the given @time_,
582  * possibly adjusting @time_ if required to fit into an interval.
583  * The meaning of @time_ depends on @type.
584  *
585  * This function is similar to g_time_zone_find_interval(), with the
586  * difference that it always succeeds (by making the adjustments
587  * described below).
588  *
589  * In any of the cases where g_time_zone_find_interval() succeeds then
590  * this function returns the same value, without modifying @time_.
591  *
592  * This function may, however, modify @time_ in order to deal with
593  * non-existent times.  If the non-existent local @time_ of 02:30 were
594  * requested on March 13th 2010 in Toronto then this function would
595  * adjust @time_ to be 03:00 and return the interval containing the
596  * adjusted time.
597  *
598  * Returns: the interval containing @time_, never -1
599  *
600  * Since: 2.26
601  **/
602 gint
603 g_time_zone_adjust_time (GTimeZone *tz,
604                          GTimeType  type,
605                          gint64    *time_)
606 {
607   gint i;
608
609   if (tz->zoneinfo == NULL)
610     return 0;
611
612   /* find the interval containing *time UTC
613    * TODO: this could be binary searched (or better) */
614   for (i = 0; i < tz->timecnt; i++)
615     if (*time_ <= interval_end (tz, i))
616       break;
617
618   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
619
620   if (type != G_TIME_TYPE_UNIVERSAL)
621     {
622       if (*time_ < interval_local_start (tz, i))
623         /* if time came before the start of this interval... */
624         {
625           i--;
626
627           /* if it's not in the previous interval... */
628           if (*time_ > interval_local_end (tz, i))
629             {
630               /* it doesn't exist.  fast-forward it. */
631               i++;
632               *time_ = interval_local_start (tz, i);
633             }
634         }
635
636       else if (*time_ > interval_local_end (tz, i))
637         /* if time came after the end of this interval... */
638         {
639           i++;
640
641           /* if it's not in the next interval... */
642           if (*time_ < interval_local_start (tz, i))
643             /* it doesn't exist.  fast-forward it. */
644             *time_ = interval_local_start (tz, i);
645         }
646
647       else if (interval_isdst (tz, i) != type)
648         /* it's in this interval, but dst flag doesn't match.
649          * check neighbours for a better fit. */
650         {
651           if (i && *time_ <= interval_local_end (tz, i - 1))
652             i--;
653
654           else if (i < tz->timecnt &&
655                    *time_ >= interval_local_start (tz, i + 1))
656             i++;
657         }
658     }
659
660   return i;
661 }
662
663 /**
664  * g_time_zone_find_interval:
665  * @tz: a #GTimeZone
666  * @type: the #GTimeType of @time_
667  * @time_: a number of seconds since January 1, 1970
668  *
669  * Finds an the interval within @tz that corresponds to the given @time_.
670  * The meaning of @time_ depends on @type.
671  *
672  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
673  * succeed (since universal time is monotonic and continuous).
674  *
675  * Otherwise @time_ is treated is local time.  The distinction between
676  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
677  * the case that the given @time_ is ambiguous.  In Toronto, for example,
678  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
679  * savings time and the next, an hour later, outside of daylight savings
680  * time).  In this case, the different value of @type would result in a
681  * different interval being returned.
682  *
683  * It is still possible for this function to fail.  In Toronto, for
684  * example, 02:00 on March 14th 2010 does not exist (due to the leap
685  * forward to begin daylight savings time).  -1 is returned in that
686  * case.
687  *
688  * Returns: the interval containing @time_, or -1 in case of failure
689  *
690  * Since: 2.26
691  */
692 gint
693 g_time_zone_find_interval (GTimeZone *tz,
694                            GTimeType  type,
695                            gint64     time_)
696 {
697   gint i;
698
699   if (tz->zoneinfo == NULL)
700     return 0;
701
702   for (i = 0; i < tz->timecnt; i++)
703     if (time_ <= interval_end (tz, i))
704       break;
705
706   if (type == G_TIME_TYPE_UNIVERSAL)
707     return i;
708
709   if (time_ < interval_local_start (tz, i))
710     {
711       if (time_ > interval_local_end (tz, --i))
712         return -1;
713     }
714
715   else if (time_ > interval_local_end (tz, i))
716     {
717       if (time_ < interval_local_start (tz, ++i))
718         return -1;
719     }
720
721   else if (interval_isdst (tz, i) != type)
722     {
723       if (i && time_ <= interval_local_end (tz, i - 1))
724         i--;
725
726       else if (i < tz->timecnt && time_ >= interval_local_start (tz, i + 1))
727         i++;
728     }
729
730   return i;
731 }
732
733 /* Public API accessors {{{1 */
734
735 /**
736  * g_time_zone_get_abbreviation:
737  * @tz: a #GTimeZone
738  * @interval: an interval within the timezone
739  *
740  * Determines the time zone abbreviation to be used during a particular
741  * @interval of time in the time zone @tz.
742  *
743  * For example, in Toronto this is currently "EST" during the winter
744  * months and "EDT" during the summer months when daylight savings time
745  * is in effect.
746  *
747  * Returns: the time zone abbreviation, which belongs to @tz
748  *
749  * Since: 2.26
750  **/
751 const gchar *
752 g_time_zone_get_abbreviation (GTimeZone *tz,
753                               gint       interval)
754 {
755   g_return_val_if_fail (interval_valid (tz, interval), NULL);
756
757   if (tz->header == NULL)
758     return "UTC";
759
760   return tz->abbrs + interval_abbrind (tz, interval);
761 }
762
763 /**
764  * g_time_zone_get_offset:
765  * @tz: a #GTimeZone
766  * @interval: an interval within the timezone
767  *
768  * Determines the offset to UTC in effect during a particular @interval
769  * of time in the time zone @tz.
770  *
771  * The offset is the number of seconds that you add to UTC time to
772  * arrive at local time for @tz (ie: negative numbers for time zones
773  * west of GMT, positive numbers for east).
774  *
775  * Returns: the number of seconds that should be added to UTC to get the
776  *          local time in @tz
777  *
778  * Since: 2.26
779  **/
780 gint32
781 g_time_zone_get_offset (GTimeZone *tz,
782                         gint       interval)
783 {
784   g_return_val_if_fail (interval_valid (tz, interval), 0);
785
786   if (tz->header == NULL)
787     return 0;
788
789   return interval_offset (tz, interval);
790 }
791
792 /**
793  * g_time_zone_is_dst:
794  * @tz: a #GTimeZone
795  * @interval: an interval within the timezone
796  *
797  * Determines if daylight savings time is in effect during a particular
798  * @interval of time in the time zone @tz.
799  *
800  * Returns: %TRUE if daylight savings time is in effect
801  *
802  * Since: 2.26
803  **/
804 gboolean
805 g_time_zone_is_dst (GTimeZone *tz,
806                     gint       interval)
807 {
808   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
809
810   if (tz->header == NULL)
811     return FALSE;
812
813   return interval_isdst (tz, interval);
814 }
815
816 /* Epilogue {{{1 */
817 /* vim:set foldmethod=marker: */