GTimeZone: Parse more offset formats
[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 typedef struct
119 {
120   gint32     gmt_offset;
121   gboolean   is_dst;
122   gboolean   is_standard;
123   gboolean   is_gmt;
124   gchar     *abbrev;
125 } TransitionInfo;
126
127 typedef struct
128 {
129   gint64 time;
130   gint   info_index;
131 } Transition;
132
133
134 /* GTimeZone structure and lifecycle {{{1 */
135 struct _GTimeZone
136 {
137   gchar   *name;
138   GArray  *t_info;
139   GArray  *transitions;
140   gint     ref_count;
141 };
142
143 G_LOCK_DEFINE_STATIC (time_zones);
144 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
145
146 /**
147  * g_time_zone_unref:
148  * @tz: a #GTimeZone
149  *
150  * Decreases the reference count on @tz.
151  *
152  * Since: 2.26
153  **/
154 void
155 g_time_zone_unref (GTimeZone *tz)
156 {
157   int ref_count;
158
159 again:
160   ref_count = g_atomic_int_get (&tz->ref_count);
161
162   g_assert (ref_count > 0);
163
164   if (ref_count == 1)
165     {
166       if (tz->name != NULL)
167         {
168           G_LOCK(time_zones);
169
170           /* someone else might have grabbed a ref in the meantime */
171           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
172             {
173               G_UNLOCK(time_zones);
174               goto again;
175             }
176
177           g_hash_table_remove (time_zones, tz->name);
178           G_UNLOCK(time_zones);
179         }
180
181       g_array_free (tz->t_info, TRUE);
182       if (tz->transitions != NULL)
183         g_array_free (tz->transitions, TRUE);
184       g_free (tz->name);
185
186       g_slice_free (GTimeZone, tz);
187     }
188
189   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
190                                                           ref_count,
191                                                           ref_count - 1))
192     goto again;
193 }
194
195 /**
196  * g_time_zone_ref:
197  * @tz: a #GTimeZone
198  *
199  * Increases the reference count on @tz.
200  *
201  * Returns: a new reference to @tz.
202  *
203  * Since: 2.26
204  **/
205 GTimeZone *
206 g_time_zone_ref (GTimeZone *tz)
207 {
208   g_assert (tz->ref_count > 0);
209
210   g_atomic_int_inc (&tz->ref_count);
211
212   return tz;
213 }
214
215 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
216 /*
217  * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
218  *  - h[h] is 0 to 23
219  *  - mm is 00 to 59
220  *  - ss is 00 to 59
221  */
222 static gboolean
223 parse_time (const gchar *time_,
224             gint32      *offset)
225 {
226   if (*time_ < '0' || '9' < *time_)
227     return FALSE;
228
229   *offset = 60 * 60 * (*time_++ - '0');
230
231   if (*time_ == '\0')
232     return TRUE;
233
234   if (*time_ != ':')
235     {
236       if (*time_ < '0' || '9' < *time_)
237         return FALSE;
238
239       *offset *= 10;
240       *offset += 60 * 60 * (*time_++ - '0');
241
242       if (*offset > 23 * 60 * 60)
243         return FALSE;
244
245       if (*time_ == '\0')
246         return TRUE;
247     }
248
249   if (*time_ == ':')
250     time_++;
251
252   if (*time_ < '0' || '5' < *time_)
253     return FALSE;
254
255   *offset += 10 * 60 * (*time_++ - '0');
256
257   if (*time_ < '0' || '9' < *time_)
258     return FALSE;
259
260   *offset += 60 * (*time_++ - '0');
261
262   if (*time_ == '\0')
263     return TRUE;
264
265   if (*time_ == ':')
266     time_++;
267
268   if (*time_ < '0' || '5' < *time_)
269     return FALSE;
270
271   *offset += 10 * (*time_++ - '0');
272
273   if (*time_ < '0' || '9' < *time_)
274     return FALSE;
275
276   *offset += *time_++ - '0';
277
278   return *time_ == '\0';
279 }
280
281 static gboolean
282 parse_constant_offset (const gchar *name,
283                        gint32      *offset)
284 {
285   if (g_strcmp0 (name, "UTC") == 0)
286     {
287       *offset = 0;
288       return TRUE;
289     }
290
291   if (*name >= '0' && '9' >= *name)
292     return parse_time (name, offset);
293
294   switch (*name++)
295     {
296     case 'Z':
297       *offset = 0;
298       return !*name;
299
300     case '+':
301       return parse_time (name, offset);
302
303     case '-':
304       if (parse_time (name, offset))
305         {
306           *offset = -*offset;
307           return TRUE;
308         }
309
310     default:
311       return FALSE;
312     }
313 }
314
315 static void
316 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
317 {
318   gint32 offset;
319   TransitionInfo info;
320
321   if (name == NULL || !parse_constant_offset (name, &offset))
322     return;
323
324   info.gmt_offset = offset;
325   info.is_dst = FALSE;
326   info.is_standard = TRUE;
327   info.is_gmt = TRUE;
328   info.abbrev =  g_strdup (name);
329
330
331   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
332   g_array_append_val (gtz->t_info, info);
333
334   /* Constant offset, no transitions */
335   gtz->transitions = NULL;
336 }
337
338 #ifdef G_OS_UNIX
339 static GBytes*
340 zone_info_unix (const gchar *identifier)
341 {
342   gchar *filename;
343   GMappedFile *file = NULL;
344   GBytes *zoneinfo = NULL;
345
346   /* identifier can be a relative or absolute path name;
347      if relative, it is interpreted starting from /usr/share/zoneinfo
348      while the POSIX standard says it should start with :,
349      glibc allows both syntaxes, so we should too */
350   if (identifier != NULL)
351     {
352       const gchar *tzdir;
353
354       tzdir = getenv ("TZDIR");
355       if (tzdir == NULL)
356         tzdir = "/usr/share/zoneinfo";
357
358       if (*identifier == ':')
359         identifier ++;
360
361       if (g_path_is_absolute (identifier))
362         filename = g_strdup (identifier);
363       else
364         filename = g_build_filename (tzdir, identifier, NULL);
365     }
366   else
367     filename = g_strdup ("/etc/localtime");
368
369   file = g_mapped_file_new (filename, FALSE, NULL);
370   if (file != NULL)
371     {
372       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
373                                              g_mapped_file_get_length (file),
374                                              (GDestroyNotify)g_mapped_file_unref,
375                                              g_mapped_file_ref (file));
376       g_mapped_file_unref (file);
377     }
378   g_free (filename);
379   return zoneinfo;
380 }
381
382 static void
383 init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
384 {
385   gsize size;
386   guint index;
387   guint32 time_count, type_count, leap_count, isgmt_count;
388   guint32  isstd_count, char_count ;
389   gpointer tz_transitions, tz_type_index, tz_ttinfo;
390   gpointer  tz_leaps, tz_isgmt, tz_isstd;
391   gchar* tz_abbrs;
392   guint timesize = sizeof (gint32), countsize = sizeof (gint32);
393   const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
394
395   g_return_if_fail (size >= sizeof (struct tzhead) &&
396                     memcmp (header, "TZif", 4) == 0);
397
398   if (header->tzh_version == '2')
399       {
400         /* Skip ahead to the newer 64-bit data if it's available. */
401         header = (const struct tzhead *)
402           (((const gchar *) (header + 1)) +
403            guint32_from_be(header->tzh_ttisgmtcnt) +
404            guint32_from_be(header->tzh_ttisstdcnt) +
405            8 * guint32_from_be(header->tzh_leapcnt) +
406            5 * guint32_from_be(header->tzh_timecnt) +
407            6 * guint32_from_be(header->tzh_typecnt) +
408            guint32_from_be(header->tzh_charcnt));
409         timesize = sizeof (gint64);
410       }
411   time_count = guint32_from_be(header->tzh_timecnt);
412   type_count = guint32_from_be(header->tzh_typecnt);
413   leap_count = guint32_from_be(header->tzh_leapcnt);
414   isgmt_count = guint32_from_be(header->tzh_ttisgmtcnt);
415   isstd_count = guint32_from_be(header->tzh_ttisstdcnt);
416   char_count = guint32_from_be(header->tzh_charcnt);
417
418   g_assert (type_count == isgmt_count);
419   g_assert (type_count == isstd_count);
420
421   tz_transitions = (gpointer)(header + 1);
422   tz_type_index = tz_transitions + timesize * time_count;
423   tz_ttinfo = tz_type_index + time_count;
424   tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
425   tz_leaps = tz_abbrs + char_count;
426   tz_isstd = tz_leaps + (timesize + countsize) * leap_count;
427   tz_isgmt = tz_isstd + isstd_count;
428
429   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
430                                    type_count);
431   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
432                                         time_count);
433
434   for (index = 0; index < type_count; index++)
435     {
436       TransitionInfo t_info;
437       struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
438       t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
439       t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
440       t_info.is_standard = ((guint8*)tz_isstd)[index] ? TRUE : FALSE;
441       t_info.is_gmt = ((guint8*)tz_isgmt)[index] ? TRUE : FALSE;
442       t_info.abbrev = g_strdup (&tz_abbrs[info.tt_abbrind]);
443       g_array_append_val (gtz->t_info, t_info);
444     }
445
446   for (index = 0; index < time_count; index++)
447     {
448       Transition trans;
449       if (header->tzh_version == '2')
450         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
451       else
452         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
453       trans.info_index = ((guint8*)tz_type_index)[index];
454       g_assert (trans.info_index >= 0);
455       g_assert (trans.info_index < gtz->t_info->len);
456       g_array_append_val (gtz->transitions, trans);
457     }
458   g_bytes_unref (zoneinfo);
459 }
460
461 #endif
462
463 /* Construction {{{1 */
464 /**
465  * g_time_zone_new:
466  * @identifier: (allow-none): a timezone identifier
467  *
468  * Creates a #GTimeZone corresponding to @identifier.
469  *
470  * @identifier can either be an RFC3339/ISO 8601 time offset or
471  * something that would pass as a valid value for the
472  * <varname>TZ</varname> environment variable (including %NULL).
473  *
474  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
475  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
476  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.
477  *
478  * The <varname>TZ</varname> environment variable typically corresponds
479  * to the name of a file in the zoneinfo database, but there are many
480  * other possibilities.  Note that those other possibilities are not
481  * currently implemented, but are planned.
482  *
483  * g_time_zone_new_local() calls this function with the value of the
484  * <varname>TZ</varname> environment variable.  This function itself is
485  * independent of the value of <varname>TZ</varname>, but if @identifier
486  * is %NULL then <filename>/etc/localtime</filename> will be consulted
487  * to discover the correct timezone.
488  *
489  * See <ulink
490  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
491  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
492  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
493  * full list of valid time offsets.  See <ulink
494  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
495  * GNU C Library manual</ulink> for an explanation of the possible
496  * values of the <varname>TZ</varname> environment variable.
497  *
498  * You should release the return value by calling g_time_zone_unref()
499  * when you are done with it.
500  *
501  * Returns: the requested timezone
502  *
503  * Since: 2.26
504  **/
505 GTimeZone *
506 g_time_zone_new (const gchar *identifier)
507 {
508   GTimeZone *tz = NULL;
509
510   G_LOCK (time_zones);
511   if (time_zones == NULL)
512     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
513
514   if (identifier)
515     {
516       tz = g_hash_table_lookup (time_zones, identifier);
517       if (tz)
518         {
519           g_atomic_int_inc (&tz->ref_count);
520           G_UNLOCK (time_zones);
521           return tz;
522         }
523     }
524
525   tz = g_slice_new0 (GTimeZone);
526   tz->name = g_strdup (identifier);
527   tz->ref_count = 0;
528
529   zone_for_constant_offset (tz, identifier);
530
531   if (tz->t_info == NULL)
532     {
533 #ifdef G_OS_UNIX
534       GBytes *zoneinfo = zone_info_unix (identifier);
535       if (!zoneinfo)
536         zone_for_constant_offset (tz, "UTC");
537       else
538         {
539           init_zone_from_iana_info (tz, zoneinfo);
540           g_bytes_unref (zoneinfo);
541         }
542 #elif defined G_OS_WIN32
543 #endif
544     }
545
546   if (tz->t_info != NULL)
547     {
548       if (identifier)
549         g_hash_table_insert (time_zones, tz->name, tz);
550     }
551   g_atomic_int_inc (&tz->ref_count);
552   G_UNLOCK (time_zones);
553
554   return tz;
555 }
556
557 /**
558  * g_time_zone_new_utc:
559  *
560  * Creates a #GTimeZone corresponding to UTC.
561  *
562  * This is equivalent to calling g_time_zone_new() with a value like
563  * "Z", "UTC", "+00", etc.
564  *
565  * You should release the return value by calling g_time_zone_unref()
566  * when you are done with it.
567  *
568  * Returns: the universal timezone
569  *
570  * Since: 2.26
571  **/
572 GTimeZone *
573 g_time_zone_new_utc (void)
574 {
575   return g_time_zone_new ("UTC");
576 }
577
578 /**
579  * g_time_zone_new_local:
580  *
581  * Creates a #GTimeZone corresponding to local time.  The local time
582  * zone may change between invocations to this function; for example,
583  * if the system administrator changes it.
584  *
585  * This is equivalent to calling g_time_zone_new() with the value of the
586  * <varname>TZ</varname> environment variable (including the possibility
587  * of %NULL).
588  *
589  * You should release the return value by calling g_time_zone_unref()
590  * when you are done with it.
591  *
592  * Returns: the local timezone
593  *
594  * Since: 2.26
595  **/
596 GTimeZone *
597 g_time_zone_new_local (void)
598 {
599   return g_time_zone_new (getenv ("TZ"));
600 }
601
602 #define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
603 #define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
604
605 /* Internal helpers {{{1 */
606 /* Note that interval 0 is *before* the first transition time, so
607  * interval 1 gets transitions[0].
608  */
609 inline static const TransitionInfo*
610 interval_info (GTimeZone *tz,
611                guint      interval)
612 {
613   guint index;
614   g_return_val_if_fail (tz->t_info != NULL, NULL);
615   if (interval && tz->transitions && interval <= tz->transitions->len)
616     index = (TRANSITION(interval - 1)).info_index;
617   else
618     index = 0;
619   return &(TRANSITION_INFO(index));
620 }
621
622 inline static gint64
623 interval_start (GTimeZone *tz,
624                 guint      interval)
625 {
626   if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
627     return G_MININT64;
628   if (interval > tz->transitions->len)
629     interval = tz->transitions->len;
630   return (TRANSITION(interval - 1)).time;
631 }
632
633 inline static gint64
634 interval_end (GTimeZone *tz,
635               guint      interval)
636 {
637   if (tz->transitions && interval < tz->transitions->len)
638     return (TRANSITION(interval)).time - 1;
639   return G_MAXINT64;
640 }
641
642 inline static gint32
643 interval_offset (GTimeZone *tz,
644                  guint      interval)
645 {
646   g_return_val_if_fail (tz->t_info != NULL, 0);
647   return interval_info (tz, interval)->gmt_offset;
648 }
649
650 inline static gboolean
651 interval_isdst (GTimeZone *tz,
652                 guint      interval)
653 {
654   g_return_val_if_fail (tz->t_info != NULL, 0);
655   return interval_info (tz, interval)->is_dst;
656 }
657
658
659 inline static gboolean
660 interval_isgmt (GTimeZone *tz,
661                 guint      interval)
662 {
663   g_return_val_if_fail (tz->t_info != NULL, 0);
664   return interval_info (tz, interval)->is_gmt;
665 }
666
667 inline static gboolean
668 interval_isstandard (GTimeZone *tz,
669                 guint      interval)
670 {
671   return interval_info (tz, interval)->is_standard;
672 }
673
674 inline static gchar*
675 interval_abbrev (GTimeZone *tz,
676                   guint      interval)
677 {
678   g_return_val_if_fail (tz->t_info != NULL, 0);
679   return interval_info (tz, interval)->abbrev;
680 }
681
682 inline static gint64
683 interval_local_start (GTimeZone *tz,
684                       guint      interval)
685 {
686   if (interval)
687     return interval_start (tz, interval) + interval_offset (tz, interval);
688
689   return G_MININT64;
690 }
691
692 inline static gint64
693 interval_local_end (GTimeZone *tz,
694                     guint      interval)
695 {
696   if (tz->transitions && interval < tz->transitions->len)
697     return interval_end (tz, interval) + interval_offset (tz, interval);
698
699   return G_MAXINT64;
700 }
701
702 static gboolean
703 interval_valid (GTimeZone *tz,
704                 guint      interval)
705 {
706   if ( tz->transitions == NULL)
707     return interval == 0;
708   return interval <= tz->transitions->len;
709 }
710
711 /* g_time_zone_find_interval() {{{1 */
712
713 /**
714  * g_time_zone_adjust_time:
715  * @tz: a #GTimeZone
716  * @type: the #GTimeType of @time_
717  * @time_: a pointer to a number of seconds since January 1, 1970
718  *
719  * Finds an interval within @tz that corresponds to the given @time_,
720  * possibly adjusting @time_ if required to fit into an interval.
721  * The meaning of @time_ depends on @type.
722  *
723  * This function is similar to g_time_zone_find_interval(), with the
724  * difference that it always succeeds (by making the adjustments
725  * described below).
726  *
727  * In any of the cases where g_time_zone_find_interval() succeeds then
728  * this function returns the same value, without modifying @time_.
729  *
730  * This function may, however, modify @time_ in order to deal with
731  * non-existent times.  If the non-existent local @time_ of 02:30 were
732  * requested on March 14th 2010 in Toronto then this function would
733  * adjust @time_ to be 03:00 and return the interval containing the
734  * adjusted time.
735  *
736  * Returns: the interval containing @time_, never -1
737  *
738  * Since: 2.26
739  **/
740 gint
741 g_time_zone_adjust_time (GTimeZone *tz,
742                          GTimeType  type,
743                          gint64    *time_)
744 {
745   gint i;
746   guint intervals;
747
748   if (tz->transitions == NULL)
749     return 0;
750
751   intervals = tz->transitions->len;
752
753   /* find the interval containing *time UTC
754    * TODO: this could be binary searched (or better) */
755   for (i = 0; i <= intervals; i++)
756     if (*time_ <= interval_end (tz, i))
757       break;
758
759   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
760
761   if (type != G_TIME_TYPE_UNIVERSAL)
762     {
763       if (*time_ < interval_local_start (tz, i))
764         /* if time came before the start of this interval... */
765         {
766           i--;
767
768           /* if it's not in the previous interval... */
769           if (*time_ > interval_local_end (tz, i))
770             {
771               /* it doesn't exist.  fast-forward it. */
772               i++;
773               *time_ = interval_local_start (tz, i);
774             }
775         }
776
777       else if (*time_ > interval_local_end (tz, i))
778         /* if time came after the end of this interval... */
779         {
780           i++;
781
782           /* if it's not in the next interval... */
783           if (*time_ < interval_local_start (tz, i))
784             /* it doesn't exist.  fast-forward it. */
785             *time_ = interval_local_start (tz, i);
786         }
787
788       else if (interval_isdst (tz, i) != type)
789         /* it's in this interval, but dst flag doesn't match.
790          * check neighbours for a better fit. */
791         {
792           if (i && *time_ <= interval_local_end (tz, i - 1))
793             i--;
794
795           else if (i < intervals &&
796                    *time_ >= interval_local_start (tz, i + 1))
797             i++;
798         }
799     }
800
801   return i;
802 }
803
804 /**
805  * g_time_zone_find_interval:
806  * @tz: a #GTimeZone
807  * @type: the #GTimeType of @time_
808  * @time_: a number of seconds since January 1, 1970
809  *
810  * Finds an the interval within @tz that corresponds to the given @time_.
811  * The meaning of @time_ depends on @type.
812  *
813  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
814  * succeed (since universal time is monotonic and continuous).
815  *
816  * Otherwise @time_ is treated is local time.  The distinction between
817  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
818  * the case that the given @time_ is ambiguous.  In Toronto, for example,
819  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
820  * savings time and the next, an hour later, outside of daylight savings
821  * time).  In this case, the different value of @type would result in a
822  * different interval being returned.
823  *
824  * It is still possible for this function to fail.  In Toronto, for
825  * example, 02:00 on March 14th 2010 does not exist (due to the leap
826  * forward to begin daylight savings time).  -1 is returned in that
827  * case.
828  *
829  * Returns: the interval containing @time_, or -1 in case of failure
830  *
831  * Since: 2.26
832  */
833 gint
834 g_time_zone_find_interval (GTimeZone *tz,
835                            GTimeType  type,
836                            gint64     time_)
837 {
838   gint i;
839   guint intervals;
840
841   if (tz->transitions == NULL)
842     return 0;
843   intervals = tz->transitions->len;
844   for (i = 0; i <= intervals; i++)
845     if (time_ <= interval_end (tz, i))
846       break;
847
848   if (type == G_TIME_TYPE_UNIVERSAL)
849     return i;
850
851   if (time_ < interval_local_start (tz, i))
852     {
853       if (time_ > interval_local_end (tz, --i))
854         return -1;
855     }
856
857   else if (time_ > interval_local_end (tz, i))
858     {
859       if (time_ < interval_local_start (tz, ++i))
860         return -1;
861     }
862
863   else if (interval_isdst (tz, i) != type)
864     {
865       if (i && time_ <= interval_local_end (tz, i - 1))
866         i--;
867
868       else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
869         i++;
870     }
871
872   return i;
873 }
874
875 /* Public API accessors {{{1 */
876
877 /**
878  * g_time_zone_get_abbreviation:
879  * @tz: a #GTimeZone
880  * @interval: an interval within the timezone
881  *
882  * Determines the time zone abbreviation to be used during a particular
883  * @interval of time in the time zone @tz.
884  *
885  * For example, in Toronto this is currently "EST" during the winter
886  * months and "EDT" during the summer months when daylight savings time
887  * is in effect.
888  *
889  * Returns: the time zone abbreviation, which belongs to @tz
890  *
891  * Since: 2.26
892  **/
893 const gchar *
894 g_time_zone_get_abbreviation (GTimeZone *tz,
895                               gint       interval)
896 {
897   g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
898
899   return interval_abbrev (tz, (guint)interval);
900 }
901
902 /**
903  * g_time_zone_get_offset:
904  * @tz: a #GTimeZone
905  * @interval: an interval within the timezone
906  *
907  * Determines the offset to UTC in effect during a particular @interval
908  * of time in the time zone @tz.
909  *
910  * The offset is the number of seconds that you add to UTC time to
911  * arrive at local time for @tz (ie: negative numbers for time zones
912  * west of GMT, positive numbers for east).
913  *
914  * Returns: the number of seconds that should be added to UTC to get the
915  *          local time in @tz
916  *
917  * Since: 2.26
918  **/
919 gint32
920 g_time_zone_get_offset (GTimeZone *tz,
921                         gint       interval)
922 {
923   g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
924
925   return interval_offset (tz, (guint)interval);
926 }
927
928 /**
929  * g_time_zone_is_dst:
930  * @tz: a #GTimeZone
931  * @interval: an interval within the timezone
932  *
933  * Determines if daylight savings time is in effect during a particular
934  * @interval of time in the time zone @tz.
935  *
936  * Returns: %TRUE if daylight savings time is in effect
937  *
938  * Since: 2.26
939  **/
940 gboolean
941 g_time_zone_is_dst (GTimeZone *tz,
942                     gint       interval)
943 {
944   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
945
946   if (tz->transitions == NULL)
947     return FALSE;
948
949   return interval_isdst (tz, (guint)interval);
950 }
951
952 /* Epilogue {{{1 */
953 /* vim:set foldmethod=marker: */