GTimeZone: Add initialization functions for rules
[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
42 /**
43  * SECTION:timezone
44  * @title: GTimeZone
45  * @short_description: a structure representing a time zone
46  * @see_also: #GDateTime
47  *
48  * #GTimeZone is a structure that represents a time zone, at no
49  * particular point in time.  It is refcounted and immutable.
50  *
51  * A time zone contains a number of intervals.  Each interval has
52  * an abbreviation to describe it, an offet to UTC and a flag indicating
53  * if the daylight savings time is in effect during that interval.  A
54  * time zone always has at least one interval -- interval 0.
55  *
56  * Every UTC time is contained within exactly one interval, but a given
57  * local time may be contained within zero, one or two intervals (due to
58  * incontinuities associated with daylight savings time).
59  *
60  * An interval may refer to a specific period of time (eg: the duration
61  * of daylight savings time during 2010) or it may refer to many periods
62  * of time that share the same properties (eg: all periods of daylight
63  * savings time).  It is also possible (usually for political reasons)
64  * that some properties (like the abbreviation) change between intervals
65  * without other properties changing.
66  *
67  * #GTimeZone is available since GLib 2.26.
68  */
69
70 /**
71  * GTimeZone:
72  *
73  * #GDateTime is an opaque structure whose members cannot be accessed
74  * directly.
75  *
76  * Since: 2.26
77  **/
78
79 /* zoneinfo file format {{{1 */
80
81 /* unaligned */
82 typedef struct { gchar bytes[8]; } gint64_be;
83 typedef struct { gchar bytes[4]; } gint32_be;
84 typedef struct { gchar bytes[4]; } guint32_be;
85
86 static inline gint64 gint64_from_be (const gint64_be be) {
87   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
88 }
89
90 static inline gint32 gint32_from_be (const gint32_be be) {
91   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
92 }
93
94 static inline guint32 guint32_from_be (const guint32_be be) {
95   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
96 }
97
98 struct tzhead
99 {
100   gchar      tzh_magic[4];
101   gchar      tzh_version;
102   guchar     tzh_reserved[15];
103
104   guint32_be tzh_ttisgmtcnt;
105   guint32_be tzh_ttisstdcnt;
106   guint32_be tzh_leapcnt;
107   guint32_be tzh_timecnt;
108   guint32_be tzh_typecnt;
109   guint32_be tzh_charcnt;
110 };
111
112 struct ttinfo
113 {
114   gint32_be tt_gmtoff;
115   guint8    tt_isdst;
116   guint8    tt_abbrind;
117 };
118
119 typedef struct
120 {
121   gint32     gmt_offset;
122   gboolean   is_dst;
123   gboolean   is_standard;
124   gboolean   is_gmt;
125   gchar     *abbrev;
126 } TransitionInfo;
127
128 typedef struct
129 {
130   gint64 time;
131   gint   info_index;
132 } Transition;
133
134 typedef struct
135 {
136   gint     year;
137   gint     mon;
138   gint     mday;
139   gint     wday;
140   gint     week;
141   gint     hour;
142   gint     min;
143   gint     sec;
144   gboolean isstd;
145   gboolean isgmt;
146 } TimeZoneDate;
147
148 typedef struct
149 {
150   gint         start_year;
151   gint32       std_offset;
152   gint32       dlt_offset;
153   TimeZoneDate dlt_start;
154   TimeZoneDate dlt_end;
155   const gchar *std_name;
156   const gchar *dlt_name;
157 } TimeZoneRule;
158
159
160 /* GTimeZone structure and lifecycle {{{1 */
161 struct _GTimeZone
162 {
163   gchar   *name;
164   GArray  *t_info;
165   GArray  *transitions;
166   gint     ref_count;
167 };
168
169 G_LOCK_DEFINE_STATIC (time_zones);
170 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
171
172 #define MIN_TZYEAR 1900
173 #define MAX_TZYEAR 2038
174
175 /**
176  * g_time_zone_unref:
177  * @tz: a #GTimeZone
178  *
179  * Decreases the reference count on @tz.
180  *
181  * Since: 2.26
182  **/
183 void
184 g_time_zone_unref (GTimeZone *tz)
185 {
186   int ref_count;
187
188 again:
189   ref_count = g_atomic_int_get (&tz->ref_count);
190
191   g_assert (ref_count > 0);
192
193   if (ref_count == 1)
194     {
195       if (tz->name != NULL)
196         {
197           G_LOCK(time_zones);
198
199           /* someone else might have grabbed a ref in the meantime */
200           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
201             {
202               G_UNLOCK(time_zones);
203               goto again;
204             }
205
206           g_hash_table_remove (time_zones, tz->name);
207           G_UNLOCK(time_zones);
208         }
209
210       g_array_free (tz->t_info, TRUE);
211       if (tz->transitions != NULL)
212         g_array_free (tz->transitions, TRUE);
213       g_free (tz->name);
214
215       g_slice_free (GTimeZone, tz);
216     }
217
218   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
219                                                           ref_count,
220                                                           ref_count - 1))
221     goto again;
222 }
223
224 /**
225  * g_time_zone_ref:
226  * @tz: a #GTimeZone
227  *
228  * Increases the reference count on @tz.
229  *
230  * Returns: a new reference to @tz.
231  *
232  * Since: 2.26
233  **/
234 GTimeZone *
235 g_time_zone_ref (GTimeZone *tz)
236 {
237   g_assert (tz->ref_count > 0);
238
239   g_atomic_int_inc (&tz->ref_count);
240
241   return tz;
242 }
243
244 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
245 /*
246  * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
247  *  - h[h] is 0 to 23
248  *  - mm is 00 to 59
249  *  - ss is 00 to 59
250  */
251 static gboolean
252 parse_time (const gchar *time_,
253             gint32      *offset)
254 {
255   if (*time_ < '0' || '9' < *time_)
256     return FALSE;
257
258   *offset = 60 * 60 * (*time_++ - '0');
259
260   if (*time_ == '\0')
261     return TRUE;
262
263   if (*time_ != ':')
264     {
265       if (*time_ < '0' || '9' < *time_)
266         return FALSE;
267
268       *offset *= 10;
269       *offset += 60 * 60 * (*time_++ - '0');
270
271       if (*offset > 23 * 60 * 60)
272         return FALSE;
273
274       if (*time_ == '\0')
275         return TRUE;
276     }
277
278   if (*time_ == ':')
279     time_++;
280
281   if (*time_ < '0' || '5' < *time_)
282     return FALSE;
283
284   *offset += 10 * 60 * (*time_++ - '0');
285
286   if (*time_ < '0' || '9' < *time_)
287     return FALSE;
288
289   *offset += 60 * (*time_++ - '0');
290
291   if (*time_ == '\0')
292     return TRUE;
293
294   if (*time_ == ':')
295     time_++;
296
297   if (*time_ < '0' || '5' < *time_)
298     return FALSE;
299
300   *offset += 10 * (*time_++ - '0');
301
302   if (*time_ < '0' || '9' < *time_)
303     return FALSE;
304
305   *offset += *time_++ - '0';
306
307   return *time_ == '\0';
308 }
309
310 static gboolean
311 parse_constant_offset (const gchar *name,
312                        gint32      *offset)
313 {
314   if (g_strcmp0 (name, "UTC") == 0)
315     {
316       *offset = 0;
317       return TRUE;
318     }
319
320   if (*name >= '0' && '9' >= *name)
321     return parse_time (name, offset);
322
323   switch (*name++)
324     {
325     case 'Z':
326       *offset = 0;
327       return !*name;
328
329     case '+':
330       return parse_time (name, offset);
331
332     case '-':
333       if (parse_time (name, offset))
334         {
335           *offset = -*offset;
336           return TRUE;
337         }
338
339     default:
340       return FALSE;
341     }
342 }
343
344 static void
345 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
346 {
347   gint32 offset;
348   TransitionInfo info;
349
350   if (name == NULL || !parse_constant_offset (name, &offset))
351     return;
352
353   info.gmt_offset = offset;
354   info.is_dst = FALSE;
355   info.is_standard = TRUE;
356   info.is_gmt = TRUE;
357   info.abbrev =  g_strdup (name);
358
359
360   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
361   g_array_append_val (gtz->t_info, info);
362
363   /* Constant offset, no transitions */
364   gtz->transitions = NULL;
365 }
366
367 #ifdef G_OS_UNIX
368 static GBytes*
369 zone_info_unix (const gchar *identifier)
370 {
371   gchar *filename;
372   GMappedFile *file = NULL;
373   GBytes *zoneinfo = NULL;
374
375   /* identifier can be a relative or absolute path name;
376      if relative, it is interpreted starting from /usr/share/zoneinfo
377      while the POSIX standard says it should start with :,
378      glibc allows both syntaxes, so we should too */
379   if (identifier != NULL)
380     {
381       const gchar *tzdir;
382
383       tzdir = getenv ("TZDIR");
384       if (tzdir == NULL)
385         tzdir = "/usr/share/zoneinfo";
386
387       if (*identifier == ':')
388         identifier ++;
389
390       if (g_path_is_absolute (identifier))
391         filename = g_strdup (identifier);
392       else
393         filename = g_build_filename (tzdir, identifier, NULL);
394     }
395   else
396     filename = g_strdup ("/etc/localtime");
397
398   file = g_mapped_file_new (filename, FALSE, NULL);
399   if (file != NULL)
400     {
401       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
402                                              g_mapped_file_get_length (file),
403                                              (GDestroyNotify)g_mapped_file_unref,
404                                              g_mapped_file_ref (file));
405       g_mapped_file_unref (file);
406     }
407   g_free (filename);
408   return zoneinfo;
409 }
410
411 static void
412 init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
413 {
414   gsize size;
415   guint index;
416   guint32 time_count, type_count, leap_count, isgmt_count;
417   guint32  isstd_count, char_count ;
418   gpointer tz_transitions, tz_type_index, tz_ttinfo;
419   gpointer  tz_leaps, tz_isgmt, tz_isstd;
420   gchar* tz_abbrs;
421   guint timesize = sizeof (gint32), countsize = sizeof (gint32);
422   const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
423
424   g_return_if_fail (size >= sizeof (struct tzhead) &&
425                     memcmp (header, "TZif", 4) == 0);
426
427   if (header->tzh_version == '2')
428       {
429         /* Skip ahead to the newer 64-bit data if it's available. */
430         header = (const struct tzhead *)
431           (((const gchar *) (header + 1)) +
432            guint32_from_be(header->tzh_ttisgmtcnt) +
433            guint32_from_be(header->tzh_ttisstdcnt) +
434            8 * guint32_from_be(header->tzh_leapcnt) +
435            5 * guint32_from_be(header->tzh_timecnt) +
436            6 * guint32_from_be(header->tzh_typecnt) +
437            guint32_from_be(header->tzh_charcnt));
438         timesize = sizeof (gint64);
439       }
440   time_count = guint32_from_be(header->tzh_timecnt);
441   type_count = guint32_from_be(header->tzh_typecnt);
442   leap_count = guint32_from_be(header->tzh_leapcnt);
443   isgmt_count = guint32_from_be(header->tzh_ttisgmtcnt);
444   isstd_count = guint32_from_be(header->tzh_ttisstdcnt);
445   char_count = guint32_from_be(header->tzh_charcnt);
446
447   g_assert (type_count == isgmt_count);
448   g_assert (type_count == isstd_count);
449
450   tz_transitions = (gpointer)(header + 1);
451   tz_type_index = tz_transitions + timesize * time_count;
452   tz_ttinfo = tz_type_index + time_count;
453   tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
454   tz_leaps = tz_abbrs + char_count;
455   tz_isstd = tz_leaps + (timesize + countsize) * leap_count;
456   tz_isgmt = tz_isstd + isstd_count;
457
458   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
459                                    type_count);
460   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
461                                         time_count);
462
463   for (index = 0; index < type_count; index++)
464     {
465       TransitionInfo t_info;
466       struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
467       t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
468       t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
469       t_info.is_standard = ((guint8*)tz_isstd)[index] ? TRUE : FALSE;
470       t_info.is_gmt = ((guint8*)tz_isgmt)[index] ? TRUE : FALSE;
471       t_info.abbrev = g_strdup (&tz_abbrs[info.tt_abbrind]);
472       g_array_append_val (gtz->t_info, t_info);
473     }
474
475   for (index = 0; index < time_count; index++)
476     {
477       Transition trans;
478       if (header->tzh_version == '2')
479         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
480       else
481         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
482       trans.info_index = ((guint8*)tz_type_index)[index];
483       g_assert (trans.info_index >= 0);
484       g_assert (trans.info_index < gtz->t_info->len);
485       g_array_append_val (gtz->transitions, trans);
486     }
487   g_bytes_unref (zoneinfo);
488 }
489
490 #endif
491
492 static void
493 find_relative_date (TimeZoneDate *buffer,
494                     GTimeZone    *tz)
495 {
496   GDateTime *dt;
497   gint wday;
498
499   wday = buffer->wday;
500
501   /* Get last day if last is needed, first day otherwise */
502   dt = g_date_time_new (tz,
503                         buffer->year,
504                         buffer->mon + (buffer->week < 5? 0 : 1),
505                         buffer->week < 5? 1 : 0,
506                         buffer->hour, buffer->min, buffer->sec);
507
508   buffer->wday = g_date_time_get_day_of_week (dt);
509   buffer->mday = g_date_time_get_day_of_month (dt);
510
511   if (buffer->week < 5)
512     {
513       if (wday < buffer->wday)
514         buffer->wday -= 7;
515
516       buffer->mday += (buffer->week - 1) * 7;
517     }
518
519   else if (wday > buffer->wday)
520     buffer->wday += 7;
521
522   buffer->mday += wday - buffer->wday;
523   buffer->wday = wday;
524
525   g_date_time_unref (dt);
526 }
527
528 /* Offset is previous offset of local time */
529 static gint64
530 boundary_for_year (TimeZoneDate *boundary,
531                    gint          year,
532                    gint32        prev_offset,
533                    gint32        std_offset)
534 {
535   TimeZoneDate buffer;
536   GDateTime *dt;
537   GTimeZone *tz;
538   gint64 t;
539   gint32 offset;
540   gchar *identifier;
541
542   buffer = *boundary;
543
544   if (boundary->isgmt)
545     offset = 0;
546   else if (boundary->isstd)
547     offset = std_offset;
548   else
549     offset = prev_offset;
550
551   G_UNLOCK (time_zones);
552
553   identifier = g_strdup_printf ("%+03d:%02d:%02d",
554                                 (int) offset / 3600,
555                                 (int) abs (offset / 60) % 60,
556                                 (int) abs (offset) % 3600);
557   tz = g_time_zone_new (identifier);
558   g_free (identifier);
559
560   if (boundary->year == 0)
561     {
562       buffer.year = year;
563
564       if (buffer.wday)
565         find_relative_date (&buffer, tz);
566     }
567
568   g_assert (buffer.year == year);
569
570   dt = g_date_time_new (tz,
571                         buffer.year, buffer.mon, buffer.mday,
572                         buffer.hour, buffer.min, buffer.sec);
573   t = g_date_time_to_unix (dt);
574   g_date_time_unref (dt);
575
576   g_time_zone_unref (tz);
577
578   G_LOCK (time_zones);
579
580   return t;
581 }
582
583 static void
584 init_zone_from_rules (GTimeZone    *gtz,
585                       TimeZoneRule *rules,
586                       gint          rules_num)
587 {
588   TransitionInfo info[2];
589   Transition trans;
590   gint type_count, trans_count;
591   gint year, i, x, y;
592   gint32 last_offset;
593
594   type_count = 0;
595   trans_count = 0;
596
597   /* Last rule only contains max year */
598   for (i = 0; i < rules_num - 1; i++)
599     {
600       if (rules[i].dlt_start.mon)
601         {
602           type_count += 2;
603           trans_count += 2 * (rules[i+1].start_year - rules[i].start_year);
604         }
605       else
606         type_count++;
607     }
608
609   x = 0;
610   y = 0;
611
612   /* If standard time happens before daylight time in first rule
613    * with daylight, skip first transition so the minimum is in
614    * standard time and the first transition is in daylight time */
615   for (i = 0; i < rules_num - 1 && rules[0].dlt_start.mon == 0; i++);
616
617   if (i < rules_num -1 && rules[i].dlt_start.mon > 0 &&
618       rules[i].dlt_start.mon > rules[i].dlt_end.mon)
619     {
620       trans_count--;
621       x = -1;
622     }
623
624   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
625   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
626
627   last_offset = rules[0].std_offset;
628
629   for (i = 0; i < rules_num - 1; i++)
630     {
631       if (rules[i].dlt_start.mon)
632         {
633           /* Standard */
634           info[0].gmt_offset = rules[i].std_offset;
635           info[0].is_dst = FALSE;
636           info[0].is_standard = rules[i].dlt_end.isstd;
637           info[0].is_gmt = rules[i].dlt_end.isgmt;
638
639           if (rules[i].std_name)
640             info[0].abbrev = g_strdup (rules[i].std_name);
641
642           else
643             info[0].abbrev = g_strdup_printf ("%+03d%02d",
644                                               (int) rules[i].std_offset / 3600,
645                                               (int) abs (rules[i].std_offset / 60) % 60);
646
647
648           /* Daylight */
649           info[1].gmt_offset = rules[i].dlt_offset;
650           info[1].is_dst = TRUE;
651           info[1].is_standard = rules[i].dlt_start.isstd;
652           info[1].is_gmt = rules[i].dlt_start.isgmt;
653
654           if (rules[i].dlt_name)
655             info[1].abbrev = g_strdup (rules[i].dlt_name);
656
657           else
658             info[1].abbrev = g_strdup_printf ("%+03d%02d",
659                                               (int) rules[i].dlt_offset / 3600,
660                                               (int) abs (rules[i].dlt_offset / 60) % 60);
661
662           if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
663             {
664               g_array_append_val (gtz->t_info, info[1]);
665               g_array_append_val (gtz->t_info, info[0]);
666             }
667           else
668             {
669               g_array_append_val (gtz->t_info, info[0]);
670               g_array_append_val (gtz->t_info, info[1]);
671             }
672
673           /* Transition dates */
674           for (year = rules[i].start_year; year < rules[i+1].start_year; year++)
675             {
676               if (rules[i].dlt_start.mon < rules[i].dlt_end.mon)
677                 {
678                   /* Daylight Data */
679                   trans.info_index = y;
680                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
681                                                   last_offset, rules[i].std_offset);
682                   g_array_insert_val (gtz->transitions, x++, trans);
683                   last_offset = rules[i].dlt_offset;
684
685                   /* Standard Data */
686                   trans.info_index = y+1;
687                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
688                                                   last_offset, rules[i].std_offset);
689                   g_array_insert_val (gtz->transitions, x++, trans);
690                   last_offset = rules[i].std_offset;
691                 }
692               else
693                 {
694                   /* Standard Data */
695                   trans.info_index = y;
696                   trans.time = boundary_for_year (&rules[i].dlt_end, year,
697                                                   last_offset, rules[i].std_offset);
698                   if (x >= 0)
699                     g_array_insert_val (gtz->transitions, x++, trans);
700                   else
701                     x++;
702                   last_offset = rules[i].std_offset;
703
704                   /* Daylight Data */
705                   trans.info_index = y+1;
706                   trans.time = boundary_for_year (&rules[i].dlt_start, year,
707                                                   last_offset, rules[i].std_offset);
708                   g_array_insert_val (gtz->transitions, x++, trans);
709                   last_offset = rules[i].dlt_offset;
710                 }
711             }
712
713           y += 2;
714         }
715       else
716         {
717           /* Standard */
718           info[0].gmt_offset = rules[i].std_offset;
719           info[0].is_dst = FALSE;
720           info[0].is_standard = FALSE;
721           info[0].is_gmt = FALSE;
722
723           if (rules[i].std_name)
724             info[0].abbrev = g_strdup (rules[i].std_name);
725
726           else
727             info[0].abbrev = g_strdup_printf ("%+03d%02d",
728                                               (int) rules[i].std_offset / 3600,
729                                               (int) abs (rules[i].std_offset / 60) % 60);
730
731           g_array_append_val (gtz->t_info, info[0]);
732
733           last_offset = rules[i].std_offset;
734
735           y++;
736         }
737     }
738 }
739
740 /* Construction {{{1 */
741 /**
742  * g_time_zone_new:
743  * @identifier: (allow-none): a timezone identifier
744  *
745  * Creates a #GTimeZone corresponding to @identifier.
746  *
747  * @identifier can either be an RFC3339/ISO 8601 time offset or
748  * something that would pass as a valid value for the
749  * <varname>TZ</varname> environment variable (including %NULL).
750  *
751  * Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
752  * <literal>"±hh:mm"</literal>.  ISO 8601 additionally specifies
753  * <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.
754  *
755  * The <varname>TZ</varname> environment variable typically corresponds
756  * to the name of a file in the zoneinfo database, but there are many
757  * other possibilities.  Note that those other possibilities are not
758  * currently implemented, but are planned.
759  *
760  * g_time_zone_new_local() calls this function with the value of the
761  * <varname>TZ</varname> environment variable.  This function itself is
762  * independent of the value of <varname>TZ</varname>, but if @identifier
763  * is %NULL then <filename>/etc/localtime</filename> will be consulted
764  * to discover the correct timezone.
765  *
766  * See <ulink
767  * url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
768  * §5.6</ulink> for a precise definition of valid RFC3339 time offsets
769  * (the <varname>time-offset</varname> expansion) and ISO 8601 for the
770  * full list of valid time offsets.  See <ulink
771  * url='http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html'>The
772  * GNU C Library manual</ulink> for an explanation of the possible
773  * values of the <varname>TZ</varname> environment variable.
774  *
775  * You should release the return value by calling g_time_zone_unref()
776  * when you are done with it.
777  *
778  * Returns: the requested timezone
779  *
780  * Since: 2.26
781  **/
782 GTimeZone *
783 g_time_zone_new (const gchar *identifier)
784 {
785   GTimeZone *tz = NULL;
786
787   G_LOCK (time_zones);
788   if (time_zones == NULL)
789     time_zones = g_hash_table_new (g_str_hash, g_str_equal);
790
791   if (identifier)
792     {
793       tz = g_hash_table_lookup (time_zones, identifier);
794       if (tz)
795         {
796           g_atomic_int_inc (&tz->ref_count);
797           G_UNLOCK (time_zones);
798           return tz;
799         }
800     }
801
802   tz = g_slice_new0 (GTimeZone);
803   tz->name = g_strdup (identifier);
804   tz->ref_count = 0;
805
806   zone_for_constant_offset (tz, identifier);
807
808   if (tz->t_info == NULL)
809     {
810 #ifdef G_OS_UNIX
811       GBytes *zoneinfo = zone_info_unix (identifier);
812       if (!zoneinfo)
813         zone_for_constant_offset (tz, "UTC");
814       else
815         {
816           init_zone_from_iana_info (tz, zoneinfo);
817           g_bytes_unref (zoneinfo);
818         }
819 #elif defined G_OS_WIN32
820 #endif
821     }
822
823   if (tz->t_info != NULL)
824     {
825       if (identifier)
826         g_hash_table_insert (time_zones, tz->name, tz);
827     }
828   g_atomic_int_inc (&tz->ref_count);
829   G_UNLOCK (time_zones);
830
831   return tz;
832 }
833
834 /**
835  * g_time_zone_new_utc:
836  *
837  * Creates a #GTimeZone corresponding to UTC.
838  *
839  * This is equivalent to calling g_time_zone_new() with a value like
840  * "Z", "UTC", "+00", etc.
841  *
842  * You should release the return value by calling g_time_zone_unref()
843  * when you are done with it.
844  *
845  * Returns: the universal timezone
846  *
847  * Since: 2.26
848  **/
849 GTimeZone *
850 g_time_zone_new_utc (void)
851 {
852   return g_time_zone_new ("UTC");
853 }
854
855 /**
856  * g_time_zone_new_local:
857  *
858  * Creates a #GTimeZone corresponding to local time.  The local time
859  * zone may change between invocations to this function; for example,
860  * if the system administrator changes it.
861  *
862  * This is equivalent to calling g_time_zone_new() with the value of the
863  * <varname>TZ</varname> environment variable (including the possibility
864  * of %NULL).
865  *
866  * You should release the return value by calling g_time_zone_unref()
867  * when you are done with it.
868  *
869  * Returns: the local timezone
870  *
871  * Since: 2.26
872  **/
873 GTimeZone *
874 g_time_zone_new_local (void)
875 {
876   return g_time_zone_new (getenv ("TZ"));
877 }
878
879 #define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
880 #define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
881
882 /* Internal helpers {{{1 */
883 /* Note that interval 0 is *before* the first transition time, so
884  * interval 1 gets transitions[0].
885  */
886 inline static const TransitionInfo*
887 interval_info (GTimeZone *tz,
888                guint      interval)
889 {
890   guint index;
891   g_return_val_if_fail (tz->t_info != NULL, NULL);
892   if (interval && tz->transitions && interval <= tz->transitions->len)
893     index = (TRANSITION(interval - 1)).info_index;
894   else
895     index = 0;
896   return &(TRANSITION_INFO(index));
897 }
898
899 inline static gint64
900 interval_start (GTimeZone *tz,
901                 guint      interval)
902 {
903   if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
904     return G_MININT64;
905   if (interval > tz->transitions->len)
906     interval = tz->transitions->len;
907   return (TRANSITION(interval - 1)).time;
908 }
909
910 inline static gint64
911 interval_end (GTimeZone *tz,
912               guint      interval)
913 {
914   if (tz->transitions && interval < tz->transitions->len)
915     return (TRANSITION(interval)).time - 1;
916   return G_MAXINT64;
917 }
918
919 inline static gint32
920 interval_offset (GTimeZone *tz,
921                  guint      interval)
922 {
923   g_return_val_if_fail (tz->t_info != NULL, 0);
924   return interval_info (tz, interval)->gmt_offset;
925 }
926
927 inline static gboolean
928 interval_isdst (GTimeZone *tz,
929                 guint      interval)
930 {
931   g_return_val_if_fail (tz->t_info != NULL, 0);
932   return interval_info (tz, interval)->is_dst;
933 }
934
935
936 inline static gboolean
937 interval_isgmt (GTimeZone *tz,
938                 guint      interval)
939 {
940   g_return_val_if_fail (tz->t_info != NULL, 0);
941   return interval_info (tz, interval)->is_gmt;
942 }
943
944 inline static gboolean
945 interval_isstandard (GTimeZone *tz,
946                 guint      interval)
947 {
948   return interval_info (tz, interval)->is_standard;
949 }
950
951 inline static gchar*
952 interval_abbrev (GTimeZone *tz,
953                   guint      interval)
954 {
955   g_return_val_if_fail (tz->t_info != NULL, 0);
956   return interval_info (tz, interval)->abbrev;
957 }
958
959 inline static gint64
960 interval_local_start (GTimeZone *tz,
961                       guint      interval)
962 {
963   if (interval)
964     return interval_start (tz, interval) + interval_offset (tz, interval);
965
966   return G_MININT64;
967 }
968
969 inline static gint64
970 interval_local_end (GTimeZone *tz,
971                     guint      interval)
972 {
973   if (tz->transitions && interval < tz->transitions->len)
974     return interval_end (tz, interval) + interval_offset (tz, interval);
975
976   return G_MAXINT64;
977 }
978
979 static gboolean
980 interval_valid (GTimeZone *tz,
981                 guint      interval)
982 {
983   if ( tz->transitions == NULL)
984     return interval == 0;
985   return interval <= tz->transitions->len;
986 }
987
988 /* g_time_zone_find_interval() {{{1 */
989
990 /**
991  * g_time_zone_adjust_time:
992  * @tz: a #GTimeZone
993  * @type: the #GTimeType of @time_
994  * @time_: a pointer to a number of seconds since January 1, 1970
995  *
996  * Finds an interval within @tz that corresponds to the given @time_,
997  * possibly adjusting @time_ if required to fit into an interval.
998  * The meaning of @time_ depends on @type.
999  *
1000  * This function is similar to g_time_zone_find_interval(), with the
1001  * difference that it always succeeds (by making the adjustments
1002  * described below).
1003  *
1004  * In any of the cases where g_time_zone_find_interval() succeeds then
1005  * this function returns the same value, without modifying @time_.
1006  *
1007  * This function may, however, modify @time_ in order to deal with
1008  * non-existent times.  If the non-existent local @time_ of 02:30 were
1009  * requested on March 14th 2010 in Toronto then this function would
1010  * adjust @time_ to be 03:00 and return the interval containing the
1011  * adjusted time.
1012  *
1013  * Returns: the interval containing @time_, never -1
1014  *
1015  * Since: 2.26
1016  **/
1017 gint
1018 g_time_zone_adjust_time (GTimeZone *tz,
1019                          GTimeType  type,
1020                          gint64    *time_)
1021 {
1022   gint i;
1023   guint intervals;
1024
1025   if (tz->transitions == NULL)
1026     return 0;
1027
1028   intervals = tz->transitions->len;
1029
1030   /* find the interval containing *time UTC
1031    * TODO: this could be binary searched (or better) */
1032   for (i = 0; i <= intervals; i++)
1033     if (*time_ <= interval_end (tz, i))
1034       break;
1035
1036   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
1037
1038   if (type != G_TIME_TYPE_UNIVERSAL)
1039     {
1040       if (*time_ < interval_local_start (tz, i))
1041         /* if time came before the start of this interval... */
1042         {
1043           i--;
1044
1045           /* if it's not in the previous interval... */
1046           if (*time_ > interval_local_end (tz, i))
1047             {
1048               /* it doesn't exist.  fast-forward it. */
1049               i++;
1050               *time_ = interval_local_start (tz, i);
1051             }
1052         }
1053
1054       else if (*time_ > interval_local_end (tz, i))
1055         /* if time came after the end of this interval... */
1056         {
1057           i++;
1058
1059           /* if it's not in the next interval... */
1060           if (*time_ < interval_local_start (tz, i))
1061             /* it doesn't exist.  fast-forward it. */
1062             *time_ = interval_local_start (tz, i);
1063         }
1064
1065       else if (interval_isdst (tz, i) != type)
1066         /* it's in this interval, but dst flag doesn't match.
1067          * check neighbours for a better fit. */
1068         {
1069           if (i && *time_ <= interval_local_end (tz, i - 1))
1070             i--;
1071
1072           else if (i < intervals &&
1073                    *time_ >= interval_local_start (tz, i + 1))
1074             i++;
1075         }
1076     }
1077
1078   return i;
1079 }
1080
1081 /**
1082  * g_time_zone_find_interval:
1083  * @tz: a #GTimeZone
1084  * @type: the #GTimeType of @time_
1085  * @time_: a number of seconds since January 1, 1970
1086  *
1087  * Finds an the interval within @tz that corresponds to the given @time_.
1088  * The meaning of @time_ depends on @type.
1089  *
1090  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
1091  * succeed (since universal time is monotonic and continuous).
1092  *
1093  * Otherwise @time_ is treated is local time.  The distinction between
1094  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
1095  * the case that the given @time_ is ambiguous.  In Toronto, for example,
1096  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
1097  * savings time and the next, an hour later, outside of daylight savings
1098  * time).  In this case, the different value of @type would result in a
1099  * different interval being returned.
1100  *
1101  * It is still possible for this function to fail.  In Toronto, for
1102  * example, 02:00 on March 14th 2010 does not exist (due to the leap
1103  * forward to begin daylight savings time).  -1 is returned in that
1104  * case.
1105  *
1106  * Returns: the interval containing @time_, or -1 in case of failure
1107  *
1108  * Since: 2.26
1109  */
1110 gint
1111 g_time_zone_find_interval (GTimeZone *tz,
1112                            GTimeType  type,
1113                            gint64     time_)
1114 {
1115   gint i;
1116   guint intervals;
1117
1118   if (tz->transitions == NULL)
1119     return 0;
1120   intervals = tz->transitions->len;
1121   for (i = 0; i <= intervals; i++)
1122     if (time_ <= interval_end (tz, i))
1123       break;
1124
1125   if (type == G_TIME_TYPE_UNIVERSAL)
1126     return i;
1127
1128   if (time_ < interval_local_start (tz, i))
1129     {
1130       if (time_ > interval_local_end (tz, --i))
1131         return -1;
1132     }
1133
1134   else if (time_ > interval_local_end (tz, i))
1135     {
1136       if (time_ < interval_local_start (tz, ++i))
1137         return -1;
1138     }
1139
1140   else if (interval_isdst (tz, i) != type)
1141     {
1142       if (i && time_ <= interval_local_end (tz, i - 1))
1143         i--;
1144
1145       else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
1146         i++;
1147     }
1148
1149   return i;
1150 }
1151
1152 /* Public API accessors {{{1 */
1153
1154 /**
1155  * g_time_zone_get_abbreviation:
1156  * @tz: a #GTimeZone
1157  * @interval: an interval within the timezone
1158  *
1159  * Determines the time zone abbreviation to be used during a particular
1160  * @interval of time in the time zone @tz.
1161  *
1162  * For example, in Toronto this is currently "EST" during the winter
1163  * months and "EDT" during the summer months when daylight savings time
1164  * is in effect.
1165  *
1166  * Returns: the time zone abbreviation, which belongs to @tz
1167  *
1168  * Since: 2.26
1169  **/
1170 const gchar *
1171 g_time_zone_get_abbreviation (GTimeZone *tz,
1172                               gint       interval)
1173 {
1174   g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
1175
1176   return interval_abbrev (tz, (guint)interval);
1177 }
1178
1179 /**
1180  * g_time_zone_get_offset:
1181  * @tz: a #GTimeZone
1182  * @interval: an interval within the timezone
1183  *
1184  * Determines the offset to UTC in effect during a particular @interval
1185  * of time in the time zone @tz.
1186  *
1187  * The offset is the number of seconds that you add to UTC time to
1188  * arrive at local time for @tz (ie: negative numbers for time zones
1189  * west of GMT, positive numbers for east).
1190  *
1191  * Returns: the number of seconds that should be added to UTC to get the
1192  *          local time in @tz
1193  *
1194  * Since: 2.26
1195  **/
1196 gint32
1197 g_time_zone_get_offset (GTimeZone *tz,
1198                         gint       interval)
1199 {
1200   g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
1201
1202   return interval_offset (tz, (guint)interval);
1203 }
1204
1205 /**
1206  * g_time_zone_is_dst:
1207  * @tz: a #GTimeZone
1208  * @interval: an interval within the timezone
1209  *
1210  * Determines if daylight savings time is in effect during a particular
1211  * @interval of time in the time zone @tz.
1212  *
1213  * Returns: %TRUE if daylight savings time is in effect
1214  *
1215  * Since: 2.26
1216  **/
1217 gboolean
1218 g_time_zone_is_dst (GTimeZone *tz,
1219                     gint       interval)
1220 {
1221   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
1222
1223   if (tz->transitions == NULL)
1224     return FALSE;
1225
1226   return interval_isdst (tz, (guint)interval);
1227 }
1228
1229 /* Epilogue {{{1 */
1230 /* vim:set foldmethod=marker: */