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