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