• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.1 of the License, 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, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19 
20 /* Prologue {{{1 */
21 
22 #include "config.h"
23 
24 #include "gtimezone.h"
25 
26 #include <string.h>
27 #include <stdlib.h>
28 #include <signal.h>
29 
30 #include "gmappedfile.h"
31 #include "gtestutils.h"
32 #include "gfileutils.h"
33 #include "gstrfuncs.h"
34 #include "ghash.h"
35 #include "gthread.h"
36 #include "gbytes.h"
37 #include "gslice.h"
38 #include "gdatetime.h"
39 #include "gdate.h"
40 #include "genviron.h"
41 
42 #ifdef G_OS_WIN32
43 
44 #define STRICT
45 #include <windows.h>
46 #include <wchar.h>
47 #endif
48 
49 /**
50  * SECTION:timezone
51  * @title: GTimeZone
52  * @short_description: a structure representing a time zone
53  * @see_also: #GDateTime
54  *
55  * #GTimeZone is a structure that represents a time zone, at no
56  * particular point in time.  It is refcounted and immutable.
57  *
58  * Each time zone has an identifier (for example, ‘Europe/London’) which is
59  * platform dependent. See g_time_zone_new() for information on the identifier
60  * formats. The identifier of a time zone can be retrieved using
61  * g_time_zone_get_identifier().
62  *
63  * A time zone contains a number of intervals.  Each interval has
64  * an abbreviation to describe it (for example, ‘PDT’), an offset to UTC and a
65  * flag indicating if the daylight savings time is in effect during that
66  * interval.  A time zone always has at least one interval — interval 0. Note
67  * that interval abbreviations are not the same as time zone identifiers
68  * (apart from ‘UTC’), and cannot be passed to g_time_zone_new().
69  *
70  * Every UTC time is contained within exactly one interval, but a given
71  * local time may be contained within zero, one or two intervals (due to
72  * incontinuities associated with daylight savings time).
73  *
74  * An interval may refer to a specific period of time (eg: the duration
75  * of daylight savings time during 2010) or it may refer to many periods
76  * of time that share the same properties (eg: all periods of daylight
77  * savings time).  It is also possible (usually for political reasons)
78  * that some properties (like the abbreviation) change between intervals
79  * without other properties changing.
80  *
81  * #GTimeZone is available since GLib 2.26.
82  */
83 
84 /**
85  * GTimeZone:
86  *
87  * #GTimeZone is an opaque structure whose members cannot be accessed
88  * directly.
89  *
90  * Since: 2.26
91  **/
92 
93 /* IANA zoneinfo file format {{{1 */
94 
95 /* unaligned */
96 typedef struct { gchar bytes[8]; } gint64_be;
97 typedef struct { gchar bytes[4]; } gint32_be;
98 typedef struct { gchar bytes[4]; } guint32_be;
99 
gint64_from_be(const gint64_be be)100 static inline gint64 gint64_from_be (const gint64_be be) {
101   gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
102 }
103 
gint32_from_be(const gint32_be be)104 static inline gint32 gint32_from_be (const gint32_be be) {
105   gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
106 }
107 
guint32_from_be(const guint32_be be)108 static inline guint32 guint32_from_be (const guint32_be be) {
109   guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
110 }
111 
112 /* The layout of an IANA timezone file header */
113 struct tzhead
114 {
115   gchar      tzh_magic[4];
116   gchar      tzh_version;
117   guchar     tzh_reserved[15];
118 
119   guint32_be tzh_ttisgmtcnt;
120   guint32_be tzh_ttisstdcnt;
121   guint32_be tzh_leapcnt;
122   guint32_be tzh_timecnt;
123   guint32_be tzh_typecnt;
124   guint32_be tzh_charcnt;
125 };
126 
127 struct ttinfo
128 {
129   gint32_be tt_gmtoff;
130   guint8    tt_isdst;
131   guint8    tt_abbrind;
132 };
133 
134 /* A Transition Date structure for TZ Rules, an intermediate structure
135    for parsing MSWindows and Environment-variable time zones. It
136    Generalizes MSWindows's SYSTEMTIME struct.
137  */
138 typedef struct
139 {
140   gint     year;
141   gint     mon;
142   gint     mday;
143   gint     wday;
144   gint     week;
145   gint32   offset;  /* hour*3600 + min*60 + sec; can be negative.  */
146 } TimeZoneDate;
147 
148 /* POSIX Timezone abbreviations are typically 3 or 4 characters, but
149    Microsoft uses 32-character names. We'll use one larger to ensure
150    we have room for the terminating \0.
151  */
152 #define NAME_SIZE 33
153 
154 /* A MSWindows-style time zone transition rule. Generalizes the
155    MSWindows TIME_ZONE_INFORMATION struct. Also used to compose time
156    zones from tzset-style identifiers.
157  */
158 typedef struct
159 {
160   gint         start_year;
161   gint32       std_offset;
162   gint32       dlt_offset;
163   TimeZoneDate dlt_start;
164   TimeZoneDate dlt_end;
165   gchar std_name[NAME_SIZE];
166   gchar dlt_name[NAME_SIZE];
167 } TimeZoneRule;
168 
169 /* GTimeZone's internal representation of a Daylight Savings (Summer)
170    time interval.
171  */
172 typedef struct
173 {
174   gint32     gmt_offset;
175   gboolean   is_dst;
176   gchar     *abbrev;
177 } TransitionInfo;
178 
179 /* GTimeZone's representation of a transition time to or from Daylight
180    Savings (Summer) time and Standard time for the zone. */
181 typedef struct
182 {
183   gint64 time;
184   gint   info_index;
185 } Transition;
186 
187 /* GTimeZone structure */
188 struct _GTimeZone
189 {
190   gchar   *name;
191   GArray  *t_info;         /* Array of TransitionInfo */
192   GArray  *transitions;    /* Array of Transition */
193   gint     ref_count;
194 };
195 
196 G_LOCK_DEFINE_STATIC (time_zones);
197 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
198 G_LOCK_DEFINE_STATIC (tz_default);
199 static GTimeZone *tz_default = NULL;
200 G_LOCK_DEFINE_STATIC (tz_local);
201 static GTimeZone *tz_local = NULL;
202 
203 #define MIN_TZYEAR 1916 /* Daylight Savings started in WWI */
204 #define MAX_TZYEAR 2999 /* And it's not likely ever to go away, but
205                            there's no point in getting carried
206                            away. */
207 
208 #ifdef G_OS_UNIX
209 static GTimeZone *parse_footertz (const gchar *, size_t);
210 #endif
211 
212 /**
213  * g_time_zone_unref:
214  * @tz: a #GTimeZone
215  *
216  * Decreases the reference count on @tz.
217  *
218  * Since: 2.26
219  **/
220 void
g_time_zone_unref(GTimeZone * tz)221 g_time_zone_unref (GTimeZone *tz)
222 {
223   int ref_count;
224 
225 again:
226   ref_count = g_atomic_int_get (&tz->ref_count);
227 
228   g_assert (ref_count > 0);
229 
230   if (ref_count == 1)
231     {
232       if (tz->name != NULL)
233         {
234           G_LOCK(time_zones);
235 
236           /* someone else might have grabbed a ref in the meantime */
237           if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
238             {
239               G_UNLOCK(time_zones);
240               goto again;
241             }
242 
243           if (time_zones != NULL)
244             g_hash_table_remove (time_zones, tz->name);
245           G_UNLOCK(time_zones);
246         }
247 
248       if (tz->t_info != NULL)
249         {
250           guint idx;
251           for (idx = 0; idx < tz->t_info->len; idx++)
252             {
253               TransitionInfo *info = &g_array_index (tz->t_info, TransitionInfo, idx);
254               g_free (info->abbrev);
255             }
256           g_array_free (tz->t_info, TRUE);
257         }
258       if (tz->transitions != NULL)
259         g_array_free (tz->transitions, TRUE);
260       g_free (tz->name);
261 
262       g_slice_free (GTimeZone, tz);
263     }
264 
265   else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
266                                                           ref_count,
267                                                           ref_count - 1))
268     goto again;
269 }
270 
271 /**
272  * g_time_zone_ref:
273  * @tz: a #GTimeZone
274  *
275  * Increases the reference count on @tz.
276  *
277  * Returns: a new reference to @tz.
278  *
279  * Since: 2.26
280  **/
281 GTimeZone *
g_time_zone_ref(GTimeZone * tz)282 g_time_zone_ref (GTimeZone *tz)
283 {
284   g_assert (tz->ref_count > 0);
285 
286   g_atomic_int_inc (&tz->ref_count);
287 
288   return tz;
289 }
290 
291 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
292 /*
293  * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
294  *  - h[h] is 0 to 24
295  *  - mm is 00 to 59
296  *  - ss is 00 to 59
297  * If RFC8536, TIME_ is a transition time sans sign,
298  * so colons are required before mm and ss, and hh can be up to 167.
299  * See Internet RFC 8536 section 3.3.1:
300  * https://tools.ietf.org/html/rfc8536#section-3.3.1
301  * and POSIX Base Definitions 8.3 TZ rule time:
302  * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
303  */
304 static gboolean
parse_time(const gchar * time_,gint32 * offset,gboolean rfc8536)305 parse_time (const gchar *time_,
306             gint32      *offset,
307             gboolean    rfc8536)
308 {
309   if (*time_ < '0' || '9' < *time_)
310     return FALSE;
311 
312   *offset = 60 * 60 * (*time_++ - '0');
313 
314   if (*time_ == '\0')
315     return TRUE;
316 
317   if (*time_ != ':')
318     {
319       if (*time_ < '0' || '9' < *time_)
320         return FALSE;
321 
322       *offset *= 10;
323       *offset += 60 * 60 * (*time_++ - '0');
324 
325       if (rfc8536)
326         {
327           /* Internet RFC 8536 section 3.3.1 and POSIX 8.3 TZ together say
328              that a transition time must be of the form [+-]hh[:mm[:ss]] where
329              the hours part can range from -167 to 167.  */
330           if ('0' <= *time_ && *time_ <= '9')
331             {
332               *offset *= 10;
333               *offset += 60 * 60 * (*time_++ - '0');
334             }
335           if (*offset > 167 * 60 * 60)
336             return FALSE;
337         }
338       else if (*offset > 24 * 60 * 60)
339         return FALSE;
340 
341       if (*time_ == '\0')
342         return TRUE;
343     }
344 
345   if (*time_ == ':')
346     time_++;
347   else if (rfc8536)
348     return FALSE;
349 
350   if (*time_ < '0' || '5' < *time_)
351     return FALSE;
352 
353   *offset += 10 * 60 * (*time_++ - '0');
354 
355   if (*time_ < '0' || '9' < *time_)
356     return FALSE;
357 
358   *offset += 60 * (*time_++ - '0');
359 
360   if (*time_ == '\0')
361     return TRUE;
362 
363   if (*time_ == ':')
364     time_++;
365   else if (rfc8536)
366     return FALSE;
367 
368   if (*time_ < '0' || '5' < *time_)
369     return FALSE;
370 
371   *offset += 10 * (*time_++ - '0');
372 
373   if (*time_ < '0' || '9' < *time_)
374     return FALSE;
375 
376   *offset += *time_++ - '0';
377 
378   return *time_ == '\0';
379 }
380 
381 static gboolean
parse_constant_offset(const gchar * name,gint32 * offset,gboolean rfc8536)382 parse_constant_offset (const gchar *name,
383                        gint32      *offset,
384                        gboolean    rfc8536)
385 {
386   /* Internet RFC 8536 section 3.3.1 and POSIX 8.3 TZ together say
387      that a transition time must be numeric.  */
388   if (!rfc8536 && g_strcmp0 (name, "UTC") == 0)
389     {
390       *offset = 0;
391       return TRUE;
392     }
393 
394   if (*name >= '0' && '9' >= *name)
395     return parse_time (name, offset, rfc8536);
396 
397   switch (*name++)
398     {
399     case 'Z':
400       *offset = 0;
401       /* Internet RFC 8536 section 3.3.1 requires a numeric zone.  */
402       return !rfc8536 && !*name;
403 
404     case '+':
405       return parse_time (name, offset, rfc8536);
406 
407     case '-':
408       if (parse_time (name, offset, rfc8536))
409         {
410           *offset = -*offset;
411           return TRUE;
412         }
413       else
414         return FALSE;
415 
416     default:
417       return FALSE;
418     }
419 }
420 
421 static void
zone_for_constant_offset(GTimeZone * gtz,const gchar * name)422 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
423 {
424   gint32 offset;
425   TransitionInfo info;
426 
427   if (name == NULL || !parse_constant_offset (name, &offset, FALSE))
428     return;
429 
430   info.gmt_offset = offset;
431   info.is_dst = FALSE;
432   info.abbrev =  g_strdup (name);
433 
434   gtz->name = g_strdup (name);
435   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
436   g_array_append_val (gtz->t_info, info);
437 
438   /* Constant offset, no transitions */
439   gtz->transitions = NULL;
440 }
441 
442 #ifdef G_OS_UNIX
443 static gchar *
zone_identifier_unix(void)444 zone_identifier_unix (void)
445 {
446   gchar *resolved_identifier = NULL;
447   gsize prefix_len = 0;
448   gchar *canonical_path = NULL;
449   GError *read_link_err = NULL;
450   const gchar *tzdir;
451 
452   /* Resolve the actual timezone pointed to by /etc/localtime. */
453   resolved_identifier = g_file_read_link ("/etc/localtime", &read_link_err);
454   if (resolved_identifier == NULL)
455     {
456       gboolean not_a_symlink = g_error_matches (read_link_err,
457                                                 G_FILE_ERROR,
458                                                 G_FILE_ERROR_INVAL);
459       g_clear_error (&read_link_err);
460 
461       /* Fallback to the content of /var/db/zoneinfo or /etc/timezone
462        * if /etc/localtime is not a symlink. /var/db/zoneinfo is
463        * where 'tzsetup' program on FreeBSD and DragonflyBSD stores
464        * the timezone chosen by the user. /etc/timezone is where user
465        * choice is expressed on Gentoo OpenRC and others. */
466       if (not_a_symlink && (g_file_get_contents ("/var/db/zoneinfo",
467                                                  &resolved_identifier,
468                                                  NULL, NULL) ||
469                             g_file_get_contents ("/etc/timezone",
470                                                  &resolved_identifier,
471                                                  NULL, NULL)))
472         g_strchomp (resolved_identifier);
473       else
474         {
475           /* Error */
476           g_assert (resolved_identifier == NULL);
477           goto out;
478         }
479     }
480   else
481     {
482       /* Resolve relative path */
483       canonical_path = g_canonicalize_filename (resolved_identifier, "/etc");
484       g_free (resolved_identifier);
485       resolved_identifier = g_steal_pointer (&canonical_path);
486     }
487 
488   tzdir = g_getenv ("TZDIR");
489   if (tzdir == NULL)
490     tzdir = "/usr/share/zoneinfo";
491 
492   /* Strip the prefix and slashes if possible. */
493   if (g_str_has_prefix (resolved_identifier, tzdir))
494     {
495       prefix_len = strlen (tzdir);
496       while (*(resolved_identifier + prefix_len) == '/')
497         prefix_len++;
498     }
499 
500   if (prefix_len > 0)
501     memmove (resolved_identifier, resolved_identifier + prefix_len,
502              strlen (resolved_identifier) - prefix_len + 1  /* nul terminator */);
503 
504   g_assert (resolved_identifier != NULL);
505 
506 out:
507   g_free (canonical_path);
508 
509   return resolved_identifier;
510 }
511 
512 static GBytes*
zone_info_unix(const gchar * identifier,const gchar * resolved_identifier)513 zone_info_unix (const gchar *identifier,
514                 const gchar *resolved_identifier)
515 {
516   gchar *filename = NULL;
517   GMappedFile *file = NULL;
518   GBytes *zoneinfo = NULL;
519   const gchar *tzdir;
520 
521   tzdir = g_getenv ("TZDIR");
522   if (tzdir == NULL)
523     tzdir = "/usr/share/zoneinfo";
524 
525   /* identifier can be a relative or absolute path name;
526      if relative, it is interpreted starting from /usr/share/zoneinfo
527      while the POSIX standard says it should start with :,
528      glibc allows both syntaxes, so we should too */
529   if (identifier != NULL)
530     {
531       if (*identifier == ':')
532         identifier ++;
533 
534       if (g_path_is_absolute (identifier))
535         filename = g_strdup (identifier);
536       else
537         filename = g_build_filename (tzdir, identifier, NULL);
538     }
539   else
540     {
541       if (resolved_identifier == NULL)
542         goto out;
543 
544       filename = g_strdup ("/etc/localtime");
545     }
546 
547   file = g_mapped_file_new (filename, FALSE, NULL);
548   if (file != NULL)
549     {
550       zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
551                                              g_mapped_file_get_length (file),
552                                              (GDestroyNotify)g_mapped_file_unref,
553                                              g_mapped_file_ref (file));
554       g_mapped_file_unref (file);
555     }
556 
557   g_assert (resolved_identifier != NULL);
558 
559 out:
560   g_free (filename);
561 
562   return zoneinfo;
563 }
564 
565 static void
init_zone_from_iana_info(GTimeZone * gtz,GBytes * zoneinfo,gchar * identifier)566 init_zone_from_iana_info (GTimeZone *gtz,
567                           GBytes    *zoneinfo,
568                           gchar     *identifier  /* (transfer full) */)
569 {
570   gsize size;
571   guint index;
572   guint32 time_count, type_count;
573   guint8 *tz_transitions, *tz_type_index, *tz_ttinfo;
574   guint8 *tz_abbrs;
575   gsize timesize = sizeof (gint32);
576   gconstpointer header_data = g_bytes_get_data (zoneinfo, &size);
577   const gchar *data = header_data;
578   const struct tzhead *header = header_data;
579   GTimeZone *footertz = NULL;
580   guint extra_time_count = 0, extra_type_count = 0;
581   gint64 last_explicit_transition_time;
582 
583   g_return_if_fail (size >= sizeof (struct tzhead) &&
584                     memcmp (header, "TZif", 4) == 0);
585 
586   /* FIXME: Handle invalid TZif files better (Issue#1088).  */
587 
588   if (header->tzh_version >= '2')
589       {
590         /* Skip ahead to the newer 64-bit data if it's available. */
591         header = (const struct tzhead *)
592           (((const gchar *) (header + 1)) +
593            guint32_from_be(header->tzh_ttisgmtcnt) +
594            guint32_from_be(header->tzh_ttisstdcnt) +
595            8 * guint32_from_be(header->tzh_leapcnt) +
596            5 * guint32_from_be(header->tzh_timecnt) +
597            6 * guint32_from_be(header->tzh_typecnt) +
598            guint32_from_be(header->tzh_charcnt));
599         timesize = sizeof (gint64);
600       }
601   time_count = guint32_from_be(header->tzh_timecnt);
602   type_count = guint32_from_be(header->tzh_typecnt);
603 
604   if (header->tzh_version >= '2')
605     {
606       const gchar *footer = (((const gchar *) (header + 1))
607                              + guint32_from_be(header->tzh_ttisgmtcnt)
608                              + guint32_from_be(header->tzh_ttisstdcnt)
609                              + 12 * guint32_from_be(header->tzh_leapcnt)
610                              + 9 * time_count
611                              + 6 * type_count
612                              + guint32_from_be(header->tzh_charcnt));
613       const gchar *footerlast;
614       size_t footerlen;
615       g_return_if_fail (footer <= data + size - 2 && footer[0] == '\n');
616       footerlast = memchr (footer + 1, '\n', data + size - (footer + 1));
617       g_return_if_fail (footerlast);
618       footerlen = footerlast + 1 - footer;
619       if (footerlen != 2)
620         {
621           footertz = parse_footertz (footer, footerlen);
622           g_return_if_fail (footertz);
623           extra_type_count = footertz->t_info->len;
624           extra_time_count = footertz->transitions->len;
625         }
626     }
627 
628   tz_transitions = ((guint8 *) (header) + sizeof (*header));
629   tz_type_index = tz_transitions + timesize * time_count;
630   tz_ttinfo = tz_type_index + time_count;
631   tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
632 
633   gtz->name = g_steal_pointer (&identifier);
634   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
635                                    type_count + extra_type_count);
636   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
637                                         time_count + extra_time_count);
638 
639   for (index = 0; index < type_count; index++)
640     {
641       TransitionInfo t_info;
642       struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
643       t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
644       t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
645       t_info.abbrev = g_strdup ((gchar *) &tz_abbrs[info.tt_abbrind]);
646       g_array_append_val (gtz->t_info, t_info);
647     }
648 
649   for (index = 0; index < time_count; index++)
650     {
651       Transition trans;
652       if (header->tzh_version >= '2')
653         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
654       else
655         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
656       last_explicit_transition_time = trans.time;
657       trans.info_index = tz_type_index[index];
658       g_assert (trans.info_index >= 0);
659       g_assert ((guint) trans.info_index < gtz->t_info->len);
660       g_array_append_val (gtz->transitions, trans);
661     }
662 
663   if (footertz)
664     {
665       /* Append footer time types.  Don't bother to coalesce
666          duplicates with existing time types.  */
667       for (index = 0; index < extra_type_count; index++)
668         {
669           TransitionInfo t_info;
670           TransitionInfo *footer_t_info
671             = &g_array_index (footertz->t_info, TransitionInfo, index);
672           t_info.gmt_offset = footer_t_info->gmt_offset;
673           t_info.is_dst = footer_t_info->is_dst;
674           t_info.abbrev = g_steal_pointer (&footer_t_info->abbrev);
675           g_array_append_val (gtz->t_info, t_info);
676         }
677 
678       /* Append footer transitions that follow the last explicit
679          transition.  */
680       for (index = 0; index < extra_time_count; index++)
681         {
682           Transition *footer_transition
683             = &g_array_index (footertz->transitions, Transition, index);
684           if (time_count <= 0
685               || last_explicit_transition_time < footer_transition->time)
686             {
687               Transition trans;
688               trans.time = footer_transition->time;
689               trans.info_index = type_count + footer_transition->info_index;
690               g_array_append_val (gtz->transitions, trans);
691             }
692         }
693 
694       g_time_zone_unref (footertz);
695     }
696 }
697 
698 #elif defined (G_OS_WIN32)
699 
700 static void
copy_windows_systemtime(SYSTEMTIME * s_time,TimeZoneDate * tzdate)701 copy_windows_systemtime (SYSTEMTIME *s_time, TimeZoneDate *tzdate)
702 {
703   tzdate->offset
704     = s_time->wHour * 3600 + s_time->wMinute * 60 + s_time->wSecond;
705   tzdate->mon = s_time->wMonth;
706   tzdate->year = s_time->wYear;
707   tzdate->wday = s_time->wDayOfWeek ? s_time->wDayOfWeek : 7;
708 
709   if (s_time->wYear)
710     {
711       tzdate->mday = s_time->wDay;
712       tzdate->wday = 0;
713     }
714   else
715     tzdate->week = s_time->wDay;
716 }
717 
718 /* UTC = local time + bias while local time = UTC + offset */
719 static gboolean
rule_from_windows_time_zone_info(TimeZoneRule * rule,TIME_ZONE_INFORMATION * tzi)720 rule_from_windows_time_zone_info (TimeZoneRule *rule,
721                                   TIME_ZONE_INFORMATION *tzi)
722 {
723   gchar *std_name, *dlt_name;
724 
725   std_name = g_utf16_to_utf8 ((gunichar2 *)tzi->StandardName, -1, NULL, NULL, NULL);
726   if (std_name == NULL)
727     return FALSE;
728 
729   dlt_name = g_utf16_to_utf8 ((gunichar2 *)tzi->DaylightName, -1, NULL, NULL, NULL);
730   if (dlt_name == NULL)
731     {
732       g_free (std_name);
733       return FALSE;
734     }
735 
736   /* Set offset */
737   if (tzi->StandardDate.wMonth)
738     {
739       rule->std_offset = -(tzi->Bias + tzi->StandardBias) * 60;
740       rule->dlt_offset = -(tzi->Bias + tzi->DaylightBias) * 60;
741       copy_windows_systemtime (&(tzi->DaylightDate), &(rule->dlt_start));
742 
743       copy_windows_systemtime (&(tzi->StandardDate), &(rule->dlt_end));
744     }
745 
746   else
747     {
748       rule->std_offset = -tzi->Bias * 60;
749       rule->dlt_start.mon = 0;
750     }
751   strncpy (rule->std_name, std_name, NAME_SIZE - 1);
752   strncpy (rule->dlt_name, dlt_name, NAME_SIZE - 1);
753 
754   g_free (std_name);
755   g_free (dlt_name);
756 
757   return TRUE;
758 }
759 
760 static gchar*
windows_default_tzname(void)761 windows_default_tzname (void)
762 {
763   const gunichar2 *subkey =
764     L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
765   HKEY key;
766   gchar *key_name = NULL;
767   gunichar2 *key_name_w = NULL;
768   if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey, 0,
769                      KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
770     {
771       DWORD size = 0;
772       if (RegQueryValueExW (key, L"TimeZoneKeyName", NULL, NULL,
773                             NULL, &size) == ERROR_SUCCESS)
774         {
775           key_name_w = g_malloc ((gint)size);
776 
777           if (key_name_w == NULL ||
778               RegQueryValueExW (key, L"TimeZoneKeyName", NULL, NULL,
779                                 (LPBYTE)key_name_w, &size) != ERROR_SUCCESS)
780             {
781               g_free (key_name_w);
782               key_name = NULL;
783             }
784           else
785             key_name = g_utf16_to_utf8 (key_name_w, -1, NULL, NULL, NULL);
786         }
787       RegCloseKey (key);
788     }
789   return key_name;
790 }
791 
792 typedef   struct
793 {
794   LONG Bias;
795   LONG StandardBias;
796   LONG DaylightBias;
797   SYSTEMTIME StandardDate;
798   SYSTEMTIME DaylightDate;
799 } RegTZI;
800 
801 static void
system_time_copy(SYSTEMTIME * orig,SYSTEMTIME * target)802 system_time_copy (SYSTEMTIME *orig, SYSTEMTIME *target)
803 {
804   g_return_if_fail (orig != NULL);
805   g_return_if_fail (target != NULL);
806 
807   target->wYear = orig->wYear;
808   target->wMonth = orig->wMonth;
809   target->wDayOfWeek = orig->wDayOfWeek;
810   target->wDay = orig->wDay;
811   target->wHour = orig->wHour;
812   target->wMinute = orig->wMinute;
813   target->wSecond = orig->wSecond;
814   target->wMilliseconds = orig->wMilliseconds;
815 }
816 
817 static void
register_tzi_to_tzi(RegTZI * reg,TIME_ZONE_INFORMATION * tzi)818 register_tzi_to_tzi (RegTZI *reg, TIME_ZONE_INFORMATION *tzi)
819 {
820   g_return_if_fail (reg != NULL);
821   g_return_if_fail (tzi != NULL);
822   tzi->Bias = reg->Bias;
823   system_time_copy (&(reg->StandardDate), &(tzi->StandardDate));
824   tzi->StandardBias = reg->StandardBias;
825   system_time_copy (&(reg->DaylightDate), &(tzi->DaylightDate));
826   tzi->DaylightBias = reg->DaylightBias;
827 }
828 
829 static guint
rules_from_windows_time_zone(const gchar * identifier,const gchar * resolved_identifier,TimeZoneRule ** rules)830 rules_from_windows_time_zone (const gchar   *identifier,
831                               const gchar   *resolved_identifier,
832                               TimeZoneRule **rules)
833 {
834   HKEY key;
835   gchar *subkey = NULL;
836   gchar *subkey_dynamic = NULL;
837   const gchar *key_name;
838   const gchar *reg_key =
839     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
840   TIME_ZONE_INFORMATION tzi;
841   DWORD size;
842   guint rules_num = 0;
843   RegTZI regtzi, regtzi_prev;
844   WCHAR winsyspath[MAX_PATH];
845   gunichar2 *subkey_w, *subkey_dynamic_w;
846 
847   subkey_dynamic_w = NULL;
848 
849   if (GetSystemDirectoryW (winsyspath, MAX_PATH) == 0)
850     return 0;
851 
852   g_assert (rules != NULL);
853 
854   *rules = NULL;
855   key_name = NULL;
856 
857   if (!identifier)
858     key_name = resolved_identifier;
859   else
860     key_name = identifier;
861 
862   if (!key_name)
863     return 0;
864 
865   subkey = g_strconcat (reg_key, key_name, NULL);
866   subkey_w = g_utf8_to_utf16 (subkey, -1, NULL, NULL, NULL);
867   if (subkey_w == NULL)
868     goto utf16_conv_failed;
869 
870   subkey_dynamic = g_strconcat (subkey, "\\Dynamic DST", NULL);
871   subkey_dynamic_w = g_utf8_to_utf16 (subkey_dynamic, -1, NULL, NULL, NULL);
872   if (subkey_dynamic_w == NULL)
873     goto utf16_conv_failed;
874 
875   if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_w, 0,
876                      KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
877       goto utf16_conv_failed;
878 
879   size = sizeof tzi.StandardName;
880 
881   /* use RegLoadMUIStringW() to query MUI_Std from the registry if possible, otherwise
882      fallback to querying Std */
883   if (RegLoadMUIStringW (key, L"MUI_Std", tzi.StandardName,
884                          size, &size, 0, winsyspath) != ERROR_SUCCESS)
885     {
886       size = sizeof tzi.StandardName;
887       if (RegQueryValueExW (key, L"Std", NULL, NULL,
888                             (LPBYTE)&(tzi.StandardName), &size) != ERROR_SUCCESS)
889         goto registry_failed;
890     }
891 
892   size = sizeof tzi.DaylightName;
893 
894   /* use RegLoadMUIStringW() to query MUI_Dlt from the registry if possible, otherwise
895      fallback to querying Dlt */
896   if (RegLoadMUIStringW (key, L"MUI_Dlt", tzi.DaylightName,
897                          size, &size, 0, winsyspath) != ERROR_SUCCESS)
898     {
899       size = sizeof tzi.DaylightName;
900       if (RegQueryValueExW (key, L"Dlt", NULL, NULL,
901                             (LPBYTE)&(tzi.DaylightName), &size) != ERROR_SUCCESS)
902         goto registry_failed;
903     }
904 
905   RegCloseKey (key);
906   if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_dynamic_w, 0,
907                      KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
908     {
909       DWORD first, last;
910       int year, i;
911       wchar_t s[12];
912 
913       size = sizeof first;
914       if (RegQueryValueExW (key, L"FirstEntry", NULL, NULL,
915                             (LPBYTE) &first, &size) != ERROR_SUCCESS)
916         goto registry_failed;
917 
918       size = sizeof last;
919       if (RegQueryValueExW (key, L"LastEntry", NULL, NULL,
920                             (LPBYTE) &last, &size) != ERROR_SUCCESS)
921         goto registry_failed;
922 
923       rules_num = last - first + 2;
924       *rules = g_new0 (TimeZoneRule, rules_num);
925 
926       for (year = first, i = 0; *rules != NULL && year <= last; year++)
927         {
928           gboolean failed = FALSE;
929           swprintf_s (s, 11, L"%d", year);
930 
931           if (!failed)
932             {
933               size = sizeof regtzi;
934               if (RegQueryValueExW (key, s, NULL, NULL,
935                                     (LPBYTE) &regtzi, &size) != ERROR_SUCCESS)
936                 failed = TRUE;
937             }
938 
939           if (failed)
940             {
941               g_free (*rules);
942               *rules = NULL;
943               break;
944             }
945 
946           if (year > first && memcmp (&regtzi_prev, &regtzi, sizeof regtzi) == 0)
947               continue;
948           else
949             memcpy (&regtzi_prev, &regtzi, sizeof regtzi);
950 
951           register_tzi_to_tzi (&regtzi, &tzi);
952 
953           if (!rule_from_windows_time_zone_info (&(*rules)[i], &tzi))
954             {
955               g_free (*rules);
956               *rules = NULL;
957               break;
958             }
959 
960           (*rules)[i++].start_year = year;
961         }
962 
963       rules_num = i + 1;
964 
965 registry_failed:
966       RegCloseKey (key);
967     }
968   else if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, subkey_w, 0,
969                           KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
970     {
971       size = sizeof regtzi;
972       if (RegQueryValueExW (key, L"TZI", NULL, NULL,
973                             (LPBYTE) &regtzi, &size) == ERROR_SUCCESS)
974         {
975           rules_num = 2;
976           *rules = g_new0 (TimeZoneRule, 2);
977           register_tzi_to_tzi (&regtzi, &tzi);
978 
979           if (!rule_from_windows_time_zone_info (&(*rules)[0], &tzi))
980             {
981               g_free (*rules);
982               *rules = NULL;
983             }
984         }
985 
986       RegCloseKey (key);
987     }
988 
989 utf16_conv_failed:
990   g_free (subkey_dynamic_w);
991   g_free (subkey_dynamic);
992   g_free (subkey_w);
993   g_free (subkey);
994 
995   if (*rules)
996     {
997       (*rules)[0].start_year = MIN_TZYEAR;
998       if ((*rules)[rules_num - 2].start_year < MAX_TZYEAR)
999         (*rules)[rules_num - 1].start_year = MAX_TZYEAR;
1000       else
1001         (*rules)[rules_num - 1].start_year = (*rules)[rules_num - 2].start_year + 1;
1002 
1003       return rules_num;
1004     }
1005 
1006   return 0;
1007 }
1008 
1009 #endif
1010 
1011 static void
find_relative_date(TimeZoneDate * buffer)1012 find_relative_date (TimeZoneDate *buffer)
1013 {
1014   guint wday;
1015   GDate date;
1016   g_date_clear (&date, 1);
1017   wday = buffer->wday;
1018 
1019   /* Get last day if last is needed, first day otherwise */
1020   if (buffer->mon == 13 || buffer->mon == 14) /* Julian Date */
1021     {
1022       g_date_set_dmy (&date, 1, 1, buffer->year);
1023       if (wday >= 59 && buffer->mon == 13 && g_date_is_leap_year (buffer->year))
1024         g_date_add_days (&date, wday);
1025       else
1026         g_date_add_days (&date, wday - 1);
1027       buffer->mon = (int) g_date_get_month (&date);
1028       buffer->mday = (int) g_date_get_day (&date);
1029       buffer->wday = 0;
1030     }
1031   else /* M.W.D */
1032     {
1033       guint days;
1034       guint days_in_month = g_date_get_days_in_month (buffer->mon, buffer->year);
1035       GDateWeekday first_wday;
1036 
1037       g_date_set_dmy (&date, 1, buffer->mon, buffer->year);
1038       first_wday = g_date_get_weekday (&date);
1039 
1040       if (first_wday > wday)
1041         ++(buffer->week);
1042       /* week is 1 <= w <= 5, we need 0-based */
1043       days = 7 * (buffer->week - 1) + wday - first_wday;
1044 
1045       /* "days" is a 0-based offset from the 1st of the month.
1046        * Adding days == days_in_month would bring us into the next month,
1047        * hence the ">=" instead of just ">".
1048        */
1049       while (days >= days_in_month)
1050         days -= 7;
1051 
1052       g_date_add_days (&date, days);
1053 
1054       buffer->mday = g_date_get_day (&date);
1055     }
1056 }
1057 
1058 /* Offset is previous offset of local time. Returns 0 if month is 0 */
1059 static gint64
boundary_for_year(TimeZoneDate * boundary,gint year,gint32 offset)1060 boundary_for_year (TimeZoneDate *boundary,
1061                    gint          year,
1062                    gint32        offset)
1063 {
1064   TimeZoneDate buffer;
1065   GDate date;
1066   const guint64 unix_epoch_start = 719163L;
1067   const guint64 seconds_per_day = 86400L;
1068 
1069   if (!boundary->mon)
1070     return 0;
1071   buffer = *boundary;
1072 
1073   if (boundary->year == 0)
1074     {
1075       buffer.year = year;
1076 
1077       if (buffer.wday)
1078         find_relative_date (&buffer);
1079     }
1080 
1081   g_assert (buffer.year == year);
1082   g_date_clear (&date, 1);
1083   g_date_set_dmy (&date, buffer.mday, buffer.mon, buffer.year);
1084   return ((g_date_get_julian (&date) - unix_epoch_start) * seconds_per_day +
1085           buffer.offset - offset);
1086 }
1087 
1088 static void
fill_transition_info_from_rule(TransitionInfo * info,TimeZoneRule * rule,gboolean is_dst)1089 fill_transition_info_from_rule (TransitionInfo *info,
1090                                 TimeZoneRule   *rule,
1091                                 gboolean        is_dst)
1092 {
1093   gint offset = is_dst ? rule->dlt_offset : rule->std_offset;
1094   gchar *name = is_dst ? rule->dlt_name : rule->std_name;
1095 
1096   info->gmt_offset = offset;
1097   info->is_dst = is_dst;
1098 
1099   if (name)
1100     info->abbrev = g_strdup (name);
1101 
1102   else
1103     info->abbrev = g_strdup_printf ("%+03d%02d",
1104                                       (int) offset / 3600,
1105                                       (int) abs (offset / 60) % 60);
1106 }
1107 
1108 static void
init_zone_from_rules(GTimeZone * gtz,TimeZoneRule * rules,guint rules_num,gchar * identifier)1109 init_zone_from_rules (GTimeZone    *gtz,
1110                       TimeZoneRule *rules,
1111                       guint         rules_num,
1112                       gchar        *identifier  /* (transfer full) */)
1113 {
1114   guint type_count = 0, trans_count = 0, info_index = 0;
1115   guint ri; /* rule index */
1116   gboolean skip_first_std_trans = TRUE;
1117   gint32 last_offset;
1118 
1119   type_count = 0;
1120   trans_count = 0;
1121 
1122   /* Last rule only contains max year */
1123   for (ri = 0; ri < rules_num - 1; ri++)
1124     {
1125       if (rules[ri].dlt_start.mon || rules[ri].dlt_end.mon)
1126         {
1127           guint rulespan = (rules[ri + 1].start_year - rules[ri].start_year);
1128           guint transitions = rules[ri].dlt_start.mon > 0 ? 1 : 0;
1129           transitions += rules[ri].dlt_end.mon > 0 ? 1 : 0;
1130           type_count += rules[ri].dlt_start.mon > 0 ? 2 : 1;
1131           trans_count += transitions * rulespan;
1132         }
1133       else
1134         type_count++;
1135     }
1136 
1137   gtz->name = g_steal_pointer (&identifier);
1138   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
1139   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
1140 
1141   last_offset = rules[0].std_offset;
1142 
1143   for (ri = 0; ri < rules_num - 1; ri++)
1144     {
1145       if ((rules[ri].std_offset || rules[ri].dlt_offset) &&
1146           rules[ri].dlt_start.mon == 0 && rules[ri].dlt_end.mon == 0)
1147         {
1148           TransitionInfo std_info;
1149           /* Standard */
1150           fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
1151           g_array_append_val (gtz->t_info, std_info);
1152 
1153           if (ri > 0 &&
1154               ((rules[ri - 1].dlt_start.mon > 12 &&
1155                 rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
1156                 rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
1157             {
1158               /* The previous rule was a southern hemisphere rule that
1159                  starts the year with DST, so we need to add a
1160                  transition to return to standard time */
1161               guint year = rules[ri].start_year;
1162               gint64 std_time =  boundary_for_year (&rules[ri].dlt_end,
1163                                                     year, last_offset);
1164               Transition std_trans = {std_time, info_index};
1165               g_array_append_val (gtz->transitions, std_trans);
1166 
1167             }
1168           last_offset = rules[ri].std_offset;
1169           ++info_index;
1170           skip_first_std_trans = TRUE;
1171          }
1172       else
1173         {
1174           const guint start_year = rules[ri].start_year;
1175           const guint end_year = rules[ri + 1].start_year;
1176           gboolean dlt_first;
1177           guint year;
1178           TransitionInfo std_info, dlt_info;
1179           if (rules[ri].dlt_start.mon > 12)
1180             dlt_first = rules[ri].dlt_start.wday > rules[ri].dlt_end.wday;
1181           else
1182             dlt_first = rules[ri].dlt_start.mon > rules[ri].dlt_end.mon;
1183           /* Standard rules are always even, because before the first
1184              transition is always standard time, and 0 is even. */
1185           fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
1186           fill_transition_info_from_rule (&dlt_info, &(rules[ri]), TRUE);
1187 
1188           g_array_append_val (gtz->t_info, std_info);
1189           g_array_append_val (gtz->t_info, dlt_info);
1190 
1191           /* Transition dates. We hope that a year which ends daylight
1192              time in a southern-hemisphere country (i.e., one that
1193              begins the year in daylight time) will include a rule
1194              which has only a dlt_end. */
1195           for (year = start_year; year < end_year; year++)
1196             {
1197               gint32 dlt_offset = (dlt_first ? last_offset :
1198                                    rules[ri].dlt_offset);
1199               gint32 std_offset = (dlt_first ? rules[ri].std_offset :
1200                                    last_offset);
1201               /* NB: boundary_for_year returns 0 if mon == 0 */
1202               gint64 std_time =  boundary_for_year (&rules[ri].dlt_end,
1203                                                     year, dlt_offset);
1204               gint64 dlt_time = boundary_for_year (&rules[ri].dlt_start,
1205                                                    year, std_offset);
1206               Transition std_trans = {std_time, info_index};
1207               Transition dlt_trans = {dlt_time, info_index + 1};
1208               last_offset = (dlt_first ? rules[ri].dlt_offset :
1209                              rules[ri].std_offset);
1210               if (dlt_first)
1211                 {
1212                   if (skip_first_std_trans)
1213                     skip_first_std_trans = FALSE;
1214                   else if (std_time)
1215                     g_array_append_val (gtz->transitions, std_trans);
1216                   if (dlt_time)
1217                     g_array_append_val (gtz->transitions, dlt_trans);
1218                 }
1219               else
1220                 {
1221                   if (dlt_time)
1222                     g_array_append_val (gtz->transitions, dlt_trans);
1223                   if (std_time)
1224                     g_array_append_val (gtz->transitions, std_trans);
1225                 }
1226             }
1227 
1228           info_index += 2;
1229         }
1230     }
1231   if (ri > 0 &&
1232       ((rules[ri - 1].dlt_start.mon > 12 &&
1233         rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
1234        rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
1235     {
1236       /* The previous rule was a southern hemisphere rule that
1237          starts the year with DST, so we need to add a
1238          transition to return to standard time */
1239       TransitionInfo info;
1240       guint year = rules[ri].start_year;
1241       Transition trans;
1242       fill_transition_info_from_rule (&info, &(rules[ri - 1]), FALSE);
1243       g_array_append_val (gtz->t_info, info);
1244       trans.time = boundary_for_year (&rules[ri - 1].dlt_end,
1245                                       year, last_offset);
1246       trans.info_index = info_index;
1247       g_array_append_val (gtz->transitions, trans);
1248      }
1249 }
1250 
1251 /*
1252  * parses date[/time] for parsing TZ environment variable
1253  *
1254  * date is either Mm.w.d, Jn or N
1255  * - m is 1 to 12
1256  * - w is 1 to 5
1257  * - d is 0 to 6
1258  * - n is 1 to 365
1259  * - N is 0 to 365
1260  *
1261  * time is either h or hh[[:]mm[[[:]ss]]]
1262  *  - h[h] is 0 to 24
1263  *  - mm is 00 to 59
1264  *  - ss is 00 to 59
1265  */
1266 static gboolean
parse_mwd_boundary(gchar ** pos,TimeZoneDate * boundary)1267 parse_mwd_boundary (gchar **pos, TimeZoneDate *boundary)
1268 {
1269   gint month, week, day;
1270 
1271   if (**pos == '\0' || **pos < '0' || '9' < **pos)
1272     return FALSE;
1273 
1274   month = *(*pos)++ - '0';
1275 
1276   if ((month == 1 && **pos >= '0' && '2' >= **pos) ||
1277       (month == 0 && **pos >= '0' && '9' >= **pos))
1278     {
1279       month *= 10;
1280       month += *(*pos)++ - '0';
1281     }
1282 
1283   if (*(*pos)++ != '.' || month == 0)
1284     return FALSE;
1285 
1286   if (**pos == '\0' || **pos < '1' || '5' < **pos)
1287     return FALSE;
1288 
1289   week = *(*pos)++ - '0';
1290 
1291   if (*(*pos)++ != '.')
1292     return FALSE;
1293 
1294   if (**pos == '\0' || **pos < '0' || '6' < **pos)
1295     return FALSE;
1296 
1297   day = *(*pos)++ - '0';
1298 
1299   if (!day)
1300     day += 7;
1301 
1302   boundary->year = 0;
1303   boundary->mon = month;
1304   boundary->week = week;
1305   boundary->wday = day;
1306   return TRUE;
1307 }
1308 
1309 /*
1310  * This parses two slightly different ways of specifying
1311  * the Julian day:
1312  *
1313  * - ignore_leap == TRUE
1314  *
1315  *   Jn   This specifies the Julian day with n between 1 and 365. Leap days
1316  *        are not counted. In this format, February 29 can't be represented;
1317  *        February 28 is day 59, and March 1 is always day 60.
1318  *
1319  * - ignore_leap == FALSE
1320  *
1321  *   n   This specifies the zero-based Julian day with n between 0 and 365.
1322  *       February 29 is counted in leap years.
1323  */
1324 static gboolean
parse_julian_boundary(gchar ** pos,TimeZoneDate * boundary,gboolean ignore_leap)1325 parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
1326                        gboolean ignore_leap)
1327 {
1328   gint day = 0;
1329   GDate date;
1330 
1331   while (**pos >= '0' && '9' >= **pos)
1332     {
1333       day *= 10;
1334       day += *(*pos)++ - '0';
1335     }
1336 
1337   if (ignore_leap)
1338     {
1339       if (day < 1 || 365 < day)
1340         return FALSE;
1341       if (day >= 59)
1342         day++;
1343     }
1344   else
1345     {
1346       if (day < 0 || 365 < day)
1347         return FALSE;
1348       /* GDate wants day in range 1->366 */
1349       day++;
1350     }
1351 
1352   g_date_clear (&date, 1);
1353   g_date_set_julian (&date, day);
1354   boundary->year = 0;
1355   boundary->mon = (int) g_date_get_month (&date);
1356   boundary->mday = (int) g_date_get_day (&date);
1357   boundary->wday = 0;
1358 
1359   return TRUE;
1360 }
1361 
1362 static gboolean
parse_tz_boundary(const gchar * identifier,TimeZoneDate * boundary)1363 parse_tz_boundary (const gchar  *identifier,
1364                    TimeZoneDate *boundary)
1365 {
1366   gchar *pos;
1367 
1368   pos = (gchar*)identifier;
1369   /* Month-week-weekday */
1370   if (*pos == 'M')
1371     {
1372       ++pos;
1373       if (!parse_mwd_boundary (&pos, boundary))
1374         return FALSE;
1375     }
1376   /* Julian date which ignores Feb 29 in leap years */
1377   else if (*pos == 'J')
1378     {
1379       ++pos;
1380       if (!parse_julian_boundary (&pos, boundary, TRUE))
1381         return FALSE ;
1382     }
1383   /* Julian date which counts Feb 29 in leap years */
1384   else if (*pos >= '0' && '9' >= *pos)
1385     {
1386       if (!parse_julian_boundary (&pos, boundary, FALSE))
1387         return FALSE;
1388     }
1389   else
1390     return FALSE;
1391 
1392   /* Time */
1393 
1394   if (*pos == '/')
1395     return parse_constant_offset (pos + 1, &boundary->offset, TRUE);
1396   else
1397     {
1398       boundary->offset = 2 * 60 * 60;
1399       return *pos == '\0';
1400     }
1401 }
1402 
1403 static guint
create_ruleset_from_rule(TimeZoneRule ** rules,TimeZoneRule * rule)1404 create_ruleset_from_rule (TimeZoneRule **rules, TimeZoneRule *rule)
1405 {
1406   *rules = g_new0 (TimeZoneRule, 2);
1407 
1408   (*rules)[0].start_year = MIN_TZYEAR;
1409   (*rules)[1].start_year = MAX_TZYEAR;
1410 
1411   (*rules)[0].std_offset = -rule->std_offset;
1412   (*rules)[0].dlt_offset = -rule->dlt_offset;
1413   (*rules)[0].dlt_start  = rule->dlt_start;
1414   (*rules)[0].dlt_end = rule->dlt_end;
1415   strcpy ((*rules)[0].std_name, rule->std_name);
1416   strcpy ((*rules)[0].dlt_name, rule->dlt_name);
1417   return 2;
1418 }
1419 
1420 static gboolean
parse_offset(gchar ** pos,gint32 * target)1421 parse_offset (gchar **pos, gint32 *target)
1422 {
1423   gchar *buffer;
1424   gchar *target_pos = *pos;
1425   gboolean ret;
1426 
1427   while (**pos == '+' || **pos == '-' || **pos == ':' ||
1428          (**pos >= '0' && '9' >= **pos))
1429     ++(*pos);
1430 
1431   buffer = g_strndup (target_pos, *pos - target_pos);
1432   ret = parse_constant_offset (buffer, target, FALSE);
1433   g_free (buffer);
1434 
1435   return ret;
1436 }
1437 
1438 static gboolean
parse_identifier_boundary(gchar ** pos,TimeZoneDate * target)1439 parse_identifier_boundary (gchar **pos, TimeZoneDate *target)
1440 {
1441   gchar *buffer;
1442   gchar *target_pos = *pos;
1443   gboolean ret;
1444 
1445   while (**pos != ',' && **pos != '\0')
1446     ++(*pos);
1447   buffer = g_strndup (target_pos, *pos - target_pos);
1448   ret = parse_tz_boundary (buffer, target);
1449   g_free (buffer);
1450 
1451   return ret;
1452 }
1453 
1454 static gboolean
set_tz_name(gchar ** pos,gchar * buffer,guint size)1455 set_tz_name (gchar **pos, gchar *buffer, guint size)
1456 {
1457   gboolean quoted = **pos == '<';
1458   gchar *name_pos = *pos;
1459   guint len;
1460 
1461   if (quoted)
1462     {
1463       name_pos++;
1464       do
1465         ++(*pos);
1466       while (g_ascii_isalnum (**pos) || **pos == '-' || **pos == '+');
1467       if (**pos != '>')
1468         return FALSE;
1469     }
1470   else
1471     while (g_ascii_isalpha (**pos))
1472       ++(*pos);
1473 
1474   /* Name should be three or more characters */
1475   /* FIXME: Should return FALSE if the name is too long.
1476      This should simplify code later in this function.  */
1477   if (*pos - name_pos < 3)
1478     return FALSE;
1479 
1480   memset (buffer, 0, size);
1481   /* name_pos isn't 0-terminated, so we have to limit the length expressly */
1482   len = *pos - name_pos > size - 1 ? size - 1 : *pos - name_pos;
1483   strncpy (buffer, name_pos, len);
1484   *pos += quoted;
1485   return TRUE;
1486 }
1487 
1488 static gboolean
parse_identifier_boundaries(gchar ** pos,TimeZoneRule * tzr)1489 parse_identifier_boundaries (gchar **pos, TimeZoneRule *tzr)
1490 {
1491   if (*(*pos)++ != ',')
1492     return FALSE;
1493 
1494   /* Start date */
1495   if (!parse_identifier_boundary (pos, &(tzr->dlt_start)) || *(*pos)++ != ',')
1496     return FALSE;
1497 
1498   /* End date */
1499   if (!parse_identifier_boundary (pos, &(tzr->dlt_end)))
1500     return FALSE;
1501   return TRUE;
1502 }
1503 
1504 /*
1505  * Creates an array of TimeZoneRule from a TZ environment variable
1506  * type of identifier.  Should free rules afterwards
1507  */
1508 static guint
rules_from_identifier(const gchar * identifier,TimeZoneRule ** rules)1509 rules_from_identifier (const gchar   *identifier,
1510                        TimeZoneRule **rules)
1511 {
1512   gchar *pos;
1513   TimeZoneRule tzr;
1514 
1515   g_assert (rules != NULL);
1516 
1517   *rules = NULL;
1518 
1519   if (!identifier)
1520     return 0;
1521 
1522   pos = (gchar*)identifier;
1523   memset (&tzr, 0, sizeof (tzr));
1524   /* Standard offset */
1525   if (!(set_tz_name (&pos, tzr.std_name, NAME_SIZE)) ||
1526       !parse_offset (&pos, &(tzr.std_offset)))
1527     return 0;
1528 
1529   if (*pos == 0)
1530     {
1531       return create_ruleset_from_rule (rules, &tzr);
1532     }
1533 
1534   /* Format 2 */
1535   if (!(set_tz_name (&pos, tzr.dlt_name, NAME_SIZE)))
1536     return 0;
1537   parse_offset (&pos, &(tzr.dlt_offset));
1538   if (tzr.dlt_offset == 0) /* No daylight offset given, assume it's 1
1539                               hour earlier that standard */
1540     tzr.dlt_offset = tzr.std_offset - 3600;
1541   if (*pos == '\0')
1542 #ifdef G_OS_WIN32
1543     /* Windows allows us to use the US DST boundaries if they're not given */
1544     {
1545       int i;
1546       guint rules_num = 0;
1547 
1548       /* Use US rules, Windows' default is Pacific Standard Time */
1549       if ((rules_num = rules_from_windows_time_zone ("Pacific Standard Time",
1550                                                      NULL,
1551                                                      rules)))
1552         {
1553           for (i = 0; i < rules_num - 1; i++)
1554             {
1555               (*rules)[i].std_offset = - tzr.std_offset;
1556               (*rules)[i].dlt_offset = - tzr.dlt_offset;
1557               strcpy ((*rules)[i].std_name, tzr.std_name);
1558               strcpy ((*rules)[i].dlt_name, tzr.dlt_name);
1559             }
1560 
1561           return rules_num;
1562         }
1563       else
1564         return 0;
1565     }
1566 #else
1567   return 0;
1568 #endif
1569   /* Start and end required (format 2) */
1570   if (!parse_identifier_boundaries (&pos, &tzr))
1571     return 0;
1572 
1573   return create_ruleset_from_rule (rules, &tzr);
1574 }
1575 
1576 #ifdef G_OS_UNIX
1577 static GTimeZone *
parse_footertz(const gchar * footer,size_t footerlen)1578 parse_footertz (const gchar *footer, size_t footerlen)
1579 {
1580   gchar *tzstring = g_strndup (footer + 1, footerlen - 2);
1581   GTimeZone *footertz = NULL;
1582 
1583   /* FIXME: The allocation for tzstring could be avoided by
1584      passing a gsize identifier_len argument to rules_from_identifier
1585      and changing the code in that function to stop assuming that
1586      identifier is nul-terminated.  */
1587   TimeZoneRule *rules;
1588   guint rules_num = rules_from_identifier (tzstring, &rules);
1589 
1590   g_free (tzstring);
1591   if (rules_num > 1)
1592     {
1593       footertz = g_slice_new0 (GTimeZone);
1594       init_zone_from_rules (footertz, rules, rules_num, NULL);
1595       footertz->ref_count++;
1596     }
1597   g_free (rules);
1598   return footertz;
1599 }
1600 #endif
1601 
1602 /* Construction {{{1 */
1603 /**
1604  * g_time_zone_new:
1605  * @identifier: (nullable): a timezone identifier
1606  *
1607  * A version of g_time_zone_new_identifier() which returns the UTC time zone
1608  * if @identifier could not be parsed or loaded.
1609  *
1610  * If you need to check whether @identifier was loaded successfully, use
1611  * g_time_zone_new_identifier().
1612  *
1613  * Returns: (transfer full) (not nullable): the requested timezone
1614  * Deprecated: 2.68: Use g_time_zone_new_identifier() instead, as it provides
1615  *     error reporting. Change your code to handle a potentially %NULL return
1616  *     value.
1617  *
1618  * Since: 2.26
1619  **/
1620 GTimeZone *
g_time_zone_new(const gchar * identifier)1621 g_time_zone_new (const gchar *identifier)
1622 {
1623   GTimeZone *tz = g_time_zone_new_identifier (identifier);
1624 
1625   /* Always fall back to UTC. */
1626   if (tz == NULL)
1627     tz = g_time_zone_new_utc ();
1628 
1629   g_assert (tz != NULL);
1630 
1631   return g_steal_pointer (&tz);
1632 }
1633 
1634 /**
1635  * g_time_zone_new_identifier:
1636  * @identifier: (nullable): a timezone identifier
1637  *
1638  * Creates a #GTimeZone corresponding to @identifier. If @identifier cannot be
1639  * parsed or loaded, %NULL is returned.
1640  *
1641  * @identifier can either be an RFC3339/ISO 8601 time offset or
1642  * something that would pass as a valid value for the `TZ` environment
1643  * variable (including %NULL).
1644  *
1645  * In Windows, @identifier can also be the unlocalized name of a time
1646  * zone for standard time, for example "Pacific Standard Time".
1647  *
1648  * Valid RFC3339 time offsets are `"Z"` (for UTC) or
1649  * `"±hh:mm"`.  ISO 8601 additionally specifies
1650  * `"±hhmm"` and `"±hh"`.  Offsets are
1651  * time values to be added to Coordinated Universal Time (UTC) to get
1652  * the local time.
1653  *
1654  * In UNIX, the `TZ` environment variable typically corresponds
1655  * to the name of a file in the zoneinfo database, an absolute path to a file
1656  * somewhere else, or a string in
1657  * "std offset [dst [offset],start[/time],end[/time]]" (POSIX) format.
1658  * There  are  no spaces in the specification. The name of standard
1659  * and daylight savings time zone must be three or more alphabetic
1660  * characters. Offsets are time values to be added to local time to
1661  * get Coordinated Universal Time (UTC) and should be
1662  * `"[±]hh[[:]mm[:ss]]"`.  Dates are either
1663  * `"Jn"` (Julian day with n between 1 and 365, leap
1664  * years not counted), `"n"` (zero-based Julian day
1665  * with n between 0 and 365) or `"Mm.w.d"` (day d
1666  * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
1667  * 0 is a Sunday).  Times are in local wall clock time, the default is
1668  * 02:00:00.
1669  *
1670  * In Windows, the "tzn[+|–]hh[:mm[:ss]][dzn]" format is used, but also
1671  * accepts POSIX format.  The Windows format uses US rules for all time
1672  * zones; daylight savings time is 60 minutes behind the standard time
1673  * with date and time of change taken from Pacific Standard Time.
1674  * Offsets are time values to be added to the local time to get
1675  * Coordinated Universal Time (UTC).
1676  *
1677  * g_time_zone_new_local() calls this function with the value of the
1678  * `TZ` environment variable. This function itself is independent of
1679  * the value of `TZ`, but if @identifier is %NULL then `/etc/localtime`
1680  * will be consulted to discover the correct time zone on UNIX and the
1681  * registry will be consulted or GetTimeZoneInformation() will be used
1682  * to get the local time zone on Windows.
1683  *
1684  * If intervals are not available, only time zone rules from `TZ`
1685  * environment variable or other means, then they will be computed
1686  * from year 1900 to 2037.  If the maximum year for the rules is
1687  * available and it is greater than 2037, then it will followed
1688  * instead.
1689  *
1690  * See
1691  * [RFC3339 §5.6](http://tools.ietf.org/html/rfc3339#section-5.6)
1692  * for a precise definition of valid RFC3339 time offsets
1693  * (the `time-offset` expansion) and ISO 8601 for the
1694  * full list of valid time offsets.  See
1695  * [The GNU C Library manual](http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html)
1696  * for an explanation of the possible
1697  * values of the `TZ` environment variable. See
1698  * [Microsoft Time Zone Index Values](http://msdn.microsoft.com/en-us/library/ms912391%28v=winembedded.11%29.aspx)
1699  * for the list of time zones on Windows.
1700  *
1701  * You should release the return value by calling g_time_zone_unref()
1702  * when you are done with it.
1703  *
1704  * Returns: (transfer full) (nullable): the requested timezone, or %NULL on
1705  *     failure
1706  * Since: 2.68
1707  */
1708 GTimeZone *
g_time_zone_new_identifier(const gchar * identifier)1709 g_time_zone_new_identifier (const gchar *identifier)
1710 {
1711   GTimeZone *tz = NULL;
1712   TimeZoneRule *rules;
1713   gint rules_num;
1714   gchar *resolved_identifier = NULL;
1715 
1716   if (identifier)
1717     {
1718       G_LOCK (time_zones);
1719       if (time_zones == NULL)
1720         time_zones = g_hash_table_new (g_str_hash, g_str_equal);
1721 
1722       tz = g_hash_table_lookup (time_zones, identifier);
1723       if (tz)
1724         {
1725           g_atomic_int_inc (&tz->ref_count);
1726           G_UNLOCK (time_zones);
1727           return tz;
1728         }
1729       else
1730         resolved_identifier = g_strdup (identifier);
1731     }
1732   else
1733     {
1734       G_LOCK (tz_default);
1735 #ifdef G_OS_UNIX
1736       resolved_identifier = zone_identifier_unix ();
1737 #elif defined (G_OS_WIN32)
1738       resolved_identifier = windows_default_tzname ();
1739 #endif
1740       if (tz_default)
1741         {
1742           /* Flush default if changed. If the identifier couldn’t be resolved,
1743            * we’re going to fall back to UTC eventually, so don’t clear out the
1744            * cache if it’s already UTC. */
1745           if (!(resolved_identifier == NULL && g_str_equal (tz_default->name, "UTC")) &&
1746               g_strcmp0 (tz_default->name, resolved_identifier) != 0)
1747             {
1748               g_clear_pointer (&tz_default, g_time_zone_unref);
1749             }
1750           else
1751             {
1752               tz = g_time_zone_ref (tz_default);
1753               G_UNLOCK (tz_default);
1754 
1755               g_free (resolved_identifier);
1756               return tz;
1757             }
1758         }
1759     }
1760 
1761   tz = g_slice_new0 (GTimeZone);
1762   tz->ref_count = 0;
1763 
1764   zone_for_constant_offset (tz, identifier);
1765 
1766   if (tz->t_info == NULL &&
1767       (rules_num = rules_from_identifier (identifier, &rules)))
1768     {
1769       init_zone_from_rules (tz, rules, rules_num, g_steal_pointer (&resolved_identifier));
1770       g_free (rules);
1771     }
1772 
1773   if (tz->t_info == NULL)
1774     {
1775 #ifdef G_OS_UNIX
1776       GBytes *zoneinfo = zone_info_unix (identifier, resolved_identifier);
1777       if (zoneinfo != NULL)
1778         {
1779           init_zone_from_iana_info (tz, zoneinfo, g_steal_pointer (&resolved_identifier));
1780           g_bytes_unref (zoneinfo);
1781         }
1782 #elif defined (G_OS_WIN32)
1783       if ((rules_num = rules_from_windows_time_zone (identifier,
1784                                                      resolved_identifier,
1785                                                      &rules)))
1786         {
1787           init_zone_from_rules (tz, rules, rules_num, g_steal_pointer (&resolved_identifier));
1788           g_free (rules);
1789         }
1790 #endif
1791     }
1792 
1793 #if defined (G_OS_WIN32)
1794   if (tz->t_info == NULL)
1795     {
1796       if (identifier == NULL)
1797         {
1798           TIME_ZONE_INFORMATION tzi;
1799 
1800           if (GetTimeZoneInformation (&tzi) != TIME_ZONE_ID_INVALID)
1801             {
1802               rules = g_new0 (TimeZoneRule, 2);
1803 
1804               if (rule_from_windows_time_zone_info (&rules[0], &tzi))
1805                 {
1806                   memset (rules[0].std_name, 0, NAME_SIZE);
1807                   memset (rules[0].dlt_name, 0, NAME_SIZE);
1808 
1809                   rules[0].start_year = MIN_TZYEAR;
1810                   rules[1].start_year = MAX_TZYEAR;
1811 
1812                   init_zone_from_rules (tz, rules, 2, g_steal_pointer (&resolved_identifier));
1813                 }
1814 
1815               g_free (rules);
1816             }
1817         }
1818     }
1819 #endif
1820 
1821   g_free (resolved_identifier);
1822 
1823   /* Failed to load the timezone. */
1824   if (tz->t_info == NULL)
1825     {
1826       g_slice_free (GTimeZone, tz);
1827 
1828       if (identifier)
1829         G_UNLOCK (time_zones);
1830       else
1831         G_UNLOCK (tz_default);
1832 
1833       return NULL;
1834     }
1835 
1836   g_assert (tz->name != NULL);
1837   g_assert (tz->t_info != NULL);
1838 
1839   if (identifier)
1840     g_hash_table_insert (time_zones, tz->name, tz);
1841   else if (tz->name)
1842     {
1843       /* Caching reference */
1844       g_atomic_int_inc (&tz->ref_count);
1845       tz_default = tz;
1846     }
1847 
1848   g_atomic_int_inc (&tz->ref_count);
1849 
1850   if (identifier)
1851     G_UNLOCK (time_zones);
1852   else
1853     G_UNLOCK (tz_default);
1854 
1855   return tz;
1856 }
1857 
1858 /**
1859  * g_time_zone_new_utc:
1860  *
1861  * Creates a #GTimeZone corresponding to UTC.
1862  *
1863  * This is equivalent to calling g_time_zone_new() with a value like
1864  * "Z", "UTC", "+00", etc.
1865  *
1866  * You should release the return value by calling g_time_zone_unref()
1867  * when you are done with it.
1868  *
1869  * Returns: the universal timezone
1870  *
1871  * Since: 2.26
1872  **/
1873 GTimeZone *
g_time_zone_new_utc(void)1874 g_time_zone_new_utc (void)
1875 {
1876   static GTimeZone *utc = NULL;
1877   static gsize initialised;
1878 
1879   if (g_once_init_enter (&initialised))
1880     {
1881       utc = g_time_zone_new_identifier ("UTC");
1882       g_assert (utc != NULL);
1883       g_once_init_leave (&initialised, TRUE);
1884     }
1885 
1886   return g_time_zone_ref (utc);
1887 }
1888 
1889 /**
1890  * g_time_zone_new_local:
1891  *
1892  * Creates a #GTimeZone corresponding to local time.  The local time
1893  * zone may change between invocations to this function; for example,
1894  * if the system administrator changes it.
1895  *
1896  * This is equivalent to calling g_time_zone_new() with the value of
1897  * the `TZ` environment variable (including the possibility of %NULL).
1898  *
1899  * You should release the return value by calling g_time_zone_unref()
1900  * when you are done with it.
1901  *
1902  * Returns: the local timezone
1903  *
1904  * Since: 2.26
1905  **/
1906 GTimeZone *
g_time_zone_new_local(void)1907 g_time_zone_new_local (void)
1908 {
1909   const gchar *tzenv = g_getenv ("TZ");
1910   GTimeZone *tz;
1911 
1912   G_LOCK (tz_local);
1913 
1914   /* Is time zone changed and must be flushed? */
1915   if (tz_local && g_strcmp0 (g_time_zone_get_identifier (tz_local), tzenv))
1916     g_clear_pointer (&tz_local, g_time_zone_unref);
1917 
1918   if (tz_local == NULL)
1919     tz_local = g_time_zone_new_identifier (tzenv);
1920   if (tz_local == NULL)
1921     tz_local = g_time_zone_new_utc ();
1922 
1923   tz = g_time_zone_ref (tz_local);
1924 
1925   G_UNLOCK (tz_local);
1926 
1927   return tz;
1928 }
1929 
1930 /**
1931  * g_time_zone_new_offset:
1932  * @seconds: offset to UTC, in seconds
1933  *
1934  * Creates a #GTimeZone corresponding to the given constant offset from UTC,
1935  * in seconds.
1936  *
1937  * This is equivalent to calling g_time_zone_new() with a string in the form
1938  * `[+|-]hh[:mm[:ss]]`.
1939  *
1940  * Returns: (transfer full): a timezone at the given offset from UTC
1941  * Since: 2.58
1942  */
1943 GTimeZone *
g_time_zone_new_offset(gint32 seconds)1944 g_time_zone_new_offset (gint32 seconds)
1945 {
1946   GTimeZone *tz = NULL;
1947   gchar *identifier = NULL;
1948 
1949   /* Seemingly, we should be using @seconds directly to set the
1950    * #TransitionInfo.gmt_offset to avoid all this string building and parsing.
1951    * However, we always need to set the #GTimeZone.name to a constructed
1952    * string anyway, so we might as well reuse its code.
1953    * g_time_zone_new_identifier() should never fail in this situation. */
1954   identifier = g_strdup_printf ("%c%02u:%02u:%02u",
1955                                 (seconds >= 0) ? '+' : '-',
1956                                 (ABS (seconds) / 60) / 60,
1957                                 (ABS (seconds) / 60) % 60,
1958                                 ABS (seconds) % 60);
1959   tz = g_time_zone_new_identifier (identifier);
1960   g_assert (tz != NULL);
1961   g_free (identifier);
1962 
1963   g_assert (g_time_zone_get_offset (tz, 0) == seconds);
1964 
1965   return tz;
1966 }
1967 
1968 #define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
1969 #define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
1970 
1971 /* Internal helpers {{{1 */
1972 /* NB: Interval 0 is before the first transition, so there's no
1973  * transition structure to point to which TransitionInfo to
1974  * use. Rule-based zones are set up so that TI 0 is always standard
1975  * time (which is what's in effect before Daylight time got started
1976  * in the early 20th century), but IANA tzfiles don't follow that
1977  * convention. The tzfile documentation says to use the first
1978  * standard-time (i.e., non-DST) tinfo, so that's what we do.
1979  */
1980 inline static const TransitionInfo*
interval_info(GTimeZone * tz,guint interval)1981 interval_info (GTimeZone *tz,
1982                guint      interval)
1983 {
1984   guint index;
1985   g_return_val_if_fail (tz->t_info != NULL, NULL);
1986   if (interval && tz->transitions && interval <= tz->transitions->len)
1987     index = (TRANSITION(interval - 1)).info_index;
1988   else
1989     {
1990       for (index = 0; index < tz->t_info->len; index++)
1991         {
1992           TransitionInfo *tzinfo = &(TRANSITION_INFO(index));
1993           if (!tzinfo->is_dst)
1994             return tzinfo;
1995         }
1996       index = 0;
1997     }
1998 
1999   return &(TRANSITION_INFO(index));
2000 }
2001 
2002 inline static gint64
interval_start(GTimeZone * tz,guint interval)2003 interval_start (GTimeZone *tz,
2004                 guint      interval)
2005 {
2006   if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
2007     return G_MININT64;
2008   if (interval > tz->transitions->len)
2009     interval = tz->transitions->len;
2010   return (TRANSITION(interval - 1)).time;
2011 }
2012 
2013 inline static gint64
interval_end(GTimeZone * tz,guint interval)2014 interval_end (GTimeZone *tz,
2015               guint      interval)
2016 {
2017   if (tz->transitions && interval < tz->transitions->len)
2018     {
2019       gint64 lim = (TRANSITION(interval)).time;
2020       return lim - (lim != G_MININT64);
2021     }
2022   return G_MAXINT64;
2023 }
2024 
2025 inline static gint32
interval_offset(GTimeZone * tz,guint interval)2026 interval_offset (GTimeZone *tz,
2027                  guint      interval)
2028 {
2029   g_return_val_if_fail (tz->t_info != NULL, 0);
2030   return interval_info (tz, interval)->gmt_offset;
2031 }
2032 
2033 inline static gboolean
interval_isdst(GTimeZone * tz,guint interval)2034 interval_isdst (GTimeZone *tz,
2035                 guint      interval)
2036 {
2037   g_return_val_if_fail (tz->t_info != NULL, 0);
2038   return interval_info (tz, interval)->is_dst;
2039 }
2040 
2041 
2042 inline static gchar*
interval_abbrev(GTimeZone * tz,guint interval)2043 interval_abbrev (GTimeZone *tz,
2044                   guint      interval)
2045 {
2046   g_return_val_if_fail (tz->t_info != NULL, 0);
2047   return interval_info (tz, interval)->abbrev;
2048 }
2049 
2050 inline static gint64
interval_local_start(GTimeZone * tz,guint interval)2051 interval_local_start (GTimeZone *tz,
2052                       guint      interval)
2053 {
2054   if (interval)
2055     return interval_start (tz, interval) + interval_offset (tz, interval);
2056 
2057   return G_MININT64;
2058 }
2059 
2060 inline static gint64
interval_local_end(GTimeZone * tz,guint interval)2061 interval_local_end (GTimeZone *tz,
2062                     guint      interval)
2063 {
2064   if (tz->transitions && interval < tz->transitions->len)
2065     return interval_end (tz, interval) + interval_offset (tz, interval);
2066 
2067   return G_MAXINT64;
2068 }
2069 
2070 static gboolean
interval_valid(GTimeZone * tz,guint interval)2071 interval_valid (GTimeZone *tz,
2072                 guint      interval)
2073 {
2074   if ( tz->transitions == NULL)
2075     return interval == 0;
2076   return interval <= tz->transitions->len;
2077 }
2078 
2079 /* g_time_zone_find_interval() {{{1 */
2080 
2081 /**
2082  * g_time_zone_adjust_time:
2083  * @tz: a #GTimeZone
2084  * @type: the #GTimeType of @time_
2085  * @time_: a pointer to a number of seconds since January 1, 1970
2086  *
2087  * Finds an interval within @tz that corresponds to the given @time_,
2088  * possibly adjusting @time_ if required to fit into an interval.
2089  * The meaning of @time_ depends on @type.
2090  *
2091  * This function is similar to g_time_zone_find_interval(), with the
2092  * difference that it always succeeds (by making the adjustments
2093  * described below).
2094  *
2095  * In any of the cases where g_time_zone_find_interval() succeeds then
2096  * this function returns the same value, without modifying @time_.
2097  *
2098  * This function may, however, modify @time_ in order to deal with
2099  * non-existent times.  If the non-existent local @time_ of 02:30 were
2100  * requested on March 14th 2010 in Toronto then this function would
2101  * adjust @time_ to be 03:00 and return the interval containing the
2102  * adjusted time.
2103  *
2104  * Returns: the interval containing @time_, never -1
2105  *
2106  * Since: 2.26
2107  **/
2108 gint
g_time_zone_adjust_time(GTimeZone * tz,GTimeType type,gint64 * time_)2109 g_time_zone_adjust_time (GTimeZone *tz,
2110                          GTimeType  type,
2111                          gint64    *time_)
2112 {
2113   guint i, intervals;
2114   gboolean interval_is_dst;
2115 
2116   if (tz->transitions == NULL)
2117     return 0;
2118 
2119   intervals = tz->transitions->len;
2120 
2121   /* find the interval containing *time UTC
2122    * TODO: this could be binary searched (or better) */
2123   for (i = 0; i <= intervals; i++)
2124     if (*time_ <= interval_end (tz, i))
2125       break;
2126 
2127   g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
2128 
2129   if (type != G_TIME_TYPE_UNIVERSAL)
2130     {
2131       if (*time_ < interval_local_start (tz, i))
2132         /* if time came before the start of this interval... */
2133         {
2134           i--;
2135 
2136           /* if it's not in the previous interval... */
2137           if (*time_ > interval_local_end (tz, i))
2138             {
2139               /* it doesn't exist.  fast-forward it. */
2140               i++;
2141               *time_ = interval_local_start (tz, i);
2142             }
2143         }
2144 
2145       else if (*time_ > interval_local_end (tz, i))
2146         /* if time came after the end of this interval... */
2147         {
2148           i++;
2149 
2150           /* if it's not in the next interval... */
2151           if (*time_ < interval_local_start (tz, i))
2152             /* it doesn't exist.  fast-forward it. */
2153             *time_ = interval_local_start (tz, i);
2154         }
2155 
2156       else
2157         {
2158           interval_is_dst = interval_isdst (tz, i);
2159           if ((interval_is_dst && type != G_TIME_TYPE_DAYLIGHT) ||
2160               (!interval_is_dst && type == G_TIME_TYPE_DAYLIGHT))
2161             {
2162               /* it's in this interval, but dst flag doesn't match.
2163                * check neighbours for a better fit. */
2164               if (i && *time_ <= interval_local_end (tz, i - 1))
2165                 i--;
2166 
2167               else if (i < intervals &&
2168                        *time_ >= interval_local_start (tz, i + 1))
2169                 i++;
2170             }
2171         }
2172     }
2173 
2174   return i;
2175 }
2176 
2177 /**
2178  * g_time_zone_find_interval:
2179  * @tz: a #GTimeZone
2180  * @type: the #GTimeType of @time_
2181  * @time_: a number of seconds since January 1, 1970
2182  *
2183  * Finds an interval within @tz that corresponds to the given @time_.
2184  * The meaning of @time_ depends on @type.
2185  *
2186  * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
2187  * succeed (since universal time is monotonic and continuous).
2188  *
2189  * Otherwise @time_ is treated as local time.  The distinction between
2190  * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
2191  * the case that the given @time_ is ambiguous.  In Toronto, for example,
2192  * 01:30 on November 7th 2010 occurred twice (once inside of daylight
2193  * savings time and the next, an hour later, outside of daylight savings
2194  * time).  In this case, the different value of @type would result in a
2195  * different interval being returned.
2196  *
2197  * It is still possible for this function to fail.  In Toronto, for
2198  * example, 02:00 on March 14th 2010 does not exist (due to the leap
2199  * forward to begin daylight savings time).  -1 is returned in that
2200  * case.
2201  *
2202  * Returns: the interval containing @time_, or -1 in case of failure
2203  *
2204  * Since: 2.26
2205  */
2206 gint
g_time_zone_find_interval(GTimeZone * tz,GTimeType type,gint64 time_)2207 g_time_zone_find_interval (GTimeZone *tz,
2208                            GTimeType  type,
2209                            gint64     time_)
2210 {
2211   guint i, intervals;
2212   gboolean interval_is_dst;
2213 
2214   if (tz->transitions == NULL)
2215     return 0;
2216   intervals = tz->transitions->len;
2217   for (i = 0; i <= intervals; i++)
2218     if (time_ <= interval_end (tz, i))
2219       break;
2220 
2221   if (type == G_TIME_TYPE_UNIVERSAL)
2222     return i;
2223 
2224   if (time_ < interval_local_start (tz, i))
2225     {
2226       if (time_ > interval_local_end (tz, --i))
2227         return -1;
2228     }
2229 
2230   else if (time_ > interval_local_end (tz, i))
2231     {
2232       if (time_ < interval_local_start (tz, ++i))
2233         return -1;
2234     }
2235 
2236   else
2237     {
2238       interval_is_dst = interval_isdst (tz, i);
2239       if  ((interval_is_dst && type != G_TIME_TYPE_DAYLIGHT) ||
2240            (!interval_is_dst && type == G_TIME_TYPE_DAYLIGHT))
2241         {
2242           if (i && time_ <= interval_local_end (tz, i - 1))
2243             i--;
2244 
2245           else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
2246             i++;
2247         }
2248     }
2249 
2250   return i;
2251 }
2252 
2253 /* Public API accessors {{{1 */
2254 
2255 /**
2256  * g_time_zone_get_abbreviation:
2257  * @tz: a #GTimeZone
2258  * @interval: an interval within the timezone
2259  *
2260  * Determines the time zone abbreviation to be used during a particular
2261  * @interval of time in the time zone @tz.
2262  *
2263  * For example, in Toronto this is currently "EST" during the winter
2264  * months and "EDT" during the summer months when daylight savings time
2265  * is in effect.
2266  *
2267  * Returns: the time zone abbreviation, which belongs to @tz
2268  *
2269  * Since: 2.26
2270  **/
2271 const gchar *
g_time_zone_get_abbreviation(GTimeZone * tz,gint interval)2272 g_time_zone_get_abbreviation (GTimeZone *tz,
2273                               gint       interval)
2274 {
2275   g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
2276 
2277   return interval_abbrev (tz, (guint)interval);
2278 }
2279 
2280 /**
2281  * g_time_zone_get_offset:
2282  * @tz: a #GTimeZone
2283  * @interval: an interval within the timezone
2284  *
2285  * Determines the offset to UTC in effect during a particular @interval
2286  * of time in the time zone @tz.
2287  *
2288  * The offset is the number of seconds that you add to UTC time to
2289  * arrive at local time for @tz (ie: negative numbers for time zones
2290  * west of GMT, positive numbers for east).
2291  *
2292  * Returns: the number of seconds that should be added to UTC to get the
2293  *          local time in @tz
2294  *
2295  * Since: 2.26
2296  **/
2297 gint32
g_time_zone_get_offset(GTimeZone * tz,gint interval)2298 g_time_zone_get_offset (GTimeZone *tz,
2299                         gint       interval)
2300 {
2301   g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
2302 
2303   return interval_offset (tz, (guint)interval);
2304 }
2305 
2306 /**
2307  * g_time_zone_is_dst:
2308  * @tz: a #GTimeZone
2309  * @interval: an interval within the timezone
2310  *
2311  * Determines if daylight savings time is in effect during a particular
2312  * @interval of time in the time zone @tz.
2313  *
2314  * Returns: %TRUE if daylight savings time is in effect
2315  *
2316  * Since: 2.26
2317  **/
2318 gboolean
g_time_zone_is_dst(GTimeZone * tz,gint interval)2319 g_time_zone_is_dst (GTimeZone *tz,
2320                     gint       interval)
2321 {
2322   g_return_val_if_fail (interval_valid (tz, interval), FALSE);
2323 
2324   if (tz->transitions == NULL)
2325     return FALSE;
2326 
2327   return interval_isdst (tz, (guint)interval);
2328 }
2329 
2330 /**
2331  * g_time_zone_get_identifier:
2332  * @tz: a #GTimeZone
2333  *
2334  * Get the identifier of this #GTimeZone, as passed to g_time_zone_new().
2335  * If the identifier passed at construction time was not recognised, `UTC` will
2336  * be returned. If it was %NULL, the identifier of the local timezone at
2337  * construction time will be returned.
2338  *
2339  * The identifier will be returned in the same format as provided at
2340  * construction time: if provided as a time offset, that will be returned by
2341  * this function.
2342  *
2343  * Returns: identifier for this timezone
2344  * Since: 2.58
2345  */
2346 const gchar *
g_time_zone_get_identifier(GTimeZone * tz)2347 g_time_zone_get_identifier (GTimeZone *tz)
2348 {
2349   g_return_val_if_fail (tz != NULL, NULL);
2350 
2351   return tz->name;
2352 }
2353 
2354 /* Epilogue {{{1 */
2355 /* vim:set foldmethod=marker: */
2356