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