GTimeZone: Parse Windows format for TZ
[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 #include "gdatetime.h"
41 #include "gdate.h"
42
43 #ifdef G_OS_WIN32
44 #define STRICT
45 #include <windows.h>
46 #endif
47
48 /**
49  * SECTION:timezone
50  * @title: GTimeZone
51  * @short_description: a structure representing a time zone
52  * @see_also: #GDateTime
53  *
54  * #GTimeZone is a structure that represents a time zone, at no
55  * particular point in time.  It is refcounted and immutable.
56  *
57  * A time zone contains a number of intervals.  Each interval has
58  * an abbreviation to describe it, an offet to UTC and a flag indicating
59  * if the daylight savings time is in effect during that interval.  A
60  * time zone always has at least one interval -- interval 0.
61  *
62  * Every UTC time is contained within exactly one interval, but a given
63  * local time may be contained within zero, one or two intervals (due to
64  * incontinuities associated with daylight savings time).
65  *
66  * An interval may refer to a specific period of time (eg: the duration
67  * of daylight savings time during 2010) or it may refer to many periods
68  * of time that share the same properties (eg: all periods of daylight
69  * savings time).  It is also possible (usually for political reasons)
70  * that some properties (like the abbreviation) change between intervals
71  * without other properties changing.
72  *
73  * #GTimeZone is available since GLib 2.26.
74  */
75
76 /**
77  * GTimeZone:
78  *
79  * #GDateTime is an opaque structure whose members cannot be accessed
80  * directly.
81  *
82  * Since: 2.26
83  **/
84
85 /* zoneinfo file format {{{1 */
86
87 /* unaligned */
88 typedef struct { gchar bytes[8]; } gint64_be;
89 typedef struct { gchar bytes[4]; } gint32_be;
90 typedef struct { gchar bytes[4]; } guint32_be;
91
92 static inline gint64 gint64_from_be (const gint64_be be) {
93   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
94 }
95
96 static inline gint32 gint32_from_be (const gint32_be be) {
97   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
98 }
99
100 static inline guint32 guint32_from_be (const guint32_be be) {
101   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
102 }
103
104 struct tzhead
105 {
106   gchar      tzh_magic[4];
107   gchar      tzh_version;
108   guchar     tzh_reserved[15];
109
110   guint32_be tzh_ttisgmtcnt;
111   guint32_be tzh_ttisstdcnt;
112   guint32_be tzh_leapcnt;
113   guint32_be tzh_timecnt;
114   guint32_be tzh_typecnt;
115   guint32_be tzh_charcnt;
116 };
117
118 struct ttinfo
119 {
120   gint32_be tt_gmtoff;
121   guint8    tt_isdst;
122   guint8    tt_abbrind;
123 };
124
125 typedef struct
126 {
127   gint32     gmt_offset;
128   gboolean   is_dst;
129   gboolean   is_standard;
130   gboolean   is_gmt;
131   gchar     *abbrev;
132 } TransitionInfo;
133
134 typedef struct
135 {
136   gint64 time;
137   gint   info_index;
138 } Transition;
139
140 typedef struct
141 {
142   gint     year;
143   gint     mon;
144   gint     mday;
145   gint     wday;
146   gint     week;
147   gint     hour;
148   gint     min;
149   gint     sec;
150   gboolean isstd;
151   gboolean isgmt;
152 } TimeZoneDate;
153
154 typedef struct
155 {
156   gint         start_year;
157   gint32       std_offset;
158   gint32       dlt_offset;
159   TimeZoneDate dlt_start;
160   TimeZoneDate dlt_end;
161   const gchar *std_name;
162   const gchar *dlt_name;
163 } TimeZoneRule;
164
165
166 /* GTimeZone structure and lifecycle {{{1 */
167 struct _GTimeZone
168 {
169   gchar   *name;
170   GArray  *t_info;
171   GArray  *transitions;
172   gint     ref_count;
173 };
174
175 G_LOCK_DEFINE_STATIC (time_zones);
176 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
177
178 #define MIN_TZYEAR 1900
179 #define MAX_TZYEAR 2038
180
181 /**
182  * g_time_zone_unref:
183  * @tz: a #GTimeZone
184  *
185  * Decreases the reference count on @tz.
186  *
187  * Since: 2.26
188  **/
189 void
190 g_time_zone_unref (GTimeZone *tz)
191 {
192   int ref_count;
193
194 again:
195   ref_count = g_atomic_int_get (&tz->ref_count);
196
197   g_assert (ref_count > 0);
198
199   if (ref_count == 1)
200     {
201       if (tz->name != NULL)
202         {
203           G_LOCK(time_zones);
204
205           /* someone else might have grabbed a ref in the meantime */
206           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
207             {
208               G_UNLOCK(time_zones);
209               goto again;
210             }
211
212           g_hash_table_remove (time_zones, tz->name);
213           G_UNLOCK(time_zones);
214         }
215
216       g_array_free (tz->t_info, TRUE);
217       if (tz->transitions != NULL)
218         g_array_free (tz->transitions, TRUE);
219       g_free (tz->name);
220
221       g_slice_free (GTimeZone, tz);
222     }
223
224   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
225                                                           ref_count,
226                                                           ref_count - 1))
227     goto again;
228 }
229
230 /**
231  * g_time_zone_ref:
232  * @tz: a #GTimeZone
233  *
234  * Increases the reference count on @tz.
235  *
236  * Returns: a new reference to @tz.
237  *
238  * Since: 2.26
239  **/
240 GTimeZone *
241 g_time_zone_ref (GTimeZone *tz)
242 {
243   g_assert (tz->ref_count > 0);
244
245   g_atomic_int_inc (&tz->ref_count);
246
247   return tz;
248 }
249
250 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
251 /*
252  * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
253  *  - h[h] is 0 to 23
254  *  - mm is 00 to 59
255  *  - ss is 00 to 59
256  */
257 static gboolean
258 parse_time (const gchar *time_,
259             gint32      *offset)
260 {
261   if (*time_ < '0' || '9' < *time_)
262     return FALSE;
263
264   *offset = 60 * 60 * (*time_++ - '0');
265
266   if (*time_ == '\0')
267     return TRUE;
268
269   if (*time_ != ':')
270     {
271       if (*time_ < '0' || '9' < *time_)
272         return FALSE;
273
274       *offset *= 10;
275       *offset += 60 * 60 * (*time_++ - '0');
276
277       if (*offset > 23 * 60 * 60)
278         return FALSE;
279
280       if (*time_ == '\0')
281         return TRUE;
282     }
283
284   if (*time_ == ':')
285     time_++;
286
287   if (*time_ < '0' || '5' < *time_)
288     return FALSE;
289
290   *offset += 10 * 60 * (*time_++ - '0');
291
292   if (*time_ < '0' || '9' < *time_)
293     return FALSE;
294
295   *offset += 60 * (*time_++ - '0');
296
297   if (*time_ == '\0')
298     return TRUE;
299
300   if (*time_ == ':')
301     time_++;
302
303   if (*time_ < '0' || '5' < *time_)
304     return FALSE;
305
306   *offset += 10 * (*time_++ - '0');
307
308   if (*time_ < '0' || '9' < *time_)
309     return FALSE;
310
311   *offset += *time_++ - '0';
312
313   return *time_ == '\0';
314 }
315
316 static gboolean
317 parse_constant_offset (const gchar *name,
318                        gint32      *offset)
319 {
320   if (g_strcmp0 (name, "UTC") == 0)
321     {
322       *offset = 0;
323       return TRUE;
324     }
325
326   if (*name >= '0' && '9' >= *name)
327     return parse_time (name, offset);
328
329   switch (*name++)
330     {
331     case 'Z':
332       *offset = 0;
333       return !*name;
334
335     case '+':
336       return parse_time (name, offset);
337
338     case '-':
339       if (parse_time (name, offset))
340         {
341           *offset = -*offset;
342           return TRUE;
343         }
344
345     default:
346       return FALSE;
347     }
348 }
349
350 static void
351 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
352 {
353   gint32 offset;
354   TransitionInfo info;
355
356   if (name == NULL || !parse_constant_offset (name, &offset))
357     return;
358
359   info.gmt_offset = offset;
360   info.is_dst = FALSE;
361   info.is_standard = TRUE;
362   info.is_gmt = TRUE;
363   info.abbrev =  g_strdup (name);
364
365
366   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
367   g_array_append_val (gtz->t_info, info);
368
369   /* Constant offset, no transitions */
370   gtz->transitions = NULL;
371 }
372
373 #ifdef G_OS_UNIX
374 static GBytes*
375 zone_info_unix (const gchar *identifier)
376 {
377   gchar *filename;
378   GMappedFile *file = NULL;
379   GBytes *zoneinfo = NULL;
380
381   /* identifier can be a relative or absolute path name;
382      if relative, it is interpreted starting from /usr/share/zoneinfo
383      while the POSIX standard says it should start with :,
384      glibc allows both syntaxes, so we should too */
385   if (identifier != NULL)
386     {
387       const gchar *tzdir;
388
389       tzdir = getenv ("TZDIR");
390       if (tzdir == NULL)
391         tzdir = "/usr/share/zoneinfo";
392
393       if (*identifier == ':')
394         identifier ++;
395
396       if (g_path_is_absolute (identifier))
397         filename = g_strdup (identifier);
398       else
399         filename = g_build_filename (tzdir, identifier, NULL);
400     }
401   else
402     filename = g_strdup ("/etc/localtime");
403
404   file = g_mapped_file_new (filename, FALSE, NULL);
405   if (file != NULL)
406     {
407       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
408                                              g_mapped_file_get_length (file),
409                                              (GDestroyNotify)g_mapped_file_unref,
410                                              g_mapped_file_ref (file));
411       g_mapped_file_unref (file);
412     }
413   g_free (filename);
414   return zoneinfo;
415 }
416
417 static void
418 init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
419 {
420   gsize size;
421   guint index;
422   guint32 time_count, type_count, leap_count, isgmt_count;
423   guint32  isstd_count, char_count ;
424   gpointer tz_transitions, tz_type_index, tz_ttinfo;
425   gpointer  tz_leaps, tz_isgmt, tz_isstd;
426   gchar* tz_abbrs;
427   guint timesize = sizeof (gint32), countsize = sizeof (gint32);
428   const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
429
430   g_return_if_fail (size >= sizeof (struct tzhead) &&
431                     memcmp (header, "TZif", 4) == 0);
432
433   if (header->tzh_version == '2')
434       {
435         /* Skip ahead to the newer 64-bit data if it's available. */
436         header = (const struct tzhead *)
437           (((const gchar *) (header + 1)) +
438            guint32_from_be(header->tzh_ttisgmtcnt) +
439            guint32_from_be(header->tzh_ttisstdcnt) +
440            8 * guint32_from_be(header->tzh_leapcnt) +
441            5 * guint32_from_be(header->tzh_timecnt) +
442            6 * guint32_from_be(header->tzh_typecnt) +
443            guint32_from_be(header->tzh_charcnt));
444         timesize = sizeof (gint64);
445       }
446   time_count = guint32_from_be(header->tzh_timecnt);
447   type_count = guint32_from_be(header->tzh_typecnt);
448   leap_count = guint32_from_be(header->tzh_leapcnt);
449   isgmt_count = guint32_from_be(header->tzh_ttisgmtcnt);
450   isstd_count = guint32_from_be(header->tzh_ttisstdcnt);
451   char_count = guint32_from_be(header->tzh_charcnt);
452
453   g_assert (type_count == isgmt_count);
454   g_assert (type_count == isstd_count);
455
456   tz_transitions = (gpointer)(header + 1);
457   tz_type_index = tz_transitions + timesize * time_count;
458   tz_ttinfo = tz_type_index + time_count;
459   tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
460   tz_leaps = tz_abbrs + char_count;
461   tz_isstd = tz_leaps + (timesize + countsize) * leap_count;
462   tz_isgmt = tz_isstd + isstd_count;
463
464   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
465                                    type_count);
466   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
467                                         time_count);
468
469   for (index = 0; index < type_count; index++)
470     {
471       TransitionInfo t_info;
472       struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
473       t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
474       t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
475       t_info.is_standard = ((guint8*)tz_isstd)[index] ? TRUE : FALSE;
476       t_info.is_gmt = ((guint8*)tz_isgmt)[index] ? TRUE : FALSE;
477       t_info.abbrev = g_strdup (&tz_abbrs[info.tt_abbrind]);
478       g_array_append_val (gtz->t_info, t_info);
479     }
480
481   for (index = 0; index < time_count; index++)
482     {
483       Transition trans;
484       if (header->tzh_version == '2')
485         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
486       else
487         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
488       trans.info_index = ((guint8*)tz_type_index)[index];
489       g_assert (trans.info_index >= 0);
490       g_assert (trans.info_index < gtz->t_info->len);
491       g_array_append_val (gtz->transitions, trans);
492     }
493   g_bytes_unref (zoneinfo);
494 }
495
496 #elif defined (G_OS_WIN32)
497
498 /* UTC = local time + bias while local time = UTC + offset */
499 static void
500 rule_from_windows_time_zone_info (TimeZoneRule *rules,
501                                   LONG          Bias,
502                                   LONG          StandardBias,
503                                   LONG          DaylightBias,
504                                   SYSTEMTIME    StandardDate,
505                                   SYSTEMTIME    DaylightDate)
506 {
507   /* Set offset */
508   if (StandardDate.wMonth)
509     {
510       rules->std_offset = -(Bias + StandardBias) * 60;
511       rules->dlt_offset = -(Bias + DaylightBias) * 60;
512
513       rules->dlt_start.sec = DaylightDate.wSecond;
514       rules->dlt_start.min = DaylightDate.wMinute;
515       rules->dlt_start.hour = DaylightDate.wHour;
516       rules->dlt_start.mon = DaylightDate.wMonth;
517       rules->dlt_start.year = DaylightDate.wYear;
518       rules->dlt_start.wday = DaylightDate.wDayOfWeek? DaylightDate.wDayOfWeek : 7;
519
520       if (DaylightDate.wYear)
521         {
522           rules->dlt_start.mday = DaylightDate.wDay;
523           rules->dlt_start.wday = 0;
524         }
525       else
526         rules->dlt_start.week = DaylightDate.wDay;
527
528       rules->dlt_start.isstd = FALSE;
529       rules->dlt_start.isgmt = FALSE;
530
531       rules->dlt_end.sec = StandardDate.wSecond;
532       rules->dlt_end.min = StandardDate.wMinute;
533       rules->dlt_end.hour = StandardDate.wHour;
534       rules->dlt_end.mday = StandardDate.wDay;
535       rules->dlt_end.mon = StandardDate.wMonth;
536       rules->dlt_end.year = StandardDate.wYear;
537       rules->dlt_end.wday = StandardDate.wDayOfWeek? StandardDate.wDayOfWeek : 7;
538
539       if (StandardDate.wYear)
540         {
541           rules->dlt_end.mday = StandardDate.wDay;
542           rules->dlt_end.wday = 0;
543         }
544       else
545         rules->dlt_end.week = StandardDate.wDay;
546
547       rules->dlt_end.isstd = FALSE;
548       rules->dlt_end.isgmt = FALSE;
549     }
550
551   else
552     {
553       rules->std_offset = -Bias * 60;
554
555       rules->dlt_start.mon = 0;
556     }
557 }
558
559 static gboolean
560 rules_from_windows_time_zone (const gchar   *identifier,
561                               TimeZoneRule **rules,
562                               gint          *rules_num,
563                               gchar        **std_name,
564                               gchar        **dlt_name)
565 {
566   HKEY key;
567   gchar *subkey, *subkey_dynamic;
568   gchar *key_name;
569
570   /* REG_TZI_FORMAT */
571   struct {
572     LONG Bias;
573     LONG StandardBias;
574     LONG DaylightBias;
575     SYSTEMTIME StandardDate;
576     SYSTEMTIME DaylightDate;
577   } tzi, tzi_prev;
578   DWORD size;
579
580   *rules = NULL;
581   *rules_num = 0;
582   *std_name = NULL;
583   *dlt_name = NULL;
584
585   key_name = NULL;
586
587   if (!identifier)
588     {
589       subkey = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
590
591       if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
592                          KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
593         {
594           size = 0;
595           if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
596                                 NULL, &size) == ERROR_SUCCESS)
597             {
598               key_name = g_malloc (size);
599
600               if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
601                                     (LPBYTE) key_name, &size) != ERROR_SUCCESS)
602                 {
603                   g_free (key_name);
604                   key_name = NULL;
605                 }
606             }
607
608           RegCloseKey (key);
609         }
610     }
611   else
612     key_name = g_strdup (identifier);
613
614   if (!key_name)
615     return FALSE;
616
617   subkey = g_strconcat ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\",
618                         key_name,
619                         NULL);
620
621   subkey_dynamic = g_strconcat (subkey, "\\Dynamic DST", NULL);
622
623   if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey_dynamic, 0,
624                      KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
625     {
626       DWORD first, last;
627       int year, i;
628       gchar *s;
629
630       size = sizeof first;
631       if (RegQueryValueExA (key, "FirstEntry", NULL, NULL,
632                             (LPBYTE) &first, &size) != ERROR_SUCCESS)
633         goto failed;
634
635       size = sizeof last;
636       if (RegQueryValueExA (key, "LastEntry", NULL, NULL,
637                             (LPBYTE) &last, &size) != ERROR_SUCCESS)
638         goto failed;
639
640       *rules_num = last - first + 2;
641       *rules = g_new0 (TimeZoneRule, *rules_num);
642
643       for (year = first, i = 0; year <= last; year++)
644         {
645           s = g_strdup_printf ("%d", year);
646
647           size = sizeof tzi;
648           if (RegQueryValueExA (key, s, NULL, NULL,
649                             (LPBYTE) &tzi, &size) != ERROR_SUCCESS)
650             {
651               g_free (*rules);
652               *rules = NULL;
653               break;
654             }
655
656           g_free (s);
657
658           if (year > first && memcmp (&tzi_prev, &tzi, sizeof tzi) == 0)
659               continue;
660           else
661             memcpy (&tzi_prev, &tzi, sizeof tzi);
662
663           rule_from_windows_time_zone_info (&(*rules)[i], tzi.Bias,
664                                             tzi.StandardBias, tzi.DaylightBias,
665                                             tzi.StandardDate, tzi.DaylightDate);
666
667           (*rules)[i++].start_year = year;
668         }
669
670       *rules_num = i + 1;
671
672 failed:
673       RegCloseKey (key);
674     }
675   else if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
676                           KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
677     {
678       size = sizeof tzi;
679       if (RegQueryValueExA (key, "TZI", NULL, NULL,
680                             (LPBYTE) &tzi, &size) == ERROR_SUCCESS)
681         {
682           *rules_num = 2;
683           *rules = g_new0 (TimeZoneRule, 2);
684
685           rule_from_windows_time_zone_info (&(*rules)[0], tzi.Bias,
686                                             tzi.StandardBias, tzi.DaylightBias,
687                                             tzi.StandardDate, tzi.DaylightDate);
688         }
689
690       RegCloseKey (key);
691     }
692
693   g_free (subkey_dynamic);
694   g_free (subkey);
695   g_free (key_name);
696
697   if (*rules)
698     {
699       (*rules)[0].start_year = MIN_TZYEAR;
700       if ((*rules)[*rules_num - 2].start_year < MAX_TZYEAR)
701         (*rules)[*rules_num - 1].start_year = MAX_TZYEAR;
702       else
703         (*rules)[*rules_num - 1].start_year = (*rules)[*rules_num - 2].start_year + 1;
704
705       return TRUE;
706     }
707   else
708     return FALSE;
709 }
710
711 #endif
712
713 static void
714 find_relative_date (TimeZoneDate *buffer,
715                     GTimeZone    *tz)
716 {
717   GDateTime *dt;
718   gint wday;
719
720   wday = buffer->wday;
721
722   /* Get last day if last is needed, first day otherwise */
723   dt = g_date_time_new (tz,
724                         buffer->year,
725                         buffer->mon + (buffer->week < 5? 0 : 1),
726                         buffer->week < 5? 1 : 0,
727                         buffer->hour, buffer->min, buffer->sec);
728
729   buffer->wday = g_date_time_get_day_of_week (dt);
730   buffer->mday = g_date_time_get_day_of_month (dt);
731
732   if (buffer->week < 5)
733     {
734       if (wday < buffer->wday)
735         buffer->wday -= 7;
736
737       buffer->mday += (buffer->week - 1) * 7;
738     }
739
740   else if (wday > buffer->wday)
741     buffer->wday += 7;
742
743   buffer->mday += wday - buffer->wday;
744   buffer->wday = wday;
745
746   g_date_time_unref (dt);
747 }
748
749 /* Offset is previous offset of local time */
750 static gint64
751 boundary_for_year (TimeZoneDate *boundary,
752                    gint          year,
753                    gint32        prev_offset,
754                    gint32        std_offset)
755 {
756   TimeZoneDate buffer;
757   GDateTime *dt;
758   GTimeZone *tz;
759   gint64 t;
760   gint32 offset;
761   gchar *identifier;
762
763   buffer = *boundary;
764
765   if (boundary->isgmt)
766     offset = 0;
767   else if (boundary->isstd)
768     offset = std_offset;
769   else
770     offset = prev_offset;
771
772   G_UNLOCK (time_zones);
773
774   identifier = g_strdup_printf ("%+03d:%02d:%02d",
775                                 (int) offset / 3600,
776                                 (int) abs (offset / 60) % 60,
777                                 (int) abs (offset) % 3600);
778   tz = g_time_zone_new (identifier);
779   g_free (identifier);
780
781   if (boundary->year == 0)
782     {
783       buffer.year = year;
784
785       if (buffer.wday)
786         find_relative_date (&buffer, tz);
787     }
788
789   g_assert (buffer.year == year);
790
791   dt = g_date_time_new (tz,
792                         buffer.year, buffer.mon, buffer.mday,
793                         buffer.hour, buffer.min, buffer.sec);
794   t = g_date_time_to_unix (dt);
795   g_date_time_unref (dt);
796
797   g_time_zone_unref (tz);
798
799   G_LOCK (time_zones);
800
801   return t;
802 }
803
804 static void
805 init_zone_from_rules (GTimeZone    *gtz,
806                       TimeZoneRule *rules,
807                       gint          rules_num)
808 {
809   TransitionInfo info[2];
810   Transition trans;
811   gint type_count, trans_count;
812   gint year, i, x, y;
813   gint32 last_offset;
814
815   type_count = 0;
816   trans_count = 0;
817
818   /* Last rule only contains max year */
819   for (i = 0; i < rules_num - 1; i++)
820     {
821       if (rules[i].dlt_start.mon)
822         {
823           type_count += 2;
824           trans_count += 2 * (rules[i+1].start_year - rules[i].start_year);
825         }
826       else
827         type_count++;
828     }
829
830   x = 0;
831   y = 0;
832
833   /* If standard time happens before daylight time in first rule
834    * with daylight, skip first transition so the minimum is in
835    * standard time and the first transition is in daylight time */
836   for (i = 0; i < rules_num - 1 && rules[0].dlt_start.mon == 0; i++);
837
838   if (i < rules_num -1 && rules[i].dlt_start.mon > 0 &&
839       rules[i].dlt_start.mon > rules[i].dlt_end.mon)
840     {
841       trans_count--;
842       x = -1;
843     }
844
845   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
846   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
847
848   last_offset = rules[0].std_offset;
849
850   for (i = 0; i < rules_num - 1; i++)
851     {
852       if (rules[i].dlt_start.mon)
853         {
854           /* Standard */
855           info[0].gmt_offset = rules[i].std_offset;
856           info[0].is_dst = FALSE;
857           info[0].is_standard = rules[i].dlt_end.isstd;
858           info[0].is_gmt = rules[i].dlt_end.isgmt;
859
860           if (rules[i].std_name)
861             info[0].abbrev = g_strdup (rules[i].std_name);
862
863           else
864             info[0].abbrev = g_strdup_printf ("%+03d%02d",
865                                               (int) rules[i].std_offset / 3600,
866                                               (int) abs (rules[i].std_offset / 60) % 60);
867
868
869           /* Daylight */
870           info[1].gmt_offset = rules[i].dlt_offset;
871           info[1].is_dst = TRUE;
872           info[1].is_standard = rules[i].dlt_start.isstd;
873           info[1].is_gmt = rules[i].dlt_start.isgmt;
874
875           if (rules[i].dlt_name)
876             info[1].abbrev = g_strdup (rules[i].dlt_name);
877
878           else
879             info[1].abbrev = g_strdup_printf ("%+03d%02d",
880                                               (int) rules[i].dlt_offset / 3600,
881                                               (int) abs (rules[i].dlt_offset / 60) % 60);
882
883           if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
884             {
885               g_array_append_val (gtz->t_info, info[1]);
886               g_array_append_val (gtz->t_info, info[0]);
887             }
888           else
889             {
890               g_array_append_val (gtz->t_info, info[0]);
891               g_array_append_val (gtz->t_info, info[1]);
892             }
893
894           /* Transition dates */
895           for (year = rules[i].start_year; year < rules[i+1].start_year; year++)
896             {
897               if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
898                 {
899                   /* Daylight Data */
900                   trans.info_index = y;
901                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
902                                                   last_offset, rules[i].std_offset);
903                   g_array_insert_val (gtz->transitions, x++, trans);
904                   last_offset = rules[i].dlt_offset;
905
906                   /* Standard Data */
907                   trans.info_index = y+1;
908                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
909                                                   last_offset, rules[i].std_offset);
910                   g_array_insert_val (gtz->transitions, x++, trans);
911                   last_offset = rules[i].std_offset;
912                 }
913               else
914                 {
915                   /* Standard Data */
916                   trans.info_index = y;
917                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
918                                                   last_offset, rules[i].std_offset);
919                   if (x >= 0)
920                     g_array_insert_val (gtz->transitions, x++, trans);
921                   else
922                     x++;
923                   last_offset = rules[i].std_offset;
924
925                   /* Daylight Data */
926                   trans.info_index = y+1;
927                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
928                                                   last_offset, rules[i].std_offset);
929                   g_array_insert_val (gtz->transitions, x++, trans);
930                   last_offset = rules[i].dlt_offset;
931                 }
932             }
933
934           y += 2;
935         }
936       else
937         {
938           /* Standard */
939           info[0].gmt_offset = rules[i].std_offset;
940           info[0].is_dst = FALSE;
941           info[0].is_standard = FALSE;
942           info[0].is_gmt = FALSE;
943
944           if (rules[i].std_name)
945             info[0].abbrev = g_strdup (rules[i].std_name);
946
947           else
948             info[0].abbrev = g_strdup_printf ("%+03d%02d",
949                                               (int) rules[i].std_offset / 3600,
950                                               (int) abs (rules[i].std_offset / 60) % 60);
951
952           g_array_append_val (gtz->t_info, info[0]);
953
954           last_offset = rules[i].std_offset;
955
956           y++;
957         }
958     }
959 }
960
961 /*
962  * parses date[/time] for parsing TZ environment variable
963  *
964  * date is either Mm.w.d, Jn or N
965  * - m is 1 to 12
966  * - w is 1 to 5
967  * - d is 0 to 6
968  * - n is 1 to 365
969  * - N is 0 to 365
970  *
971  * time is either h or hh[[:]mm[[[:]ss]]]
972  *  - h[h] is 0 to 23
973  *  - mm is 00 to 59
974  *  - ss is 00 to 59
975  */
976 static gboolean
977 parse_tz_boundary (const gchar  *identifier,
978                    TimeZoneDate *boundary)
979 {
980   const gchar *pos;
981   gint month, week, day;
982   GDate *date;
983
984   pos = identifier;
985
986   if (*pos == 'M')                      /* Relative date */
987     {
988       pos++;
989
990       if (*pos == '\0' || *pos < '0' || '9' < *pos)
991         return FALSE;
992
993       month = *pos++ - '0';
994
995       if ((month == 1 && *pos >= '0' && '2' >= *pos) ||
996           (month == 0 && *pos >= '0' && '9' >= *pos))
997         {
998           month *= 10;
999           month += *pos++ - '0';
1000         }
1001
1002       if (*pos++ != '.' || month == 0)
1003         return FALSE;
1004
1005       if (*pos == '\0' || *pos < '1' || '5' < *pos)
1006         return FALSE;
1007
1008       week = *pos++ - '0';
1009
1010       if (*pos++ != '.')
1011         return FALSE;
1012
1013       if (*pos == '\0' || *pos < '0' || '6' < *pos)
1014         return FALSE;
1015
1016       day = *pos++ - '0';
1017
1018       if (!day)
1019         day += 7;
1020
1021       boundary->year = 0;
1022       boundary->mon = month;
1023       boundary->week = week;
1024       boundary->wday = day;
1025     }
1026
1027   else if (*pos == 'J')                 /* Julian day */
1028     {
1029       pos++;
1030
1031       day = 0;
1032       while (*pos >= '0' && '9' >= *pos)
1033         {
1034           day *= 10;
1035           day += *pos++ - '0';
1036         }
1037
1038       if (day < 1 || 365 < day)
1039         return FALSE;
1040
1041       date = g_date_new_julian (day);
1042       boundary->year = 0;
1043       boundary->mon = (int) g_date_get_month (date);
1044       boundary->mday = (int) g_date_get_day (date);
1045       boundary->wday = 0;
1046       g_date_free (date);
1047     }
1048
1049   else if (*pos >= '0' && '9' >= *pos)  /* Zero-based Julian day */
1050     {
1051       day = 0;
1052       while (*pos >= '0' && '9' >= *pos)
1053         {
1054           day *= 10;
1055           day += *pos++ - '0';
1056         }
1057
1058       if (day < 0 || 365 < day)
1059         return FALSE;
1060
1061       date = g_date_new_julian (day >= 59? day : day + 1);
1062       boundary->year = 0;
1063       boundary->mon = (int) g_date_get_month (date);
1064       boundary->mday = (int) g_date_get_day (date);
1065       boundary->wday = 0;
1066       g_date_free (date);
1067
1068       /* February 29 */
1069       if (day == 59)
1070         boundary->mday++;
1071     }
1072
1073   else
1074     return FALSE;
1075
1076   /* Time */
1077   boundary->isstd = FALSE;
1078   boundary->isgmt = FALSE;
1079
1080   if (*pos == '/')
1081     {
1082       gint32 offset;
1083
1084       if (!parse_time (++pos, &offset))
1085         return FALSE;
1086
1087       boundary->hour = offset / 3600;
1088       boundary->min = (offset / 60) % 60;
1089       boundary->sec = offset % 3600;
1090
1091       return TRUE;
1092     }
1093
1094   else
1095     {
1096       boundary->hour = 2;
1097       boundary->min = 0;
1098       boundary->sec = 0;
1099
1100       return *pos == '\0';
1101     }
1102 }
1103
1104 /*
1105  * Creates an array of TimeZoneRule from a TZ environment variable
1106  * type of identifier.  Should free rules, std_name and dlt_name
1107  * afterwards
1108  */
1109 static gboolean
1110 rules_from_identifier (const gchar   *identifier,
1111                        TimeZoneRule **rules,
1112                        gint          *rules_num,
1113                        gchar        **std_name,
1114                        gchar        **dlt_name)
1115 {
1116   const gchar *std_name_pos, *std_offset_pos;
1117   const gchar *dlt_name_pos, *dlt_offset_pos;
1118
1119   const gchar *start_date_pos, *end_date_pos;
1120
1121   const gchar *pos;
1122   gchar *buffer;
1123   gboolean ret;
1124
1125   gint32 std_offset, dlt_offset;
1126   TimeZoneDate dlt_start, dlt_end;
1127
1128   if (!identifier)
1129     return FALSE;
1130
1131   pos = identifier;
1132
1133   std_name_pos = pos;
1134
1135   /* Name is alpha */
1136   while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
1137     pos++;
1138
1139   /* Offset for standard required (format 1) */
1140   if (*pos == '\0')
1141     return FALSE;
1142
1143   /* Name should be three or more alphabetic characters */
1144   if (pos - identifier < 3)
1145     return FALSE;
1146
1147   std_offset_pos = pos;
1148
1149   /* Standard offset */
1150   while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
1151     pos++;
1152
1153   buffer = g_strndup (std_offset_pos, pos - std_offset_pos);
1154   ret = parse_constant_offset (buffer, &std_offset);
1155   g_free (buffer);
1156
1157   if (!ret)
1158     return FALSE;
1159
1160   dlt_name_pos = pos;
1161   dlt_offset_pos = NULL;
1162
1163   /* Format 2 */
1164   if (*pos != '\0')
1165     {
1166       /* Name is alpha */
1167       while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
1168         pos++;
1169
1170       /* Name should be three or more alphabetic characters */
1171       if (pos - identifier < 3)
1172         return FALSE;
1173
1174       dlt_offset_pos = pos;
1175
1176 #ifndef G_OS_WIN32
1177       /* Start and end required (format 2) */
1178       if (*pos == '\0')
1179         return FALSE;
1180 #else
1181       if (*pos != '\0')
1182         {
1183 #endif
1184
1185       /* Default offset is 1 hour less from standard offset */
1186       if (*pos++ == ',')
1187         dlt_offset = std_offset - 60 * 60;
1188
1189       else
1190         {
1191           /* Daylight offset */
1192           while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
1193             pos++;
1194
1195           buffer = g_strndup (dlt_offset_pos, pos - dlt_offset_pos);
1196           ret = parse_constant_offset (buffer, &dlt_offset);
1197           g_free (buffer);
1198
1199           if (!ret)
1200             return FALSE;
1201
1202           /* Start and end required (format 2) */
1203           if (*pos++ != ',')
1204             return FALSE;
1205         }
1206
1207       /* Start date */
1208       start_date_pos = pos;
1209
1210       while (*pos != ',' && *pos != '\0')
1211         pos++;
1212
1213       /* End required (format 2) */
1214       if (*pos == '\0')
1215         return FALSE;
1216
1217       buffer = g_strndup (start_date_pos, pos++ - start_date_pos);
1218       ret = parse_tz_boundary (buffer, &dlt_start);
1219       g_free (buffer);
1220
1221       if (!ret)
1222         return FALSE;
1223
1224       /* End date */
1225       end_date_pos = pos;
1226
1227       while (*pos != '\0')
1228         pos++;
1229
1230       buffer = g_strndup (end_date_pos, pos - end_date_pos);
1231       ret = parse_tz_boundary (buffer, &dlt_end);
1232       g_free (buffer);
1233
1234       if (!ret)
1235         return FALSE;
1236
1237 #ifdef G_OS_WIN32
1238       }
1239 #endif
1240     }
1241
1242
1243   *std_name = g_strndup (std_name_pos, std_offset_pos - std_name_pos);
1244
1245   if (dlt_name_pos != pos)
1246     *dlt_name = g_strndup (dlt_name_pos, dlt_offset_pos - dlt_name_pos);
1247   else
1248     {
1249       *dlt_name = NULL;
1250       dlt_start.mon = 0;
1251     }
1252
1253
1254 #ifdef G_OS_WIN32
1255   /* If doesn't have offset for daylight then it is Windows format */
1256   if (dlt_offset_pos == pos)
1257     {
1258       int i;
1259
1260       /* Use US rules, Windows' default is Pacific Standard Time */
1261       dlt_offset = std_offset - 60 * 60;
1262
1263       if (rules_from_windows_time_zone ("Pacific Standard Time", rules, rules_num, NULL, NULL))
1264         {
1265           for (i = 0; i < *rules_num - 1; i++)
1266             {
1267               (*rules)[i].std_offset = -std_offset;
1268               (*rules)[i].dlt_offset = -dlt_offset;
1269               (*rules)[i].std_name = *std_name;
1270               (*rules)[i].dlt_name = *dlt_name;
1271             }
1272
1273           return TRUE;
1274         }
1275       else
1276         return FALSE;
1277     }
1278 #endif
1279
1280
1281   *rules_num = 2;
1282   *rules = g_new0 (TimeZoneRule, 2);
1283
1284   (*rules)[0].start_year = MIN_TZYEAR;
1285   (*rules)[1].start_year = MAX_TZYEAR;
1286
1287   (*rules)[0].std_offset = -std_offset;
1288   (*rules)[0].dlt_offset = -dlt_offset;
1289   (*rules)[0].dlt_start  = dlt_start;
1290   (*rules)[0].dlt_end = dlt_end;
1291   (*rules)[0].std_name = *std_name;
1292   (*rules)[0].dlt_name = *dlt_name;
1293
1294   return TRUE;
1295 }
1296
1297 /* Construction {{{1 */
1298 /**
1299  * g_time_zone_new:
1300  * @identifier: (allow-none): a timezone identifier
1301  *
1302  * Creates a #GTimeZone corresponding to @identifier.
1303  *
1304  * @identifier can either be an RFC3339/ISO 8601 time offset or
1305  * something that would pass as a valid value for the
1306  * <varname>TZ</varname> environment variable (including %NULL).
1307  *
1308  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
1309  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
1310  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.  Offsets are
1311  * time values to be added to Coordinated Universal Time (UTC) to get
1312  * the local time.
1313  *
1314  * In Unix, the <varname>TZ</varname> environment variable typically
1315  * corresponds to the name of a file in the zoneinfo database, or
1316  * string in "std offset [dst [offset],start[/time],end[/time]]"
1317  * (POSIX) format.  There  are  no spaces in the specification.  The
1318  * name of standard and daylight savings time zone must be three or more
1319  * alphabetic characters.  Offsets are time values to be added to local
1320  * time to get Coordinated Universal Time (UTC) and should be
1321  * <literal>"[±]hh[[:]mm[:ss]]"</literal>.  Dates are either
1322  * <literal>"Jn"</literal> (Julian day with n between 1 and 365, leap
1323  * years not counted), <literal>"n"</literal> (zero-based Julian day
1324  * with n between 0 and 365) or <literal>"Mm.w.d"</literal> (day d
1325  * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
1326  * 0 is a Sunday).  Times are in local wall clock time, the default is
1327  * 02:00:00.
1328  *
1329  * In Windows, the "tzn[+|–]hh[:mm[:ss]][dzn]" format is used, but also
1330  * accepts POSIX format.  The Windows format uses US rules for all time
1331  * zones; daylight savings time is 60 minutes behind the standard time
1332  * with date and time of change taken from Pacific Standard Time.
1333  * Offsets are time values to be added to the local time to get
1334  * Coordinated Universal Time (UTC).
1335  *
1336  * g_time_zone_new_local() calls this function with the value of the
1337  * <varname>TZ</varname> environment variable.  This function itself is
1338  * independent of the value of <varname>TZ</varname>, but if @identifier
1339  * is %NULL then <filename>/etc/localtime</filename> will be consulted
1340  * to discover the correct timezone.
1341  *
1342  * If intervals are not available, only time zone rules from
1343  * <varname>TZ</varname> environment variable or other means, then they
1344  * will be computed from year 1900 to 2037.  If the maximum year for the
1345  * rules is available and it is greater than 2037, then it will followed
1346  * instead.
1347  *
1348  * See <ulink
1349  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
1350  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
1351  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
1352  * full list of valid time offsets.  See <ulink
1353  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
1354  * GNU C Library manual</ulink> for an explanation of the possible
1355  * values of the <varname>TZ</varname> environment variable.
1356  *
1357  * You should release the return value by calling g_time_zone_unref()
1358  * when you are done with it.
1359  *
1360  * Returns: the requested timezone
1361  *
1362  * Since: 2.26
1363  **/
1364 GTimeZone *
1365 g_time_zone_new (const gchar *identifier)
1366 {
1367   GTimeZone *tz = NULL;
1368   TimeZoneRule *rules;
1369   gint rules_num;
1370   gchar *std_name, *dlt_name;
1371
1372   G_LOCK (time_zones);
1373   if (time_zones == NULL)
1374     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
1375
1376   if (identifier)
1377     {
1378       tz = g_hash_table_lookup (time_zones, identifier);
1379       if (tz)
1380         {
1381           g_atomic_int_inc (&tz->ref_count);
1382           G_UNLOCK (time_zones);
1383           return tz;
1384         }
1385     }
1386
1387   tz = g_slice_new0 (GTimeZone);
1388   tz->name = g_strdup (identifier);
1389   tz->ref_count = 0;
1390
1391   zone_for_constant_offset (tz, identifier);
1392
1393   if (tz->t_info == NULL &&
1394       rules_from_identifier (identifier,
1395                              &rules, &rules_num,
1396                              &std_name, &dlt_name))
1397     {
1398       init_zone_from_rules (tz, rules, rules_num);
1399       g_free (rules);
1400       g_free (std_name);
1401       g_free (dlt_name);
1402     }
1403
1404   if (tz->t_info == NULL)
1405     {
1406 #ifdef G_OS_UNIX
1407       GBytes *zoneinfo = zone_info_unix (identifier);
1408       if (!zoneinfo)
1409         zone_for_constant_offset (tz, "UTC");
1410       else
1411         {
1412           init_zone_from_iana_info (tz, zoneinfo);
1413           g_bytes_unref (zoneinfo);
1414         }
1415 #elif defined G_OS_WIN32
1416 #endif
1417     }
1418
1419   if (tz->t_info != NULL)
1420     {
1421       if (identifier)
1422         g_hash_table_insert (time_zones, tz->name, tz);
1423     }
1424   g_atomic_int_inc (&tz->ref_count);
1425   G_UNLOCK (time_zones);
1426
1427   return tz;
1428 }
1429
1430 /**
1431  * g_time_zone_new_utc:
1432  *
1433  * Creates a #GTimeZone corresponding to UTC.
1434  *
1435  * This is equivalent to calling g_time_zone_new() with a value like
1436  * "Z", "UTC", "+00", etc.
1437  *
1438  * You should release the return value by calling g_time_zone_unref()
1439  * when you are done with it.
1440  *
1441  * Returns: the universal timezone
1442  *
1443  * Since: 2.26
1444  **/
1445 GTimeZone *
1446 g_time_zone_new_utc (void)
1447 {
1448   return g_time_zone_new ("UTC");
1449 }
1450
1451 /**
1452  * g_time_zone_new_local:
1453  *
1454  * Creates a #GTimeZone corresponding to local time.  The local time
1455  * zone may change between invocations to this function; for example,
1456  * if the system administrator changes it.
1457  *
1458  * This is equivalent to calling g_time_zone_new() with the value of the
1459  * <varname>TZ</varname> environment variable (including the possibility
1460  * of %NULL).
1461  *
1462  * You should release the return value by calling g_time_zone_unref()
1463  * when you are done with it.
1464  *
1465  * Returns: the local timezone
1466  *
1467  * Since: 2.26
1468  **/
1469 GTimeZone *
1470 g_time_zone_new_local (void)
1471 {
1472   return g_time_zone_new (getenv ("TZ"));
1473 }
1474
1475 #define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
1476 #define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
1477
1478 /* Internal helpers {{{1 */
1479 /* Note that interval 0 is *before* the first transition time, so
1480  * interval 1 gets transitions[0].
1481  */
1482 inline static const TransitionInfo*
1483 interval_info (GTimeZone *tz,
1484                guint      interval)
1485 {
1486   guint index;
1487   g_return_val_if_fail (tz->t_info != NULL, NULL);
1488   if (interval && tz->transitions && interval <= tz->transitions->len)
1489     index = (TRANSITION(interval - 1)).info_index;
1490   else
1491     index = 0;
1492   return &(TRANSITION_INFO(index));
1493 }
1494
1495 inline static gint64
1496 interval_start (GTimeZone *tz,
1497                 guint      interval)
1498 {
1499   if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
1500     return G_MININT64;
1501   if (interval > tz->transitions->len)
1502     interval = tz->transitions->len;
1503   return (TRANSITION(interval - 1)).time;
1504 }
1505
1506 inline static gint64
1507 interval_end (GTimeZone *tz,
1508               guint      interval)
1509 {
1510   if (tz->transitions && interval < tz->transitions->len)
1511     return (TRANSITION(interval)).time - 1;
1512   return G_MAXINT64;
1513 }
1514
1515 inline static gint32
1516 interval_offset (GTimeZone *tz,
1517                  guint      interval)
1518 {
1519   g_return_val_if_fail (tz->t_info != NULL, 0);
1520   return interval_info (tz, interval)->gmt_offset;
1521 }
1522
1523 inline static gboolean
1524 interval_isdst (GTimeZone *tz,
1525                 guint      interval)
1526 {
1527   g_return_val_if_fail (tz->t_info != NULL, 0);
1528   return interval_info (tz, interval)->is_dst;
1529 }
1530
1531
1532 inline static gboolean
1533 interval_isgmt (GTimeZone *tz,
1534                 guint      interval)
1535 {
1536   g_return_val_if_fail (tz->t_info != NULL, 0);
1537   return interval_info (tz, interval)->is_gmt;
1538 }
1539
1540 inline static gboolean
1541 interval_isstandard (GTimeZone *tz,
1542                 guint      interval)
1543 {
1544   return interval_info (tz, interval)->is_standard;
1545 }
1546
1547 inline static gchar*
1548 interval_abbrev (GTimeZone *tz,
1549                   guint      interval)
1550 {
1551   g_return_val_if_fail (tz->t_info != NULL, 0);
1552   return interval_info (tz, interval)->abbrev;
1553 }
1554
1555 inline static gint64
1556 interval_local_start (GTimeZone *tz,
1557                       guint      interval)
1558 {
1559   if (interval)
1560     return interval_start (tz, interval) + interval_offset (tz, interval);
1561
1562   return G_MININT64;
1563 }
1564
1565 inline static gint64
1566 interval_local_end (GTimeZone *tz,
1567                     guint      interval)
1568 {
1569   if (tz->transitions && interval < tz->transitions->len)
1570     return interval_end (tz, interval) + interval_offset (tz, interval);
1571
1572   return G_MAXINT64;
1573 }
1574
1575 static gboolean
1576 interval_valid (GTimeZone *tz,
1577                 guint      interval)
1578 {
1579   if ( tz->transitions == NULL)
1580     return interval == 0;
1581   return interval <= tz->transitions->len;
1582 }
1583
1584 /* g_time_zone_find_interval() {{{1 */
1585
1586 /**
1587  * g_time_zone_adjust_time:
1588  * @tz: a #GTimeZone
1589  * @type: the #GTimeType of @time_
1590  * @time_: a pointer to a number of seconds since January 1, 1970
1591  *
1592  * Finds an interval within @tz that corresponds to the given @time_,
1593  * possibly adjusting @time_ if required to fit into an interval.
1594  * The meaning of @time_ depends on @type.
1595  *
1596  * This function is similar to g_time_zone_find_interval(), with the
1597  * difference that it always succeeds (by making the adjustments
1598  * described below).
1599  *
1600  * In any of the cases where g_time_zone_find_interval() succeeds then
1601  * this function returns the same value, without modifying @time_.
1602  *
1603  * This function may, however, modify @time_ in order to deal with
1604  * non-existent times.  If the non-existent local @time_ of 02:30 were
1605  * requested on March 14th 2010 in Toronto then this function would
1606  * adjust @time_ to be 03:00 and return the interval containing the
1607  * adjusted time.
1608  *
1609  * Returns: the interval containing @time_, never -1
1610  *
1611  * Since: 2.26
1612  **/
1613 gint
1614 g_time_zone_adjust_time (GTimeZone *tz,
1615                          GTimeType  type,
1616                          gint64    *time_)
1617 {
1618   gint i;
1619   guint intervals;
1620
1621   if (tz->transitions == NULL)
1622     return 0;
1623
1624   intervals = tz->transitions->len;
1625
1626   /* find the interval containing *time UTC
1627    * TODO: this could be binary searched (or better) */
1628   for (i = 0; i <= intervals; i++)
1629     if (*time_ <= interval_end (tz, i))
1630       break;
1631
1632   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
1633
1634   if (type != G_TIME_TYPE_UNIVERSAL)
1635     {
1636       if (*time_ < interval_local_start (tz, i))
1637         /* if time came before the start of this interval... */
1638         {
1639           i--;
1640
1641           /* if it's not in the previous interval... */
1642           if (*time_ > interval_local_end (tz, i))
1643             {
1644               /* it doesn't exist.  fast-forward it. */
1645               i++;
1646               *time_ = interval_local_start (tz, i);
1647             }
1648         }
1649
1650       else if (*time_ > interval_local_end (tz, i))
1651         /* if time came after the end of this interval... */
1652         {
1653           i++;
1654
1655           /* if it's not in the next interval... */
1656           if (*time_ < interval_local_start (tz, i))
1657             /* it doesn't exist.  fast-forward it. */
1658             *time_ = interval_local_start (tz, i);
1659         }
1660
1661       else if (interval_isdst (tz, i) != type)
1662         /* it's in this interval, but dst flag doesn't match.
1663          * check neighbours for a better fit. */
1664         {
1665           if (i && *time_ <= interval_local_end (tz, i - 1))
1666             i--;
1667
1668           else if (i < intervals &&
1669                    *time_ >= interval_local_start (tz, i + 1))
1670             i++;
1671         }
1672     }
1673
1674   return i;
1675 }
1676
1677 /**
1678  * g_time_zone_find_interval:
1679  * @tz: a #GTimeZone
1680  * @type: the #GTimeType of @time_
1681  * @time_: a number of seconds since January 1, 1970
1682  *
1683  * Finds an the interval within @tz that corresponds to the given @time_.
1684  * The meaning of @time_ depends on @type.
1685  *
1686  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
1687  * succeed (since universal time is monotonic and continuous).
1688  *
1689  * Otherwise @time_ is treated is local time.  The distinction between
1690  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
1691  * the case that the given @time_ is ambiguous.  In Toronto, for example,
1692  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
1693  * savings time and the next, an hour later, outside of daylight savings
1694  * time).  In this case, the different value of @type would result in a
1695  * different interval being returned.
1696  *
1697  * It is still possible for this function to fail.  In Toronto, for
1698  * example, 02:00 on March 14th 2010 does not exist (due to the leap
1699  * forward to begin daylight savings time).  -1 is returned in that
1700  * case.
1701  *
1702  * Returns: the interval containing @time_, or -1 in case of failure
1703  *
1704  * Since: 2.26
1705  */
1706 gint
1707 g_time_zone_find_interval (GTimeZone *tz,
1708                            GTimeType  type,
1709                            gint64     time_)
1710 {
1711   gint i;
1712   guint intervals;
1713
1714   if (tz->transitions == NULL)
1715     return 0;
1716   intervals = tz->transitions->len;
1717   for (i = 0; i <= intervals; i++)
1718     if (time_ <= interval_end (tz, i))
1719       break;
1720
1721   if (type == G_TIME_TYPE_UNIVERSAL)
1722     return i;
1723
1724   if (time_ < interval_local_start (tz, i))
1725     {
1726       if (time_ > interval_local_end (tz, --i))
1727         return -1;
1728     }
1729
1730   else if (time_ > interval_local_end (tz, i))
1731     {
1732       if (time_ < interval_local_start (tz, ++i))
1733         return -1;
1734     }
1735
1736   else if (interval_isdst (tz, i) != type)
1737     {
1738       if (i && time_ <= interval_local_end (tz, i - 1))
1739         i--;
1740
1741       else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
1742         i++;
1743     }
1744
1745   return i;
1746 }
1747
1748 /* Public API accessors {{{1 */
1749
1750 /**
1751  * g_time_zone_get_abbreviation:
1752  * @tz: a #GTimeZone
1753  * @interval: an interval within the timezone
1754  *
1755  * Determines the time zone abbreviation to be used during a particular
1756  * @interval of time in the time zone @tz.
1757  *
1758  * For example, in Toronto this is currently "EST" during the winter
1759  * months and "EDT" during the summer months when daylight savings time
1760  * is in effect.
1761  *
1762  * Returns: the time zone abbreviation, which belongs to @tz
1763  *
1764  * Since: 2.26
1765  **/
1766 const gchar *
1767 g_time_zone_get_abbreviation (GTimeZone *tz,
1768                               gint       interval)
1769 {
1770   g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
1771
1772   return interval_abbrev (tz, (guint)interval);
1773 }
1774
1775 /**
1776  * g_time_zone_get_offset:
1777  * @tz: a #GTimeZone
1778  * @interval: an interval within the timezone
1779  *
1780  * Determines the offset to UTC in effect during a particular @interval
1781  * of time in the time zone @tz.
1782  *
1783  * The offset is the number of seconds that you add to UTC time to
1784  * arrive at local time for @tz (ie: negative numbers for time zones
1785  * west of GMT, positive numbers for east).
1786  *
1787  * Returns: the number of seconds that should be added to UTC to get the
1788  *          local time in @tz
1789  *
1790  * Since: 2.26
1791  **/
1792 gint32
1793 g_time_zone_get_offset (GTimeZone *tz,
1794                         gint       interval)
1795 {
1796   g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
1797
1798   return interval_offset (tz, (guint)interval);
1799 }
1800
1801 /**
1802  * g_time_zone_is_dst:
1803  * @tz: a #GTimeZone
1804  * @interval: an interval within the timezone
1805  *
1806  * Determines if daylight savings time is in effect during a particular
1807  * @interval of time in the time zone @tz.
1808  *
1809  * Returns: %TRUE if daylight savings time is in effect
1810  *
1811  * Since: 2.26
1812  **/
1813 gboolean
1814 g_time_zone_is_dst (GTimeZone *tz,
1815                     gint       interval)
1816 {
1817   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
1818
1819   if (tz->transitions == NULL)
1820     return FALSE;
1821
1822   return interval_isdst (tz, (guint)interval);
1823 }
1824
1825 /* Epilogue {{{1 */
1826 /* vim:set foldmethod=marker: */