Add some more cases to the app-id unit tests
[glib.git] / glib / gtimezone.c
blob89aa83e41e4af60e82c742ebdcf3a258d743c843
1 /*
2 * Copyright © 2010 Codethink Limited
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.
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.
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/>.
17 * Author: Ryan Lortie <desrt@desrt.ca>
20 /* Prologue {{{1 */
22 #include "config.h"
24 #include "gtimezone.h"
26 #include <string.h>
27 #include <stdlib.h>
28 #include <signal.h>
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"
41 #ifdef G_OS_WIN32
42 #define STRICT
43 #include <windows.h>
44 #endif
46 /**
47 * SECTION:timezone
48 * @title: GTimeZone
49 * @short_description: a structure representing a time zone
50 * @see_also: #GDateTime
52 * #GTimeZone is a structure that represents a time zone, at no
53 * particular point in time. It is refcounted and immutable.
55 * A time zone contains a number of intervals. Each interval has
56 * an abbreviation to describe it, an offet to UTC and a flag indicating
57 * if the daylight savings time is in effect during that interval. A
58 * time zone always has at least one interval -- interval 0.
60 * Every UTC time is contained within exactly one interval, but a given
61 * local time may be contained within zero, one or two intervals (due to
62 * incontinuities associated with daylight savings time).
64 * An interval may refer to a specific period of time (eg: the duration
65 * of daylight savings time during 2010) or it may refer to many periods
66 * of time that share the same properties (eg: all periods of daylight
67 * savings time). It is also possible (usually for political reasons)
68 * that some properties (like the abbreviation) change between intervals
69 * without other properties changing.
71 * #GTimeZone is available since GLib 2.26.
74 /**
75 * GTimeZone:
77 * #GTimeZone is an opaque structure whose members cannot be accessed
78 * directly.
80 * Since: 2.26
81 **/
83 /* IANA zoneinfo file format {{{1 */
85 /* unaligned */
86 typedef struct { gchar bytes[8]; } gint64_be;
87 typedef struct { gchar bytes[4]; } gint32_be;
88 typedef struct { gchar bytes[4]; } guint32_be;
90 static inline gint64 gint64_from_be (const gint64_be be) {
91 gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
94 static inline gint32 gint32_from_be (const gint32_be be) {
95 gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
98 static inline guint32 guint32_from_be (const guint32_be be) {
99 guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
102 /* The layout of an IANA timezone file header */
103 struct tzhead
105 gchar tzh_magic[4];
106 gchar tzh_version;
107 guchar tzh_reserved[15];
109 guint32_be tzh_ttisgmtcnt;
110 guint32_be tzh_ttisstdcnt;
111 guint32_be tzh_leapcnt;
112 guint32_be tzh_timecnt;
113 guint32_be tzh_typecnt;
114 guint32_be tzh_charcnt;
117 struct ttinfo
119 gint32_be tt_gmtoff;
120 guint8 tt_isdst;
121 guint8 tt_abbrind;
124 /* A Transition Date structure for TZ Rules, an intermediate structure
125 for parsing MSWindows and Environment-variable time zones. It
126 Generalizes MSWindows's SYSTEMTIME struct.
128 typedef struct
130 gint year;
131 gint mon;
132 gint mday;
133 gint wday;
134 gint week;
135 gint hour;
136 gint min;
137 gint sec;
138 } TimeZoneDate;
140 /* POSIX Timezone abbreviations are typically 3 or 4 characters, but
141 Microsoft uses 32-character names. We'll use one larger to ensure
142 we have room for the terminating \0.
144 #define NAME_SIZE 33
146 /* A MSWindows-style time zone transition rule. Generalizes the
147 MSWindows TIME_ZONE_INFORMATION struct. Also used to compose time
148 zones from tzset-style identifiers.
150 typedef struct
152 gint start_year;
153 gint32 std_offset;
154 gint32 dlt_offset;
155 TimeZoneDate dlt_start;
156 TimeZoneDate dlt_end;
157 gchar std_name[NAME_SIZE];
158 gchar dlt_name[NAME_SIZE];
159 } TimeZoneRule;
161 /* GTimeZone's internal representation of a Daylight Savings (Summer)
162 time interval.
164 typedef struct
166 gint32 gmt_offset;
167 gboolean is_dst;
168 gchar *abbrev;
169 } TransitionInfo;
171 /* GTimeZone's representation of a transition time to or from Daylight
172 Savings (Summer) time and Standard time for the zone. */
173 typedef struct
175 gint64 time;
176 gint info_index;
177 } Transition;
179 /* GTimeZone structure */
180 struct _GTimeZone
182 gchar *name;
183 GArray *t_info; /* Array of TransitionInfo */
184 GArray *transitions; /* Array of Transition */
185 gint ref_count;
188 G_LOCK_DEFINE_STATIC (time_zones);
189 static GHashTable/*<string?, GTimeZone>*/ *time_zones;
191 #define MIN_TZYEAR 1916 /* Daylight Savings started in WWI */
192 #define MAX_TZYEAR 2999 /* And it's not likely ever to go away, but
193 there's no point in getting carried
194 away. */
197 * g_time_zone_unref:
198 * @tz: a #GTimeZone
200 * Decreases the reference count on @tz.
202 * Since: 2.26
204 void
205 g_time_zone_unref (GTimeZone *tz)
207 int ref_count;
209 again:
210 ref_count = g_atomic_int_get (&tz->ref_count);
212 g_assert (ref_count > 0);
214 if (ref_count == 1)
216 if (tz->name != NULL)
218 G_LOCK(time_zones);
220 /* someone else might have grabbed a ref in the meantime */
221 if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
223 G_UNLOCK(time_zones);
224 goto again;
227 g_hash_table_remove (time_zones, tz->name);
228 G_UNLOCK(time_zones);
231 if (tz->t_info != NULL)
233 gint idx;
234 for (idx = 0; idx < tz->t_info->len; idx++)
236 TransitionInfo *info = &g_array_index (tz->t_info, TransitionInfo, idx);
237 g_free (info->abbrev);
239 g_array_free (tz->t_info, TRUE);
241 if (tz->transitions != NULL)
242 g_array_free (tz->transitions, TRUE);
243 g_free (tz->name);
245 g_slice_free (GTimeZone, tz);
248 else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
249 ref_count,
250 ref_count - 1))
251 goto again;
255 * g_time_zone_ref:
256 * @tz: a #GTimeZone
258 * Increases the reference count on @tz.
260 * Returns: a new reference to @tz.
262 * Since: 2.26
264 GTimeZone *
265 g_time_zone_ref (GTimeZone *tz)
267 g_assert (tz->ref_count > 0);
269 g_atomic_int_inc (&tz->ref_count);
271 return tz;
274 /* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
276 * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
277 * - h[h] is 0 to 23
278 * - mm is 00 to 59
279 * - ss is 00 to 59
281 static gboolean
282 parse_time (const gchar *time_,
283 gint32 *offset)
285 if (*time_ < '0' || '9' < *time_)
286 return FALSE;
288 *offset = 60 * 60 * (*time_++ - '0');
290 if (*time_ == '\0')
291 return TRUE;
293 if (*time_ != ':')
295 if (*time_ < '0' || '9' < *time_)
296 return FALSE;
298 *offset *= 10;
299 *offset += 60 * 60 * (*time_++ - '0');
301 if (*offset > 23 * 60 * 60)
302 return FALSE;
304 if (*time_ == '\0')
305 return TRUE;
308 if (*time_ == ':')
309 time_++;
311 if (*time_ < '0' || '5' < *time_)
312 return FALSE;
314 *offset += 10 * 60 * (*time_++ - '0');
316 if (*time_ < '0' || '9' < *time_)
317 return FALSE;
319 *offset += 60 * (*time_++ - '0');
321 if (*time_ == '\0')
322 return TRUE;
324 if (*time_ == ':')
325 time_++;
327 if (*time_ < '0' || '5' < *time_)
328 return FALSE;
330 *offset += 10 * (*time_++ - '0');
332 if (*time_ < '0' || '9' < *time_)
333 return FALSE;
335 *offset += *time_++ - '0';
337 return *time_ == '\0';
340 static gboolean
341 parse_constant_offset (const gchar *name,
342 gint32 *offset)
344 if (g_strcmp0 (name, "UTC") == 0)
346 *offset = 0;
347 return TRUE;
350 if (*name >= '0' && '9' >= *name)
351 return parse_time (name, offset);
353 switch (*name++)
355 case 'Z':
356 *offset = 0;
357 return !*name;
359 case '+':
360 return parse_time (name, offset);
362 case '-':
363 if (parse_time (name, offset))
365 *offset = -*offset;
366 return TRUE;
369 default:
370 return FALSE;
374 static void
375 zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
377 gint32 offset;
378 TransitionInfo info;
380 if (name == NULL || !parse_constant_offset (name, &offset))
381 return;
383 info.gmt_offset = offset;
384 info.is_dst = FALSE;
385 info.abbrev = g_strdup (name);
388 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
389 g_array_append_val (gtz->t_info, info);
391 /* Constant offset, no transitions */
392 gtz->transitions = NULL;
395 #ifdef G_OS_UNIX
396 static GBytes*
397 zone_info_unix (const gchar *identifier)
399 gchar *filename;
400 GMappedFile *file = NULL;
401 GBytes *zoneinfo = NULL;
403 /* identifier can be a relative or absolute path name;
404 if relative, it is interpreted starting from /usr/share/zoneinfo
405 while the POSIX standard says it should start with :,
406 glibc allows both syntaxes, so we should too */
407 if (identifier != NULL)
409 const gchar *tzdir;
411 tzdir = getenv ("TZDIR");
412 if (tzdir == NULL)
413 tzdir = "/usr/share/zoneinfo";
415 if (*identifier == ':')
416 identifier ++;
418 if (g_path_is_absolute (identifier))
419 filename = g_strdup (identifier);
420 else
421 filename = g_build_filename (tzdir, identifier, NULL);
423 else
424 filename = g_strdup ("/etc/localtime");
426 file = g_mapped_file_new (filename, FALSE, NULL);
427 if (file != NULL)
429 zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
430 g_mapped_file_get_length (file),
431 (GDestroyNotify)g_mapped_file_unref,
432 g_mapped_file_ref (file));
433 g_mapped_file_unref (file);
435 g_free (filename);
436 return zoneinfo;
439 static void
440 init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
442 gsize size;
443 guint index;
444 guint32 time_count, type_count;
445 guint8 *tz_transitions, *tz_type_index, *tz_ttinfo;
446 guint8 *tz_abbrs;
447 gsize timesize = sizeof (gint32);
448 const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
450 g_return_if_fail (size >= sizeof (struct tzhead) &&
451 memcmp (header, "TZif", 4) == 0);
453 if (header->tzh_version == '2')
455 /* Skip ahead to the newer 64-bit data if it's available. */
456 header = (const struct tzhead *)
457 (((const gchar *) (header + 1)) +
458 guint32_from_be(header->tzh_ttisgmtcnt) +
459 guint32_from_be(header->tzh_ttisstdcnt) +
460 8 * guint32_from_be(header->tzh_leapcnt) +
461 5 * guint32_from_be(header->tzh_timecnt) +
462 6 * guint32_from_be(header->tzh_typecnt) +
463 guint32_from_be(header->tzh_charcnt));
464 timesize = sizeof (gint64);
466 time_count = guint32_from_be(header->tzh_timecnt);
467 type_count = guint32_from_be(header->tzh_typecnt);
469 tz_transitions = ((guint8 *) (header) + sizeof (*header));
470 tz_type_index = tz_transitions + timesize * time_count;
471 tz_ttinfo = tz_type_index + time_count;
472 tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
474 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
475 type_count);
476 gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
477 time_count);
479 for (index = 0; index < type_count; index++)
481 TransitionInfo t_info;
482 struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
483 t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
484 t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
485 t_info.abbrev = g_strdup ((gchar *) &tz_abbrs[info.tt_abbrind]);
486 g_array_append_val (gtz->t_info, t_info);
489 for (index = 0; index < time_count; index++)
491 Transition trans;
492 if (header->tzh_version == '2')
493 trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
494 else
495 trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
496 trans.info_index = tz_type_index[index];
497 g_assert (trans.info_index >= 0);
498 g_assert (trans.info_index < gtz->t_info->len);
499 g_array_append_val (gtz->transitions, trans);
503 #elif defined (G_OS_WIN32)
505 static void
506 copy_windows_systemtime (SYSTEMTIME *s_time, TimeZoneDate *tzdate)
508 tzdate->sec = s_time->wSecond;
509 tzdate->min = s_time->wMinute;
510 tzdate->hour = s_time->wHour;
511 tzdate->mon = s_time->wMonth;
512 tzdate->year = s_time->wYear;
513 tzdate->wday = s_time->wDayOfWeek ? s_time->wDayOfWeek : 7;
515 if (s_time->wYear)
517 tzdate->mday = s_time->wDay;
518 tzdate->wday = 0;
520 else
521 tzdate->week = s_time->wDay;
524 /* UTC = local time + bias while local time = UTC + offset */
525 static void
526 rule_from_windows_time_zone_info (TimeZoneRule *rule,
527 TIME_ZONE_INFORMATION *tzi)
529 /* Set offset */
530 if (tzi->StandardDate.wMonth)
532 rule->std_offset = -(tzi->Bias + tzi->StandardBias) * 60;
533 rule->dlt_offset = -(tzi->Bias + tzi->DaylightBias) * 60;
534 copy_windows_systemtime (&(tzi->DaylightDate), &(rule->dlt_start));
536 copy_windows_systemtime (&(tzi->StandardDate), &(rule->dlt_end));
540 else
542 rule->std_offset = -tzi->Bias * 60;
543 rule->dlt_start.mon = 0;
545 strncpy (rule->std_name, (gchar*)tzi->StandardName, NAME_SIZE - 1);
546 strncpy (rule->dlt_name, (gchar*)tzi->DaylightName, NAME_SIZE - 1);
549 static gchar*
550 windows_default_tzname (void)
552 const gchar *subkey =
553 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
554 HKEY key;
555 gchar *key_name = NULL;
556 if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
557 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
559 DWORD size = 0;
560 if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
561 NULL, &size) == ERROR_SUCCESS)
563 key_name = g_malloc ((gint)size);
564 if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
565 (LPBYTE)key_name, &size) != ERROR_SUCCESS)
567 g_free (key_name);
568 key_name = NULL;
571 RegCloseKey (key);
573 return key_name;
576 typedef struct
578 LONG Bias;
579 LONG StandardBias;
580 LONG DaylightBias;
581 SYSTEMTIME StandardDate;
582 SYSTEMTIME DaylightDate;
583 } RegTZI;
585 static void
586 system_time_copy (SYSTEMTIME *orig, SYSTEMTIME *target)
588 g_return_if_fail (orig != NULL);
589 g_return_if_fail (target != NULL);
591 target->wYear = orig->wYear;
592 target->wMonth = orig->wMonth;
593 target->wDayOfWeek = orig->wDayOfWeek;
594 target->wDay = orig->wDay;
595 target->wHour = orig->wHour;
596 target->wMinute = orig->wMinute;
597 target->wSecond = orig->wSecond;
598 target->wMilliseconds = orig->wMilliseconds;
601 static void
602 register_tzi_to_tzi (RegTZI *reg, TIME_ZONE_INFORMATION *tzi)
604 g_return_if_fail (reg != NULL);
605 g_return_if_fail (tzi != NULL);
606 tzi->Bias = reg->Bias;
607 system_time_copy (&(reg->StandardDate), &(tzi->StandardDate));
608 tzi->StandardBias = reg->StandardBias;
609 system_time_copy (&(reg->DaylightDate), &(tzi->DaylightDate));
610 tzi->DaylightBias = reg->DaylightBias;
613 static gint
614 rules_from_windows_time_zone (const gchar *identifier, TimeZoneRule **rules)
616 HKEY key;
617 gchar *subkey, *subkey_dynamic;
618 gchar *key_name = NULL;
619 const gchar *reg_key =
620 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
621 TIME_ZONE_INFORMATION tzi;
622 DWORD size;
623 gint rules_num = 0;
624 RegTZI regtzi, regtzi_prev;
626 *rules = NULL;
627 key_name = NULL;
629 if (!identifier)
630 key_name = windows_default_tzname ();
631 else
632 key_name = g_strdup (identifier);
634 if (!key_name)
635 return 0;
637 subkey = g_strconcat (reg_key, key_name, NULL);
638 subkey_dynamic = g_strconcat (subkey, "\\Dynamic DST", NULL);
640 if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
641 KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
642 return 0;
643 size = sizeof tzi.StandardName;
644 if (RegQueryValueExA (key, "Std", NULL, NULL,
645 (LPBYTE)&(tzi.StandardName), &size) != ERROR_SUCCESS)
646 goto failed;
648 size = sizeof tzi.DaylightName;
650 if (RegQueryValueExA (key, "Dlt", NULL, NULL,
651 (LPBYTE)&(tzi.DaylightName), &size) != ERROR_SUCCESS)
652 goto failed;
654 RegCloseKey (key);
655 if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey_dynamic, 0,
656 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
658 DWORD first, last;
659 int year, i;
660 gchar *s;
662 size = sizeof first;
663 if (RegQueryValueExA (key, "FirstEntry", NULL, NULL,
664 (LPBYTE) &first, &size) != ERROR_SUCCESS)
665 goto failed;
667 size = sizeof last;
668 if (RegQueryValueExA (key, "LastEntry", NULL, NULL,
669 (LPBYTE) &last, &size) != ERROR_SUCCESS)
670 goto failed;
672 rules_num = last - first + 2;
673 *rules = g_new0 (TimeZoneRule, rules_num);
675 for (year = first, i = 0; year <= last; year++)
677 s = g_strdup_printf ("%d", year);
679 size = sizeof regtzi;
680 if (RegQueryValueExA (key, s, NULL, NULL,
681 (LPBYTE) &regtzi, &size) != ERROR_SUCCESS)
683 g_free (*rules);
684 *rules = NULL;
685 break;
688 g_free (s);
690 if (year > first && memcmp (&regtzi_prev, &regtzi, sizeof regtzi) == 0)
691 continue;
692 else
693 memcpy (&regtzi_prev, &regtzi, sizeof regtzi);
695 register_tzi_to_tzi (&regtzi, &tzi);
696 rule_from_windows_time_zone_info (&(*rules)[i], &tzi);
697 (*rules)[i++].start_year = year;
700 rules_num = i + 1;
702 failed:
703 RegCloseKey (key);
705 else if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
706 KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
708 size = sizeof regtzi;
709 if (RegQueryValueExA (key, "TZI", NULL, NULL,
710 (LPBYTE) &regtzi, &size) == ERROR_SUCCESS)
712 rules_num = 2;
713 *rules = g_new0 (TimeZoneRule, 2);
714 register_tzi_to_tzi (&regtzi, &tzi);
715 rule_from_windows_time_zone_info (&(*rules)[0], &tzi);
718 RegCloseKey (key);
721 g_free (subkey_dynamic);
722 g_free (subkey);
723 g_free (key_name);
725 if (*rules)
727 (*rules)[0].start_year = MIN_TZYEAR;
728 if ((*rules)[rules_num - 2].start_year < MAX_TZYEAR)
729 (*rules)[rules_num - 1].start_year = MAX_TZYEAR;
730 else
731 (*rules)[rules_num - 1].start_year = (*rules)[rules_num - 2].start_year + 1;
733 return rules_num;
735 else
736 return 0;
739 #endif
741 static void
742 find_relative_date (TimeZoneDate *buffer)
744 gint wday;
745 GDate date;
746 g_date_clear (&date, 1);
747 wday = buffer->wday;
749 /* Get last day if last is needed, first day otherwise */
750 if (buffer->mon == 13 || buffer->mon == 14) /* Julian Date */
752 g_date_set_dmy (&date, 1, 1, buffer->year);
753 if (wday >= 59 && buffer->mon == 13 && g_date_is_leap_year (buffer->year))
754 g_date_add_days (&date, wday);
755 else
756 g_date_add_days (&date, wday - 1);
757 buffer->mon = (int) g_date_get_month (&date);
758 buffer->mday = (int) g_date_get_day (&date);
759 buffer->wday = 0;
761 else /* M.W.D */
763 guint days;
764 guint days_in_month = g_date_days_in_month (buffer->mon, buffer->year);
765 GDateWeekday first_wday;
767 g_date_set_dmy (&date, 1, buffer->mon, buffer->year);
768 first_wday = g_date_get_weekday (&date);
770 if (first_wday > wday)
771 ++(buffer->week);
772 /* week is 1 <= w <= 5, we need 0-based */
773 days = 7 * (buffer->week - 1) + wday - first_wday;
775 while (days > days_in_month)
776 days -= 7;
778 g_date_add_days (&date, days);
780 buffer->mday = g_date_get_day (&date);
784 /* Offset is previous offset of local time. Returns 0 if month is 0 */
785 static gint64
786 boundary_for_year (TimeZoneDate *boundary,
787 gint year,
788 gint32 offset)
790 TimeZoneDate buffer;
791 GDate date;
792 const guint64 unix_epoch_start = 719163L;
793 const guint64 seconds_per_day = 86400L;
795 if (!boundary->mon)
796 return 0;
797 buffer = *boundary;
799 if (boundary->year == 0)
801 buffer.year = year;
803 if (buffer.wday)
804 find_relative_date (&buffer);
807 g_assert (buffer.year == year);
808 g_date_clear (&date, 1);
809 g_date_set_dmy (&date, buffer.mday, buffer.mon, buffer.year);
810 return ((g_date_get_julian (&date) - unix_epoch_start) * seconds_per_day +
811 buffer.hour * 3600 + buffer.min * 60 + buffer.sec - offset);
814 static void
815 fill_transition_info_from_rule (TransitionInfo *info,
816 TimeZoneRule *rule,
817 gboolean is_dst)
819 gint offset = is_dst ? rule->dlt_offset : rule->std_offset;
820 gchar *name = is_dst ? rule->dlt_name : rule->std_name;
822 info->gmt_offset = offset;
823 info->is_dst = is_dst;
825 if (name)
826 info->abbrev = g_strdup (name);
828 else
829 info->abbrev = g_strdup_printf ("%+03d%02d",
830 (int) offset / 3600,
831 (int) abs (offset / 60) % 60);
834 static void
835 init_zone_from_rules (GTimeZone *gtz,
836 TimeZoneRule *rules,
837 gint rules_num)
839 guint type_count = 0, trans_count = 0, info_index = 0;
840 guint ri; /* rule index */
841 gboolean skip_first_std_trans = TRUE;
842 gint32 last_offset;
844 type_count = 0;
845 trans_count = 0;
847 /* Last rule only contains max year */
848 for (ri = 0; ri < rules_num - 1; ri++)
850 if (rules[ri].dlt_start.mon || rules[ri].dlt_end.mon)
852 guint rulespan = (rules[ri + 1].start_year - rules[ri].start_year);
853 guint transitions = rules[ri].dlt_start.mon > 0 ? 1 : 0;
854 transitions += rules[ri].dlt_end.mon > 0 ? 1 : 0;
855 type_count += rules[ri].dlt_start.mon > 0 ? 2 : 1;
856 trans_count += transitions * rulespan;
858 else
859 type_count++;
862 gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
863 gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
865 last_offset = rules[0].std_offset;
867 for (ri = 0; ri < rules_num - 1; ri++)
869 if ((rules[ri].std_offset || rules[ri].dlt_offset) &&
870 rules[ri].dlt_start.mon == 0 && rules[ri].dlt_end.mon == 0)
872 TransitionInfo std_info;
873 /* Standard */
874 fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
875 g_array_append_val (gtz->t_info, std_info);
877 if (ri > 0 &&
878 ((rules[ri - 1].dlt_start.mon > 12 &&
879 rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
880 rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
882 /* The previous rule was a southern hemisphere rule that
883 starts the year with DST, so we need to add a
884 transition to return to standard time */
885 guint year = rules[ri].start_year;
886 gint64 std_time = boundary_for_year (&rules[ri].dlt_end,
887 year, last_offset);
888 Transition std_trans = {std_time, info_index};
889 g_array_append_val (gtz->transitions, std_trans);
892 last_offset = rules[ri].std_offset;
893 ++info_index;
894 skip_first_std_trans = TRUE;
896 else
898 const guint start_year = rules[ri].start_year;
899 const guint end_year = rules[ri + 1].start_year;
900 gboolean dlt_first;
901 guint year;
902 TransitionInfo std_info, dlt_info;
903 if (rules[ri].dlt_start.mon > 12)
904 dlt_first = rules[ri].dlt_start.wday > rules[ri].dlt_end.wday;
905 else
906 dlt_first = rules[ri].dlt_start.mon > rules[ri].dlt_end.mon;
907 /* Standard rules are always even, because before the first
908 transition is always standard time, and 0 is even. */
909 fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
910 fill_transition_info_from_rule (&dlt_info, &(rules[ri]), TRUE);
912 g_array_append_val (gtz->t_info, std_info);
913 g_array_append_val (gtz->t_info, dlt_info);
915 /* Transition dates. We hope that a year which ends daylight
916 time in a southern-hemisphere country (i.e., one that
917 begins the year in daylight time) will include a rule
918 which has only a dlt_end. */
919 for (year = start_year; year < end_year; year++)
921 gint32 dlt_offset = (dlt_first ? last_offset :
922 rules[ri].dlt_offset);
923 gint32 std_offset = (dlt_first ? rules[ri].std_offset :
924 last_offset);
925 /* NB: boundary_for_year returns 0 if mon == 0 */
926 gint64 std_time = boundary_for_year (&rules[ri].dlt_end,
927 year, dlt_offset);
928 gint64 dlt_time = boundary_for_year (&rules[ri].dlt_start,
929 year, std_offset);
930 Transition std_trans = {std_time, info_index};
931 Transition dlt_trans = {dlt_time, info_index + 1};
932 last_offset = (dlt_first ? rules[ri].dlt_offset :
933 rules[ri].std_offset);
934 if (dlt_first)
936 if (skip_first_std_trans)
937 skip_first_std_trans = FALSE;
938 else if (std_time)
939 g_array_append_val (gtz->transitions, std_trans);
940 if (dlt_time)
941 g_array_append_val (gtz->transitions, dlt_trans);
943 else
945 if (dlt_time)
946 g_array_append_val (gtz->transitions, dlt_trans);
947 if (std_time)
948 g_array_append_val (gtz->transitions, std_trans);
952 info_index += 2;
955 if (ri > 0 &&
956 ((rules[ri - 1].dlt_start.mon > 12 &&
957 rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
958 rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
960 /* The previous rule was a southern hemisphere rule that
961 starts the year with DST, so we need to add a
962 transition to return to standard time */
963 TransitionInfo info;
964 guint year = rules[ri].start_year;
965 Transition trans;
966 fill_transition_info_from_rule (&info, &(rules[ri - 1]), FALSE);
967 g_array_append_val (gtz->t_info, info);
968 trans.time = boundary_for_year (&rules[ri - 1].dlt_end,
969 year, last_offset);
970 trans.info_index = info_index;
971 g_array_append_val (gtz->transitions, trans);
976 * parses date[/time] for parsing TZ environment variable
978 * date is either Mm.w.d, Jn or N
979 * - m is 1 to 12
980 * - w is 1 to 5
981 * - d is 0 to 6
982 * - n is 1 to 365
983 * - N is 0 to 365
985 * time is either h or hh[[:]mm[[[:]ss]]]
986 * - h[h] is 0 to 23
987 * - mm is 00 to 59
988 * - ss is 00 to 59
990 static gboolean
991 parse_mwd_boundary (gchar **pos, TimeZoneDate *boundary)
993 gint month, week, day;
995 if (**pos == '\0' || **pos < '0' || '9' < **pos)
996 return FALSE;
998 month = *(*pos)++ - '0';
1000 if ((month == 1 && **pos >= '0' && '2' >= **pos) ||
1001 (month == 0 && **pos >= '0' && '9' >= **pos))
1003 month *= 10;
1004 month += *(*pos)++ - '0';
1007 if (*(*pos)++ != '.' || month == 0)
1008 return FALSE;
1010 if (**pos == '\0' || **pos < '1' || '5' < **pos)
1011 return FALSE;
1013 week = *(*pos)++ - '0';
1015 if (*(*pos)++ != '.')
1016 return FALSE;
1018 if (**pos == '\0' || **pos < '0' || '6' < **pos)
1019 return FALSE;
1021 day = *(*pos)++ - '0';
1023 if (!day)
1024 day += 7;
1026 boundary->year = 0;
1027 boundary->mon = month;
1028 boundary->week = week;
1029 boundary->wday = day;
1030 return TRUE;
1033 /* Different implementations of tzset interpret the Julian day field
1034 differently. For example, Linux specifies that it should be 1-based
1035 (1 Jan is JD 1) for both Jn and n formats, while zOS and BSD
1036 specify that a Jn JD is 1-based while an n JD is 0-based. Rather
1037 than trying to follow different specs, we will follow GDate's
1038 practice thatIn order to keep it simple, we will follow Linux's
1039 practice. */
1041 static gboolean
1042 parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
1043 gboolean ignore_leap)
1045 gint day = 0;
1046 GDate date;
1048 while (**pos >= '0' && '9' >= **pos)
1050 day *= 10;
1051 day += *(*pos)++ - '0';
1054 if (day < 1 || 365 < day)
1055 return FALSE;
1057 g_date_clear (&date, 1);
1058 g_date_set_julian (&date, day);
1059 boundary->year = 0;
1060 boundary->mon = (int) g_date_get_month (&date);
1061 boundary->mday = (int) g_date_get_day (&date);
1062 boundary->wday = 0;
1064 if (!ignore_leap && day >= 59)
1065 boundary->mday++;
1067 return TRUE;
1070 static gboolean
1071 parse_tz_boundary (const gchar *identifier,
1072 TimeZoneDate *boundary)
1074 gchar *pos;
1076 pos = (gchar*)identifier;
1077 /* Month-week-weekday */
1078 if (*pos == 'M')
1080 ++pos;
1081 if (!parse_mwd_boundary (&pos, boundary))
1082 return FALSE;
1084 /* Julian date which ignores Feb 29 in leap years */
1085 else if (*pos == 'J')
1087 ++pos;
1088 if (!parse_julian_boundary (&pos, boundary, FALSE))
1089 return FALSE ;
1091 /* Julian date which counts Feb 29 in leap years */
1092 else if (*pos >= '0' && '9' >= *pos)
1094 if (!parse_julian_boundary (&pos, boundary, TRUE))
1095 return FALSE;
1097 else
1098 return FALSE;
1100 /* Time */
1102 if (*pos == '/')
1104 gint32 offset;
1106 if (!parse_time (++pos, &offset))
1107 return FALSE;
1109 boundary->hour = offset / 3600;
1110 boundary->min = (offset / 60) % 60;
1111 boundary->sec = offset % 3600;
1113 return TRUE;
1116 else
1118 boundary->hour = 2;
1119 boundary->min = 0;
1120 boundary->sec = 0;
1122 return *pos == '\0';
1126 static gint
1127 create_ruleset_from_rule (TimeZoneRule **rules, TimeZoneRule *rule)
1129 *rules = g_new0 (TimeZoneRule, 2);
1131 (*rules)[0].start_year = MIN_TZYEAR;
1132 (*rules)[1].start_year = MAX_TZYEAR;
1134 (*rules)[0].std_offset = -rule->std_offset;
1135 (*rules)[0].dlt_offset = -rule->dlt_offset;
1136 (*rules)[0].dlt_start = rule->dlt_start;
1137 (*rules)[0].dlt_end = rule->dlt_end;
1138 strcpy ((*rules)[0].std_name, rule->std_name);
1139 strcpy ((*rules)[0].dlt_name, rule->dlt_name);
1140 return 2;
1143 static gboolean
1144 parse_offset (gchar **pos, gint32 *target)
1146 gchar *buffer;
1147 gchar *target_pos = *pos;
1148 gboolean ret;
1150 while (**pos == '+' || **pos == '-' || **pos == ':' ||
1151 (**pos >= '0' && '9' >= **pos))
1152 ++(*pos);
1154 buffer = g_strndup (target_pos, *pos - target_pos);
1155 ret = parse_constant_offset (buffer, target);
1156 g_free (buffer);
1158 return ret;
1161 static gboolean
1162 parse_identifier_boundary (gchar **pos, TimeZoneDate *target)
1164 gchar *buffer;
1165 gchar *target_pos = *pos;
1166 gboolean ret;
1168 while (**pos != ',' && **pos != '\0')
1169 ++(*pos);
1170 buffer = g_strndup (target_pos, *pos - target_pos);
1171 ret = parse_tz_boundary (buffer, target);
1172 g_free (buffer);
1174 return ret;
1177 static gboolean
1178 set_tz_name (gchar **pos, gchar *buffer, guint size)
1180 gchar *name_pos = *pos;
1181 guint len;
1183 /* Name is ASCII alpha (Is this necessarily true?) */
1184 while (g_ascii_isalpha (**pos))
1185 ++(*pos);
1187 /* Name should be three or more alphabetic characters */
1188 if (*pos - name_pos < 3)
1189 return FALSE;
1191 memset (buffer, 0, NAME_SIZE);
1192 /* name_pos isn't 0-terminated, so we have to limit the length expressly */
1193 len = *pos - name_pos > size - 1 ? size - 1 : *pos - name_pos;
1194 strncpy (buffer, name_pos, len);
1195 return TRUE;
1198 static gboolean
1199 parse_identifier_boundaries (gchar **pos, TimeZoneRule *tzr)
1201 if (*(*pos)++ != ',')
1202 return FALSE;
1204 /* Start date */
1205 if (!parse_identifier_boundary (pos, &(tzr->dlt_start)) || *(*pos)++ != ',')
1206 return FALSE;
1208 /* End date */
1209 if (!parse_identifier_boundary (pos, &(tzr->dlt_end)))
1210 return FALSE;
1211 return TRUE;
1215 * Creates an array of TimeZoneRule from a TZ environment variable
1216 * type of identifier. Should free rules afterwards
1218 static gint
1219 rules_from_identifier (const gchar *identifier,
1220 TimeZoneRule **rules)
1222 gchar *pos;
1223 TimeZoneRule tzr;
1225 if (!identifier)
1226 return 0;
1228 pos = (gchar*)identifier;
1229 memset (&tzr, 0, sizeof (tzr));
1230 /* Standard offset */
1231 if (!(set_tz_name (&pos, tzr.std_name, NAME_SIZE)) ||
1232 !parse_offset (&pos, &(tzr.std_offset)))
1233 return 0;
1235 if (*pos == 0)
1236 return create_ruleset_from_rule (rules, &tzr);
1238 /* Format 2 */
1239 if (!(set_tz_name (&pos, tzr.dlt_name, NAME_SIZE)))
1240 return 0;
1241 parse_offset (&pos, &(tzr.dlt_offset));
1242 if (tzr.dlt_offset == 0) /* No daylight offset given, assume it's 1
1243 hour earlier that standard */
1244 tzr.dlt_offset = tzr.std_offset - 3600;
1245 if (*pos == '\0')
1246 #ifdef G_OS_WIN32
1247 /* Windows allows us to use the US DST boundaries if they're not given */
1249 int i;
1250 guint rules_num = 0;
1252 /* Use US rules, Windows' default is Pacific Standard Time */
1253 if ((rules_num = rules_from_windows_time_zone ("Pacific Standard Time",
1254 rules)))
1256 for (i = 0; i < rules_num - 1; i++)
1258 (*rules)[i].std_offset = - tzr.std_offset;
1259 (*rules)[i].dlt_offset = - tzr.dlt_offset;
1260 strcpy ((*rules)[i].std_name, tzr.std_name);
1261 strcpy ((*rules)[i].dlt_name, tzr.dlt_name);
1264 return rules_num;
1266 else
1267 return 0;
1269 #else
1270 return 0;
1271 #endif
1272 /* Start and end required (format 2) */
1273 if (!parse_identifier_boundaries (&pos, &tzr))
1274 return 0;
1276 return create_ruleset_from_rule (rules, &tzr);
1279 /* Construction {{{1 */
1281 * g_time_zone_new:
1282 * @identifier: (nullable): a timezone identifier
1284 * Creates a #GTimeZone corresponding to @identifier.
1286 * @identifier can either be an RFC3339/ISO 8601 time offset or
1287 * something that would pass as a valid value for the `TZ` environment
1288 * variable (including %NULL).
1290 * In Windows, @identifier can also be the unlocalized name of a time
1291 * zone for standard time, for example "Pacific Standard Time".
1293 * Valid RFC3339 time offsets are `"Z"` (for UTC) or
1294 * `"±hh:mm"`. ISO 8601 additionally specifies
1295 * `"±hhmm"` and `"±hh"`. Offsets are
1296 * time values to be added to Coordinated Universal Time (UTC) to get
1297 * the local time.
1299 * In UNIX, the `TZ` environment variable typically corresponds
1300 * to the name of a file in the zoneinfo database, or string in
1301 * "std offset [dst [offset],start[/time],end[/time]]" (POSIX) format.
1302 * There are no spaces in the specification. The name of standard
1303 * and daylight savings time zone must be three or more alphabetic
1304 * characters. Offsets are time values to be added to local time to
1305 * get Coordinated Universal Time (UTC) and should be
1306 * `"[±]hh[[:]mm[:ss]]"`. Dates are either
1307 * `"Jn"` (Julian day with n between 1 and 365, leap
1308 * years not counted), `"n"` (zero-based Julian day
1309 * with n between 0 and 365) or `"Mm.w.d"` (day d
1310 * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
1311 * 0 is a Sunday). Times are in local wall clock time, the default is
1312 * 02:00:00.
1314 * In Windows, the "tzn[+|–]hh[:mm[:ss]][dzn]" format is used, but also
1315 * accepts POSIX format. The Windows format uses US rules for all time
1316 * zones; daylight savings time is 60 minutes behind the standard time
1317 * with date and time of change taken from Pacific Standard Time.
1318 * Offsets are time values to be added to the local time to get
1319 * Coordinated Universal Time (UTC).
1321 * g_time_zone_new_local() calls this function with the value of the
1322 * `TZ` environment variable. This function itself is independent of
1323 * the value of `TZ`, but if @identifier is %NULL then `/etc/localtime`
1324 * will be consulted to discover the correct time zone on UNIX and the
1325 * registry will be consulted or GetTimeZoneInformation() will be used
1326 * to get the local time zone on Windows.
1328 * If intervals are not available, only time zone rules from `TZ`
1329 * environment variable or other means, then they will be computed
1330 * from year 1900 to 2037. If the maximum year for the rules is
1331 * available and it is greater than 2037, then it will followed
1332 * instead.
1334 * See
1335 * [RFC3339 §5.6](http://tools.ietf.org/html/rfc3339#section-5.6)
1336 * for a precise definition of valid RFC3339 time offsets
1337 * (the `time-offset` expansion) and ISO 8601 for the
1338 * full list of valid time offsets. See
1339 * [The GNU C Library manual](http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html)
1340 * for an explanation of the possible
1341 * values of the `TZ` environment variable. See
1342 * [Microsoft Time Zone Index Values](http://msdn.microsoft.com/en-us/library/ms912391%28v=winembedded.11%29.aspx)
1343 * for the list of time zones on Windows.
1345 * You should release the return value by calling g_time_zone_unref()
1346 * when you are done with it.
1348 * Returns: the requested timezone
1350 * Since: 2.26
1352 GTimeZone *
1353 g_time_zone_new (const gchar *identifier)
1355 GTimeZone *tz = NULL;
1356 TimeZoneRule *rules;
1357 gint rules_num;
1359 G_LOCK (time_zones);
1360 if (time_zones == NULL)
1361 time_zones = g_hash_table_new (g_str_hash, g_str_equal);
1363 if (identifier)
1365 tz = g_hash_table_lookup (time_zones, identifier);
1366 if (tz)
1368 g_atomic_int_inc (&tz->ref_count);
1369 G_UNLOCK (time_zones);
1370 return tz;
1374 tz = g_slice_new0 (GTimeZone);
1375 tz->name = g_strdup (identifier);
1376 tz->ref_count = 0;
1378 zone_for_constant_offset (tz, identifier);
1380 if (tz->t_info == NULL &&
1381 (rules_num = rules_from_identifier (identifier, &rules)))
1383 init_zone_from_rules (tz, rules, rules_num);
1384 g_free (rules);
1387 if (tz->t_info == NULL)
1389 #ifdef G_OS_UNIX
1390 GBytes *zoneinfo = zone_info_unix (identifier);
1391 if (!zoneinfo)
1392 zone_for_constant_offset (tz, "UTC");
1393 else
1395 init_zone_from_iana_info (tz, zoneinfo);
1396 g_bytes_unref (zoneinfo);
1398 #elif defined (G_OS_WIN32)
1399 if ((rules_num = rules_from_windows_time_zone (identifier, &rules)))
1401 init_zone_from_rules (tz, rules, rules_num);
1402 g_free (rules);
1406 if (tz->t_info == NULL)
1408 if (identifier)
1409 zone_for_constant_offset (tz, "UTC");
1410 else
1412 TIME_ZONE_INFORMATION tzi;
1414 if (GetTimeZoneInformation (&tzi) != TIME_ZONE_ID_INVALID)
1416 rules = g_new0 (TimeZoneRule, 2);
1418 rule_from_windows_time_zone_info (&rules[0], &tzi);
1420 memset (rules[0].std_name, 0, NAME_SIZE);
1421 memset (rules[0].dlt_name, 0, NAME_SIZE);
1423 rules[0].start_year = MIN_TZYEAR;
1424 rules[1].start_year = MAX_TZYEAR;
1426 init_zone_from_rules (tz, rules, 2);
1428 g_free (rules);
1431 #endif
1434 if (tz->t_info != NULL)
1436 if (identifier)
1437 g_hash_table_insert (time_zones, tz->name, tz);
1439 g_atomic_int_inc (&tz->ref_count);
1440 G_UNLOCK (time_zones);
1442 return tz;
1446 * g_time_zone_new_utc:
1448 * Creates a #GTimeZone corresponding to UTC.
1450 * This is equivalent to calling g_time_zone_new() with a value like
1451 * "Z", "UTC", "+00", etc.
1453 * You should release the return value by calling g_time_zone_unref()
1454 * when you are done with it.
1456 * Returns: the universal timezone
1458 * Since: 2.26
1460 GTimeZone *
1461 g_time_zone_new_utc (void)
1463 return g_time_zone_new ("UTC");
1467 * g_time_zone_new_local:
1469 * Creates a #GTimeZone corresponding to local time. The local time
1470 * zone may change between invocations to this function; for example,
1471 * if the system administrator changes it.
1473 * This is equivalent to calling g_time_zone_new() with the value of
1474 * the `TZ` environment variable (including the possibility of %NULL).
1476 * You should release the return value by calling g_time_zone_unref()
1477 * when you are done with it.
1479 * Returns: the local timezone
1481 * Since: 2.26
1483 GTimeZone *
1484 g_time_zone_new_local (void)
1486 return g_time_zone_new (getenv ("TZ"));
1489 #define TRANSITION(n) g_array_index (tz->transitions, Transition, n)
1490 #define TRANSITION_INFO(n) g_array_index (tz->t_info, TransitionInfo, n)
1492 /* Internal helpers {{{1 */
1493 /* NB: Interval 0 is before the first transition, so there's no
1494 * transition structure to point to which TransitionInfo to
1495 * use. Rule-based zones are set up so that TI 0 is always standard
1496 * time (which is what's in effect before Daylight time got started
1497 * in the early 20th century), but IANA tzfiles don't follow that
1498 * convention. The tzfile documentation says to use the first
1499 * standard-time (i.e., non-DST) tinfo, so that's what we do.
1501 inline static const TransitionInfo*
1502 interval_info (GTimeZone *tz,
1503 guint interval)
1505 guint index;
1506 g_return_val_if_fail (tz->t_info != NULL, NULL);
1507 if (interval && tz->transitions && interval <= tz->transitions->len)
1508 index = (TRANSITION(interval - 1)).info_index;
1509 else
1511 for (index = 0; index < tz->t_info->len; index++)
1513 TransitionInfo *tzinfo = &(TRANSITION_INFO(index));
1514 if (!tzinfo->is_dst)
1515 return tzinfo;
1517 index = 0;
1520 return &(TRANSITION_INFO(index));
1523 inline static gint64
1524 interval_start (GTimeZone *tz,
1525 guint interval)
1527 if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
1528 return G_MININT64;
1529 if (interval > tz->transitions->len)
1530 interval = tz->transitions->len;
1531 return (TRANSITION(interval - 1)).time;
1534 inline static gint64
1535 interval_end (GTimeZone *tz,
1536 guint interval)
1538 if (tz->transitions && interval < tz->transitions->len)
1539 return (TRANSITION(interval)).time - 1;
1540 return G_MAXINT64;
1543 inline static gint32
1544 interval_offset (GTimeZone *tz,
1545 guint interval)
1547 g_return_val_if_fail (tz->t_info != NULL, 0);
1548 return interval_info (tz, interval)->gmt_offset;
1551 inline static gboolean
1552 interval_isdst (GTimeZone *tz,
1553 guint interval)
1555 g_return_val_if_fail (tz->t_info != NULL, 0);
1556 return interval_info (tz, interval)->is_dst;
1560 inline static gchar*
1561 interval_abbrev (GTimeZone *tz,
1562 guint interval)
1564 g_return_val_if_fail (tz->t_info != NULL, 0);
1565 return interval_info (tz, interval)->abbrev;
1568 inline static gint64
1569 interval_local_start (GTimeZone *tz,
1570 guint interval)
1572 if (interval)
1573 return interval_start (tz, interval) + interval_offset (tz, interval);
1575 return G_MININT64;
1578 inline static gint64
1579 interval_local_end (GTimeZone *tz,
1580 guint interval)
1582 if (tz->transitions && interval < tz->transitions->len)
1583 return interval_end (tz, interval) + interval_offset (tz, interval);
1585 return G_MAXINT64;
1588 static gboolean
1589 interval_valid (GTimeZone *tz,
1590 guint interval)
1592 if ( tz->transitions == NULL)
1593 return interval == 0;
1594 return interval <= tz->transitions->len;
1597 /* g_time_zone_find_interval() {{{1 */
1600 * g_time_zone_adjust_time:
1601 * @tz: a #GTimeZone
1602 * @type: the #GTimeType of @time_
1603 * @time_: a pointer to a number of seconds since January 1, 1970
1605 * Finds an interval within @tz that corresponds to the given @time_,
1606 * possibly adjusting @time_ if required to fit into an interval.
1607 * The meaning of @time_ depends on @type.
1609 * This function is similar to g_time_zone_find_interval(), with the
1610 * difference that it always succeeds (by making the adjustments
1611 * described below).
1613 * In any of the cases where g_time_zone_find_interval() succeeds then
1614 * this function returns the same value, without modifying @time_.
1616 * This function may, however, modify @time_ in order to deal with
1617 * non-existent times. If the non-existent local @time_ of 02:30 were
1618 * requested on March 14th 2010 in Toronto then this function would
1619 * adjust @time_ to be 03:00 and return the interval containing the
1620 * adjusted time.
1622 * Returns: the interval containing @time_, never -1
1624 * Since: 2.26
1626 gint
1627 g_time_zone_adjust_time (GTimeZone *tz,
1628 GTimeType type,
1629 gint64 *time_)
1631 gint i;
1632 guint intervals;
1634 if (tz->transitions == NULL)
1635 return 0;
1637 intervals = tz->transitions->len;
1639 /* find the interval containing *time UTC
1640 * TODO: this could be binary searched (or better) */
1641 for (i = 0; i <= intervals; i++)
1642 if (*time_ <= interval_end (tz, i))
1643 break;
1645 g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
1647 if (type != G_TIME_TYPE_UNIVERSAL)
1649 if (*time_ < interval_local_start (tz, i))
1650 /* if time came before the start of this interval... */
1652 i--;
1654 /* if it's not in the previous interval... */
1655 if (*time_ > interval_local_end (tz, i))
1657 /* it doesn't exist. fast-forward it. */
1658 i++;
1659 *time_ = interval_local_start (tz, i);
1663 else if (*time_ > interval_local_end (tz, i))
1664 /* if time came after the end of this interval... */
1666 i++;
1668 /* if it's not in the next interval... */
1669 if (*time_ < interval_local_start (tz, i))
1670 /* it doesn't exist. fast-forward it. */
1671 *time_ = interval_local_start (tz, i);
1674 else if (interval_isdst (tz, i) != type)
1675 /* it's in this interval, but dst flag doesn't match.
1676 * check neighbours for a better fit. */
1678 if (i && *time_ <= interval_local_end (tz, i - 1))
1679 i--;
1681 else if (i < intervals &&
1682 *time_ >= interval_local_start (tz, i + 1))
1683 i++;
1687 return i;
1691 * g_time_zone_find_interval:
1692 * @tz: a #GTimeZone
1693 * @type: the #GTimeType of @time_
1694 * @time_: a number of seconds since January 1, 1970
1696 * Finds an the interval within @tz that corresponds to the given @time_.
1697 * The meaning of @time_ depends on @type.
1699 * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
1700 * succeed (since universal time is monotonic and continuous).
1702 * Otherwise @time_ is treated as local time. The distinction between
1703 * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
1704 * the case that the given @time_ is ambiguous. In Toronto, for example,
1705 * 01:30 on November 7th 2010 occurred twice (once inside of daylight
1706 * savings time and the next, an hour later, outside of daylight savings
1707 * time). In this case, the different value of @type would result in a
1708 * different interval being returned.
1710 * It is still possible for this function to fail. In Toronto, for
1711 * example, 02:00 on March 14th 2010 does not exist (due to the leap
1712 * forward to begin daylight savings time). -1 is returned in that
1713 * case.
1715 * Returns: the interval containing @time_, or -1 in case of failure
1717 * Since: 2.26
1719 gint
1720 g_time_zone_find_interval (GTimeZone *tz,
1721 GTimeType type,
1722 gint64 time_)
1724 gint i;
1725 guint intervals;
1727 if (tz->transitions == NULL)
1728 return 0;
1729 intervals = tz->transitions->len;
1730 for (i = 0; i <= intervals; i++)
1731 if (time_ <= interval_end (tz, i))
1732 break;
1734 if (type == G_TIME_TYPE_UNIVERSAL)
1735 return i;
1737 if (time_ < interval_local_start (tz, i))
1739 if (time_ > interval_local_end (tz, --i))
1740 return -1;
1743 else if (time_ > interval_local_end (tz, i))
1745 if (time_ < interval_local_start (tz, ++i))
1746 return -1;
1749 else if (interval_isdst (tz, i) != type)
1751 if (i && time_ <= interval_local_end (tz, i - 1))
1752 i--;
1754 else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
1755 i++;
1758 return i;
1761 /* Public API accessors {{{1 */
1764 * g_time_zone_get_abbreviation:
1765 * @tz: a #GTimeZone
1766 * @interval: an interval within the timezone
1768 * Determines the time zone abbreviation to be used during a particular
1769 * @interval of time in the time zone @tz.
1771 * For example, in Toronto this is currently "EST" during the winter
1772 * months and "EDT" during the summer months when daylight savings time
1773 * is in effect.
1775 * Returns: the time zone abbreviation, which belongs to @tz
1777 * Since: 2.26
1779 const gchar *
1780 g_time_zone_get_abbreviation (GTimeZone *tz,
1781 gint interval)
1783 g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
1785 return interval_abbrev (tz, (guint)interval);
1789 * g_time_zone_get_offset:
1790 * @tz: a #GTimeZone
1791 * @interval: an interval within the timezone
1793 * Determines the offset to UTC in effect during a particular @interval
1794 * of time in the time zone @tz.
1796 * The offset is the number of seconds that you add to UTC time to
1797 * arrive at local time for @tz (ie: negative numbers for time zones
1798 * west of GMT, positive numbers for east).
1800 * Returns: the number of seconds that should be added to UTC to get the
1801 * local time in @tz
1803 * Since: 2.26
1805 gint32
1806 g_time_zone_get_offset (GTimeZone *tz,
1807 gint interval)
1809 g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
1811 return interval_offset (tz, (guint)interval);
1815 * g_time_zone_is_dst:
1816 * @tz: a #GTimeZone
1817 * @interval: an interval within the timezone
1819 * Determines if daylight savings time is in effect during a particular
1820 * @interval of time in the time zone @tz.
1822 * Returns: %TRUE if daylight savings time is in effect
1824 * Since: 2.26
1826 gboolean
1827 g_time_zone_is_dst (GTimeZone *tz,
1828 gint interval)
1830 g_return_val_if_fail (interval_valid (tz, interval), FALSE);
1832 if (tz->transitions == NULL)
1833 return FALSE;
1835 return interval_isdst (tz, (guint)interval);
1838 /* Epilogue {{{1 */
1839 /* vim:set foldmethod=marker: */