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