1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /*======================================================================
4 CREATOR: Damon Chaplin 15 March 2001
6 $Id: icaltimezone.c,v 1.44 2008-02-03 16:10:46 dothebart Exp $
9 (C) COPYRIGHT 2001, Damon Chaplin
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of either:
14 The LGPL as published by the Free Software Foundation, version
15 2.1, available at: http://www.fsf.org/copyleft/lesser.html
19 The Mozilla Public License Version 1.0. You may obtain a copy of
20 the License at http://www.mozilla.org/MPL/
23 ======================================================================*/
25 /** @file icaltimezone.c
26 * @brief implementation of timezone handling routines
38 #include "icalproperty.h"
39 #include "icalarray.h"
40 #include "icalerror.h"
41 #include "icalparser.h"
42 #include "icaltimezone.h"
43 #include "icaltimezoneimpl.h"
44 #include "icaltz-util.h"
50 static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER;
58 /* Undef the similar macro from pthread.h, it doesn't check if
59 * gmtime() returns NULL.
63 /* The gmtime() in Microsoft's C library is MT-safe */
64 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
66 // MSVC lacks the POSIX macro S_ISDIR, however it's a trivial one:
68 #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
73 #define snprintf _snprintf
74 #define strcasecmp stricmp
77 /** This is the toplevel directory where the timezone data is installed in. */
78 #define ZONEINFO_DIRECTORY PACKAGE_DATA_DIR "/zoneinfo"
80 /** The prefix we use to uniquely identify TZIDs.
81 It must begin and end with forward slashes.
83 const char *ical_tzid_prefix = "/freeassociation.sourceforge.net/";
85 /** This is the filename of the file containing the city names and
86 coordinates of all the builtin timezones. */
87 #define ZONES_TAB_FILENAME "zones.tab"
89 /** This is the number of years of extra coverage we do when expanding
90 the timezone changes. */
91 #define ICALTIMEZONE_EXTRA_COVERAGE 5
93 /** This is the maximum year we will expand to. time_t values only go up to
94 somewhere around 2037. */
95 #define ICALTIMEZONE_MAX_YEAR 2035
97 typedef struct _icaltimezonechange icaltimezonechange;
99 struct _icaltimezonechange {
101 /**< The offset to add to UTC to get local time, in seconds. */
104 /**< The offset to add to UTC, before this change, in seconds. */
106 int year; /**< Actual year, e.g. 2001. */
107 int month; /**< 1 (Jan) to 12 (Dec). */
112 /**< The time that the change came into effect, in UTC.
113 Note that the prev_utc_offset applies to this local time,
114 since we haven't changed to the new offset yet. */
117 /**< Whether this is STANDARD or DAYLIGHT time. */
121 /** An array of icaltimezones for the builtin timezones. */
122 static icalarray *builtin_timezones = NULL;
124 /** This is the special UTC timezone, which isn't in builtin_timezones. */
125 static icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
127 static char* zone_files_directory = NULL;
129 static void icaltimezone_reset (icaltimezone *zone);
130 static char* icaltimezone_get_location_from_vtimezone (icalcomponent *component);
131 static char* icaltimezone_get_tznames_from_vtimezone (icalcomponent *component);
132 static void icaltimezone_expand_changes (icaltimezone *zone,
134 static void icaltimezone_expand_vtimezone (icalcomponent *comp,
137 static int icaltimezone_compare_change_fn (const void *elem1,
140 static int icaltimezone_find_nearby_change (icaltimezone *zone,
141 icaltimezonechange *change);
143 static void icaltimezone_adjust_change (icaltimezonechange *tt,
149 static void icaltimezone_init (icaltimezone *zone);
151 /** Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from the
152 VTIMEZONE component and places them in the icaltimezone. It returns 1 on
153 success, or 0 if the TZID can't be found. */
154 static int icaltimezone_get_vtimezone_properties (icaltimezone *zone,
155 icalcomponent *component);
158 static void icaltimezone_load_builtin_timezone (icaltimezone *zone);
160 static void icaltimezone_ensure_coverage (icaltimezone *zone,
164 static void icaltimezone_init_builtin_timezones(void);
166 static void icaltimezone_parse_zone_tab (void);
168 #ifdef USE_BUILTIN_TZDATA
169 static char* icaltimezone_load_get_line_fn (char *s,
174 static void format_utc_offset (int utc_offset,
176 static const char* get_zone_directory(void);
178 /** Creates a new icaltimezone. */
180 icaltimezone_new (void)
184 zone = (icaltimezone*) malloc (sizeof (icaltimezone));
186 icalerror_set_errno (ICAL_NEWFAILED_ERROR);
190 icaltimezone_init (zone);
196 icaltimezone_copy (icaltimezone *originalzone)
200 zone = (icaltimezone*) malloc (sizeof (icaltimezone));
202 icalerror_set_errno (ICAL_NEWFAILED_ERROR);
206 memcpy (zone, originalzone, sizeof (icaltimezone));
207 if (zone->tzid != NULL)
208 zone->tzid = strdup (zone->tzid);
209 if (zone->location != NULL)
210 zone->location = strdup (zone->location);
211 if (zone->tznames != NULL)
212 zone->tznames = strdup (zone->tznames);
213 if (zone->changes != NULL)
214 zone->changes = icalarray_copy(zone->changes);
216 /* Let the caller set the component because then they will
217 know to be careful not to free this reference twice. */
218 zone->component = NULL;
223 /** Frees all memory used for the icaltimezone. */
225 icaltimezone_free (icaltimezone *zone,
228 icaltimezone_reset (zone);
234 /** Resets the icaltimezone to the initial state, freeing most of the fields. */
236 icaltimezone_reset (icaltimezone *zone)
241 free (zone->location);
243 free (zone->tznames);
245 icalcomponent_free (zone->component);
247 icalarray_free (zone->changes);
249 icaltimezone_init (zone);
253 /** Initializes an icaltimezone. */
255 icaltimezone_init (icaltimezone *zone)
258 zone->location = NULL;
259 zone->tznames = NULL;
260 zone->latitude = 0.0;
261 zone->longitude = 0.0;
262 zone->component = NULL;
263 zone->builtin_timezone = NULL;
265 zone->changes = NULL;
269 /** Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of
270 the VTIMEZONE component and stores them in the icaltimezone. It
271 returns 1 on success, or 0 if the TZID can't be found. Note that
272 it expects the zone to be initialized or reset - it doesn't free
275 icaltimezone_get_vtimezone_properties (icaltimezone *zone,
276 icalcomponent *component)
281 prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
285 /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
286 tzid = icalproperty_get_tzid (prop);
290 if (zone->tzid) free(zone->tzid);
291 zone->tzid = strdup (tzid);
293 if (zone->component) icalcomponent_free(zone->component);
294 zone->component = component;
296 if (zone->location) free(zone->location);
297 zone->location = icaltimezone_get_location_from_vtimezone (component);
299 if (zone->tznames) free(zone->tznames);
300 zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
305 /** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
307 icaltimezone_get_location_from_vtimezone (icalcomponent *component)
310 const char *location;
313 prop = icalcomponent_get_first_property (component,
314 ICAL_LOCATION_PROPERTY);
316 location = icalproperty_get_location (prop);
318 return strdup (location);
321 prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
323 name = icalproperty_get_x_name (prop);
324 if (name && !strcasecmp (name, "X-LIC-LOCATION")) {
325 location = icalproperty_get_x (prop);
327 return strdup (location);
329 prop = icalcomponent_get_next_property (component,
337 /** Gets the TZNAMEs used for the last STANDARD & DAYLIGHT components
338 in a VTIMEZONE. If both STANDARD and DAYLIGHT components use the
339 same TZNAME, it returns that. If they use different TZNAMEs, it
340 formats them like "EST/EDT". The returned string should be freed by
343 icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
346 icalcomponent_kind type;
348 struct icaltimetype dtstart;
349 struct icaldatetimeperiodtype rdate;
350 const char *current_tzname;
351 const char *standard_tzname = NULL, *daylight_tzname = NULL;
352 struct icaltimetype standard_max_date, daylight_max_date;
353 struct icaltimetype current_max_date;
355 standard_max_date = icaltime_null_time();
356 daylight_max_date = icaltime_null_time();
358 /* Step through the STANDARD & DAYLIGHT subcomponents. */
359 comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
361 type = icalcomponent_isa (comp);
362 if (type == ICAL_XSTANDARD_COMPONENT
363 || type == ICAL_XDAYLIGHT_COMPONENT) {
364 current_max_date = icaltime_null_time ();
365 current_tzname = NULL;
367 /* Step through the properties. We want to find the TZNAME, and
368 the largest DTSTART or RDATE. */
369 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
371 switch (icalproperty_isa (prop)) {
372 case ICAL_TZNAME_PROPERTY:
373 current_tzname = icalproperty_get_tzname (prop);
376 case ICAL_DTSTART_PROPERTY:
377 dtstart = icalproperty_get_dtstart (prop);
378 if (icaltime_compare (dtstart, current_max_date) > 0)
379 current_max_date = dtstart;
383 case ICAL_RDATE_PROPERTY:
384 rdate = icalproperty_get_rdate (prop);
385 if (icaltime_compare (rdate.time, current_max_date) > 0)
386 current_max_date = rdate.time;
394 prop = icalcomponent_get_next_property (comp,
398 if (current_tzname) {
399 if (type == ICAL_XSTANDARD_COMPONENT) {
401 || icaltime_compare (current_max_date,
402 standard_max_date) > 0) {
403 standard_max_date = current_max_date;
404 standard_tzname = current_tzname;
408 || icaltime_compare (current_max_date,
409 daylight_max_date) > 0) {
410 daylight_max_date = current_max_date;
411 daylight_tzname = current_tzname;
417 comp = icalcomponent_get_next_component (component,
421 /* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
422 strings, which is totally useless. So we return NULL in that case. */
423 if (standard_tzname && !strcmp (standard_tzname, "Standard Time"))
426 /* If both standard and daylight TZNAMEs were found, if they are the same
427 we return just one, else we format them like "EST/EDT". */
428 if (standard_tzname && daylight_tzname) {
429 size_t standard_len, daylight_len;
432 if (!strcmp (standard_tzname, daylight_tzname))
433 return strdup (standard_tzname);
435 standard_len = strlen (standard_tzname);
436 daylight_len = strlen (daylight_tzname);
437 tznames = malloc (standard_len + daylight_len + 2);
438 strcpy (tznames, standard_tzname);
439 tznames[standard_len] = '/';
440 strcpy (tznames + standard_len + 1, daylight_tzname);
445 /* If either of the TZNAMEs was found just return that, else NULL. */
446 tznames = standard_tzname ? standard_tzname : daylight_tzname;
447 return tznames ? strdup (tznames) : NULL;
453 icaltimezone_ensure_coverage (icaltimezone *zone,
456 /* When we expand timezone changes we always expand at least up to this
457 year, plus ICALTIMEZONE_EXTRA_COVERAGE. */
458 static int icaltimezone_minimum_expansion_year = -1;
460 int changes_end_year;
462 icaltimezone_load_builtin_timezone (zone);
464 if (icaltimezone_minimum_expansion_year == -1) {
465 struct icaltimetype today = icaltime_today();
466 icaltimezone_minimum_expansion_year = today.year;
469 changes_end_year = end_year;
470 if (changes_end_year < icaltimezone_minimum_expansion_year)
471 changes_end_year = icaltimezone_minimum_expansion_year;
473 changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
475 if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
476 changes_end_year = ICALTIMEZONE_MAX_YEAR;
478 if (!zone->changes || zone->end_year < end_year)
479 icaltimezone_expand_changes (zone, changes_end_year);
484 icaltimezone_expand_changes (icaltimezone *zone,
491 printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
494 changes = icalarray_new (sizeof (icaltimezonechange), 32);
498 /* Scan the STANDARD and DAYLIGHT subcomponents. */
499 comp = icalcomponent_get_first_component (zone->component,
502 icaltimezone_expand_vtimezone (comp, end_year, changes);
503 comp = icalcomponent_get_next_component (zone->component,
507 /* Sort the changes. We may have duplicates but I don't think it will
509 icalarray_sort (changes, icaltimezone_compare_change_fn);
512 icalarray_free (zone->changes);
514 zone->changes = changes;
515 zone->end_year = end_year;
520 icaltimezone_expand_vtimezone (icalcomponent *comp,
524 icaltimezonechange change;
526 struct icaltimetype dtstart, occ;
527 struct icalrecurrencetype rrule;
528 icalrecur_iterator* rrule_iterator;
529 struct icaldatetimeperiodtype rdate;
530 int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
531 int has_rdate = 0, has_rrule = 0;
533 /* First we check if it is a STANDARD or DAYLIGHT component, and
534 just return if it isn't. */
535 if (icalcomponent_isa (comp) == ICAL_XSTANDARD_COMPONENT)
536 change.is_daylight = 0;
537 else if (icalcomponent_isa (comp) == ICAL_XDAYLIGHT_COMPONENT)
538 change.is_daylight = 1;
542 /* Step through each of the properties to find the DTSTART,
543 TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
544 since we need these properties before we can do that. */
545 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
547 switch (icalproperty_isa (prop)) {
548 case ICAL_DTSTART_PROPERTY:
549 dtstart = icalproperty_get_dtstart (prop);
552 case ICAL_TZOFFSETTO_PROPERTY:
553 change.utc_offset = icalproperty_get_tzoffsetto (prop);
554 /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset);*/
555 found_tzoffsetto = 1;
557 case ICAL_TZOFFSETFROM_PROPERTY:
558 change.prev_utc_offset = icalproperty_get_tzoffsetfrom (prop);
559 /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset);*/
560 found_tzoffsetfrom = 1;
562 case ICAL_RDATE_PROPERTY:
565 case ICAL_RRULE_PROPERTY:
569 /* Just ignore any other properties. */
573 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
576 /* Microsoft Outlook for Mac (and possibly other versions) will create
577 timezones without a tzoffsetfrom property if it's a timezone that
578 doesn't change for DST. */
579 if (found_tzoffsetto && !found_tzoffsetfrom) {
580 change.prev_utc_offset = change.utc_offset;
581 found_tzoffsetfrom = 1;
584 /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
585 ignore the component. FIXME: Add an error property? */
586 if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
590 printf ("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
591 dtstart.year, dtstart.month, dtstart.day,
592 dtstart.hour, dtstart.minute, dtstart.second);
595 /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add
596 a single change for the DTSTART. */
598 change.year = dtstart.year;
599 change.month = dtstart.month;
600 change.day = dtstart.day;
601 change.hour = dtstart.hour;
602 change.minute = dtstart.minute;
603 change.second = dtstart.second;
605 /* Convert to UTC. */
606 icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
609 printf (" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
610 change.year, change.month, change.day,
611 change.hour, change.minute, change.second);
614 /* Add the change to the array. */
615 icalarray_append (changes, &change);
618 /* The component has recurrence data, so we expand that now. */
619 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
620 while (prop && (has_rdate || has_rrule)) {
622 printf ("Expanding property...\n");
624 switch (icalproperty_isa (prop)) {
625 case ICAL_RDATE_PROPERTY:
626 rdate = icalproperty_get_rdate (prop);
627 change.year = rdate.time.year;
628 change.month = rdate.time.month;
629 change.day = rdate.time.day;
630 /* RDATEs with a DATE value inherit the time from
632 if (icaltime_is_date(rdate.time)) {
633 change.hour = dtstart.hour;
634 change.minute = dtstart.minute;
635 change.second = dtstart.second;
637 change.hour = rdate.time.hour;
638 change.minute = rdate.time.minute;
639 change.second = rdate.time.second;
641 /* The spec was a bit vague about whether RDATEs were in local
642 time or UTC so we support both to be safe. So if it is in
643 UTC we have to add the UTC offset to get a local time. */
644 if (!icaltime_is_utc(rdate.time))
645 icaltimezone_adjust_change (&change, 0, 0, 0,
646 -change.prev_utc_offset);
650 printf (" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
651 change.year, change.month, change.day,
652 change.hour, change.minute, change.second);
655 icalarray_append (changes, &change);
657 case ICAL_RRULE_PROPERTY:
658 rrule = icalproperty_get_rrule (prop);
660 /* If the rrule UNTIL value is set and is in UTC, we convert it to
661 a local time, since the recurrence code has no way to convert
663 if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
665 printf (" Found RRULE UNTIL in UTC.\n");
668 /* To convert from UTC to a local time, we use the TZOFFSETFROM
669 since that is the offset from UTC that will be in effect
670 when each of the RRULE occurrences happens. */
671 icaltime_adjust (&rrule.until, 0, 0, 0,
672 change.prev_utc_offset);
673 rrule.until.is_utc = 0;
676 /* Add the dtstart to changes, otherwise some oddly-defined VTIMEZONE
677 components can cause the first year to get skipped. */
678 change.year = dtstart.year;
679 change.month = dtstart.month;
680 change.day = dtstart.day;
681 change.hour = dtstart.hour;
682 change.minute = dtstart.minute;
683 change.second = dtstart.second;
686 printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
687 change.year, change.month, change.day,
688 change.hour, change.minute, change.second);
691 icaltimezone_adjust_change (&change, 0, 0, 0,
692 -change.prev_utc_offset);
694 icalarray_append (changes, &change);
696 rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
697 for (;rrule_iterator;) {
698 occ = icalrecur_iterator_next (rrule_iterator);
699 /* Skip dtstart since we just added it */
700 if (icaltime_compare(dtstart, occ) == 0)
702 if (occ.year > end_year || icaltime_is_null_time (occ))
705 change.year = occ.year;
706 change.month = occ.month;
707 change.day = occ.day;
708 change.hour = occ.hour;
709 change.minute = occ.minute;
710 change.second = occ.second;
713 printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
714 change.year, change.month, change.day,
715 change.hour, change.minute, change.second);
718 icaltimezone_adjust_change (&change, 0, 0, 0,
719 -change.prev_utc_offset);
721 icalarray_append (changes, &change);
724 icalrecur_iterator_free (rrule_iterator);
730 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
735 /** A function to compare 2 icaltimezonechange elements, used for qsort(). */
737 icaltimezone_compare_change_fn (const void *elem1,
740 const icaltimezonechange *change1, *change2;
743 change1 = (const icaltimezonechange *)elem1;
744 change2 = (const icaltimezonechange *)elem2;
746 if (change1->year < change2->year)
748 else if (change1->year > change2->year)
751 else if (change1->month < change2->month)
753 else if (change1->month > change2->month)
756 else if (change1->day < change2->day)
758 else if (change1->day > change2->day)
761 else if (change1->hour < change2->hour)
763 else if (change1->hour > change2->hour)
766 else if (change1->minute < change2->minute)
768 else if (change1->minute > change2->minute)
771 else if (change1->second < change2->second)
773 else if (change1->second > change2->second)
785 icaltimezone_convert_time (struct icaltimetype *tt,
786 icaltimezone *from_zone,
787 icaltimezone *to_zone)
789 int utc_offset, is_daylight;
791 /* If the time is a DATE value or both timezones are the same, or we are
792 converting a floating time, we don't need to do anything. */
793 if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
796 /* Convert the time to UTC by getting the UTC offset and subtracting it. */
797 utc_offset = icaltimezone_get_utc_offset (from_zone, tt, NULL);
798 icaltime_adjust (tt, 0, 0, 0, -utc_offset);
800 /* Now we convert the time to the new timezone by getting the UTC offset
801 of our UTC time and adding it. */
802 utc_offset = icaltimezone_get_utc_offset_of_utc_time (to_zone, tt,
804 tt->is_daylight = is_daylight;
805 icaltime_adjust (tt, 0, 0, 0, utc_offset);
811 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
812 timezone. Also, this takes a pointer instead of the struct. */
813 /* Calculates the UTC offset of a given local time in the given
814 timezone. It is the number of seconds to add to UTC to get local
815 time. The is_daylight flag is set to 1 if the time is in
816 daylight-savings time. */
818 icaltimezone_get_utc_offset (icaltimezone *zone,
819 struct icaltimetype *tt,
822 icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
823 int change_num, step, utc_offset_change, cmp;
824 int change_num_to_use;
833 /* For local times and UTC return 0. */
834 if (zone == NULL || zone == &utc_timezone)
837 /* Use the builtin icaltimezone if possible. */
838 if (zone->builtin_timezone)
839 zone = zone->builtin_timezone;
841 /* Make sure the changes array is expanded up to the given time. */
842 icaltimezone_ensure_coverage (zone, tt->year);
844 if (!zone->changes || zone->changes->num_elements == 0)
847 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
848 can use our comparison function on it. */
849 tt_change.year = tt->year;
850 tt_change.month = tt->month;
851 tt_change.day = tt->day;
852 tt_change.hour = tt->hour;
853 tt_change.minute = tt->minute;
854 tt_change.second = tt->second;
856 /* This should find a change close to the time, either the change before
857 it or the change after it. */
858 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
861 icalerror_assert (change_num >= 0,
862 "Negative timezone change index");
863 icalerror_assert (change_num < zone->changes->num_elements,
864 "Timezone change index out of bounds");
866 /* Now move backwards or forwards to find the timezone change that applies
867 to tt. It should only have to do 1 or 2 steps. */
868 zone_change = icalarray_element_at (zone->changes, change_num);
870 change_num_to_use = -1;
872 /* Copy the change, so we can adjust it. */
873 tmp_change = *zone_change;
875 /* If the clock is going backward, check if it is in the region of time
876 that is used twice. If it is, use the change with the daylight
877 setting which matches tt, or use standard if we don't know. */
878 if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
879 /* If the time change is at 2:00AM local time and the clock is
880 going back to 1:00AM we adjust the change to 1:00AM. We may
881 have the wrong change but we'll figure that out later. */
882 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
883 tmp_change.utc_offset);
885 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
886 tmp_change.prev_utc_offset);
889 cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
891 /* If the given time is on or after this change, then this change may
892 apply, but we continue as a later change may be the right one.
893 If the given time is before this change, then if we have already
894 found a change which applies we can use that, else we need to step
897 change_num_to_use = change_num;
901 /* If we are stepping backwards through the changes and we have found
902 a change that applies, then we know this is the change to use so
904 if (step == -1 && change_num_to_use != -1)
909 /* If we go past the start of the changes array, then we have no data
910 for this time so we return the prev UTC offset. */
911 if (change_num < 0) {
913 *is_daylight = ! tmp_change.is_daylight;
914 return tmp_change.prev_utc_offset;
917 if ((unsigned int)change_num >= zone->changes->num_elements)
920 zone_change = icalarray_element_at (zone->changes, change_num);
923 /* If we didn't find a change to use, then we have a bug! */
924 icalerror_assert (change_num_to_use != -1,
925 "No applicable timezone change found");
927 /* Now we just need to check if the time is in the overlapped region of
928 time when clocks go back. */
929 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
931 utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
932 if (utc_offset_change < 0 && change_num_to_use > 0) {
933 tmp_change = *zone_change;
934 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
935 tmp_change.prev_utc_offset);
937 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
938 /* The time is in the overlapped region, so we may need to use
939 either the current zone_change or the previous one. If the
940 time has the is_daylight field set we use the matching change,
941 else we use the change with standard time. */
942 prev_zone_change = icalarray_element_at (zone->changes,
943 change_num_to_use - 1);
945 /* I was going to add an is_daylight flag to struct icaltimetype,
946 but iCalendar doesn't let us distinguish between standard and
947 daylight time anyway, so there's no point. So we just use the
948 standard time instead. */
949 want_daylight = (tt->is_daylight == 1) ? 1 : 0;
952 if (zone_change->is_daylight == prev_zone_change->is_daylight)
953 printf (" **** Same is_daylight setting\n");
956 if (zone_change->is_daylight != want_daylight
957 && prev_zone_change->is_daylight == want_daylight)
958 zone_change = prev_zone_change;
962 /* Now we know exactly which timezone change applies to the time, so
963 we can return the UTC offset and whether it is a daylight time. */
965 *is_daylight = zone_change->is_daylight;
966 return zone_change->utc_offset;
970 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
971 timezone. Also, this takes a pointer instead of the struct. */
972 /** Calculates the UTC offset of a given UTC time in the given
973 timezone. It is the number of seconds to add to UTC to get local
974 time. The is_daylight flag is set to 1 if the time is in
975 daylight-savings time. */
977 icaltimezone_get_utc_offset_of_utc_time (icaltimezone *zone,
978 struct icaltimetype *tt,
981 icaltimezonechange *zone_change, tt_change, tmp_change;
982 int change_num, step, change_num_to_use;
987 /* For local times and UTC return 0. */
988 if (zone == NULL || zone == &utc_timezone)
991 /* Use the builtin icaltimezone if possible. */
992 if (zone->builtin_timezone)
993 zone = zone->builtin_timezone;
995 /* Make sure the changes array is expanded up to the given time. */
996 icaltimezone_ensure_coverage (zone, tt->year);
998 if (!zone->changes || zone->changes->num_elements == 0)
1001 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
1002 can use our comparison function on it. */
1003 tt_change.year = tt->year;
1004 tt_change.month = tt->month;
1005 tt_change.day = tt->day;
1006 tt_change.hour = tt->hour;
1007 tt_change.minute = tt->minute;
1008 tt_change.second = tt->second;
1010 /* This should find a change close to the time, either the change before
1011 it or the change after it. */
1012 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
1015 icalerror_assert (change_num >= 0,
1016 "Negative timezone change index");
1017 icalerror_assert (change_num < zone->changes->num_elements,
1018 "Timezone change index out of bounds");
1020 /* Now move backwards or forwards to find the timezone change that applies
1021 to tt. It should only have to do 1 or 2 steps. */
1022 zone_change = icalarray_element_at (zone->changes, change_num);
1024 change_num_to_use = -1;
1026 /* Copy the change and adjust it to UTC. */
1027 tmp_change = *zone_change;
1029 /* If the given time is on or after this change, then this change may
1030 apply, but we continue as a later change may be the right one.
1031 If the given time is before this change, then if we have already
1032 found a change which applies we can use that, else we need to step
1034 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
1035 change_num_to_use = change_num;
1039 /* If we are stepping backwards through the changes and we have found
1040 a change that applies, then we know this is the change to use so
1041 we exit the loop. */
1042 if (step == -1 && change_num_to_use != -1)
1047 /* If we go past the start of the changes array, then we have no data
1048 for this time so we return a UTC offset of 0. */
1052 if ((unsigned int)change_num >= zone->changes->num_elements)
1055 zone_change = icalarray_element_at (zone->changes, change_num);
1058 /* If we didn't find a change to use, then we have a bug! */
1059 icalerror_assert (change_num_to_use != -1,
1060 "No applicable timezone change found");
1062 /* Now we know exactly which timezone change applies to the time, so
1063 we can return the UTC offset and whether it is a daylight time. */
1064 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
1066 *is_daylight = zone_change->is_daylight;
1068 return zone_change->utc_offset;
1072 /** Returns the index of a timezone change which is close to the time
1075 icaltimezone_find_nearby_change (icaltimezone *zone,
1076 icaltimezonechange *change)
1078 icaltimezonechange *zone_change;
1079 int lower, upper, middle, cmp;
1081 /* Do a simple binary search. */
1083 upper = zone->changes->num_elements;
1085 while (lower < upper) {
1086 middle = (lower + upper) / 2;
1087 zone_change = icalarray_element_at (zone->changes, middle);
1088 cmp = icaltimezone_compare_change_fn (change, zone_change);
1103 /** Adds (or subtracts) a time from a icaltimezonechange. NOTE: This
1104 function is exactly the same as icaltime_adjust() except for the
1105 type of the first parameter. */
1107 icaltimezone_adjust_change (icaltimezonechange *tt,
1113 int second, minute, hour, day;
1114 int minutes_overflow, hours_overflow, days_overflow;
1117 /* Add on the seconds. */
1118 second = tt->second + seconds;
1119 tt->second = second % 60;
1120 minutes_overflow = second / 60;
1121 if (tt->second < 0) {
1126 /* Add on the minutes. */
1127 minute = tt->minute + minutes + minutes_overflow;
1128 tt->minute = minute % 60;
1129 hours_overflow = minute / 60;
1130 if (tt->minute < 0) {
1135 /* Add on the hours. */
1136 hour = tt->hour + hours + hours_overflow;
1137 tt->hour = hour % 24;
1138 days_overflow = hour / 24;
1144 /* Add on the days. */
1145 day = tt->day + days + days_overflow;
1148 days_in_month = icaltime_days_in_month (tt->month, tt->year);
1149 if (day <= days_in_month)
1153 if (tt->month >= 13) {
1158 day -= days_in_month;
1162 if (tt->month == 1) {
1169 day += icaltime_days_in_month (tt->month, tt->year);
1177 icaltimezone_get_tzid (icaltimezone *zone)
1179 /* If this is a floating time, without a timezone, return NULL. */
1183 icaltimezone_load_builtin_timezone (zone);
1190 icaltimezone_get_location (icaltimezone *zone)
1192 /* If this is a floating time, without a timezone, return NULL. */
1196 /* Note that for builtin timezones this comes from zones.tab so we don't
1197 need to check the timezone is loaded here. */
1198 return zone->location;
1203 icaltimezone_get_tznames (icaltimezone *zone)
1205 /* If this is a floating time, without a timezone, return NULL. */
1209 icaltimezone_load_builtin_timezone (zone);
1211 return zone->tznames;
1215 /** Returns the latitude of a builtin timezone. */
1217 icaltimezone_get_latitude (icaltimezone *zone)
1219 /* If this is a floating time, without a timezone, return 0. */
1223 /* Note that for builtin timezones this comes from zones.tab so we don't
1224 need to check the timezone is loaded here. */
1225 return zone->latitude;
1229 /** Returns the longitude of a builtin timezone. */
1231 icaltimezone_get_longitude (icaltimezone *zone)
1233 /* If this is a floating time, without a timezone, return 0. */
1237 /* Note that for builtin timezones this comes from zones.tab so we don't
1238 need to check the timezone is loaded here. */
1239 return zone->longitude;
1243 /** Returns the VTIMEZONE component of a timezone. */
1245 icaltimezone_get_component (icaltimezone *zone)
1247 /* If this is a floating time, without a timezone, return NULL. */
1251 icaltimezone_load_builtin_timezone (zone);
1253 return zone->component;
1257 /** Sets the VTIMEZONE component of an icaltimezone, initializing the
1258 tzid, location & tzname fields. It returns 1 on success or 0 on
1259 failure, i.e. no TZID was found. */
1261 icaltimezone_set_component (icaltimezone *zone,
1262 icalcomponent *comp)
1264 icaltimezone_reset (zone);
1265 return icaltimezone_get_vtimezone_properties (zone, comp);
1269 /* Returns the timezone name to display to the user. We prefer to use the
1270 Olson city name, but fall back on the TZNAME, or finally the TZID. We don't
1271 want to use "" as it may be wrongly interpreted as a floating time.
1272 Do not free the returned string. */
1274 icaltimezone_get_display_name (icaltimezone *zone)
1276 const char *display_name;
1278 display_name = icaltimezone_get_location (zone);
1280 display_name = icaltimezone_get_tznames (zone);
1281 if (!display_name) {
1282 display_name = icaltimezone_get_tzid (zone);
1283 /* Outlook will strip out X-LIC-LOCATION property and so all
1284 we get back in the iTIP replies is the TZID. So we see if
1285 this is one of our TZIDs and if so we jump to the city name
1286 at the end of it. */
1288 && !strncmp (display_name, ical_tzid_prefix, strlen(ical_tzid_prefix))) {
1289 /* Get the location, which is after the 3rd '/' char. */
1291 int num_slashes = 0;
1292 for (p = display_name; *p; p++) {
1295 if (num_slashes == 3)
1302 return display_name;
1306 icaltimezone_array_new (void)
1308 return icalarray_new (sizeof (icaltimezone), 16);
1313 icaltimezone_array_append_from_vtimezone (icalarray *timezones,
1314 icalcomponent *child)
1318 icaltimezone_init (&zone);
1319 if (icaltimezone_get_vtimezone_properties (&zone, child))
1320 icalarray_append (timezones, &zone);
1325 icaltimezone_array_free (icalarray *timezones)
1332 for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1333 zone = icalarray_element_at (timezones, i);
1334 icaltimezone_free (zone, 0);
1337 icalarray_free (timezones);
1343 * BUILTIN TIMEZONE HANDLING
1347 /** Returns an icalarray of icaltimezone structs, one for each builtin
1348 timezone. This will load and parse the zones.tab file to get the
1349 timezone names and their coordinates. It will not load the
1350 VTIMEZONE data for any timezones. */
1352 icaltimezone_get_builtin_timezones (void)
1354 if (!builtin_timezones)
1355 icaltimezone_init_builtin_timezones ();
1357 return builtin_timezones;
1360 /** Release builtin timezone memory */
1362 icaltimezone_free_builtin_timezones(void)
1364 icaltimezone_array_free(builtin_timezones);
1365 builtin_timezones = 0;
1369 /** Returns a single builtin timezone, given its Olson city name. */
1371 icaltimezone_get_builtin_timezone (const char *location)
1373 icalcomponent *comp;
1376 const char *zone_location;
1378 if (!location || !location[0])
1381 if (!builtin_timezones)
1382 icaltimezone_init_builtin_timezones ();
1384 if (strcmp (location, "UTC") == 0 || strcmp (location, "GMT") == 0)
1385 return &utc_timezone;
1388 /* Do a simple binary search. */
1390 upper = builtin_timezones->num_elements;
1392 while (lower < upper) {
1393 middle = (lower + upper) / 2;
1394 zone = icalarray_element_at (builtin_timezones, middle);
1395 zone_location = icaltimezone_get_location (zone);
1396 cmp = strcmp (location, zone_location);
1406 /* The zones from the system are not stored in alphabetical order,
1407 so we just do a sequential search */
1408 for (lower = 0; lower < builtin_timezones->num_elements; lower++) {
1409 zone = icalarray_element_at (builtin_timezones, lower);
1410 zone_location = icaltimezone_get_location (zone);
1411 if (strcmp (location, zone_location) == 0)
1415 /* Check whether file exists, but is not mentioned in zone.tab.
1416 It means it's a deprecated timezone, but still available. */
1417 comp = icaltzutil_fetch_timezone (location);
1420 icaltimezone_init (&tz);
1421 if (icaltimezone_set_component (&tz, comp)) {
1422 icalarray_append (builtin_timezones, &tz);
1423 return icalarray_element_at (builtin_timezones, builtin_timezones->num_elements - 1);
1425 icalcomponent_free (comp);
1432 static struct icaltimetype
1433 tm_to_icaltimetype (struct tm *tm)
1435 struct icaltimetype itt;
1437 memset (&itt, 0, sizeof (struct icaltimetype));
1439 itt.second = tm->tm_sec;
1440 itt.minute = tm->tm_min;
1441 itt.hour = tm->tm_hour;
1443 itt.day = tm->tm_mday;
1444 itt.month = tm->tm_mon + 1;
1445 itt.year = tm->tm_year+ 1900;
1454 get_offset (icaltimezone *zone)
1457 struct icaltimetype tt;
1459 time_t now = time(NULL);
1461 gmtime_r ((const time_t *) &now, &local);
1462 tt = tm_to_icaltimetype (&local);
1463 offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1468 /** Returns a single builtin timezone, given its offset from UTC */
1470 icaltimezone_get_builtin_timezone_from_offset (int offset, const char *tzname)
1472 icaltimezone *zone=NULL;
1475 if (!builtin_timezones)
1476 icaltimezone_init_builtin_timezones ();
1479 return &utc_timezone;
1484 count = builtin_timezones->num_elements;
1486 for (i=0; i<count; i++) {
1488 zone = icalarray_element_at (builtin_timezones, i);
1489 icaltimezone_load_builtin_timezone (zone);
1491 z_offset = get_offset(zone);
1493 if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1500 /** Returns a single builtin timezone, given its TZID. */
1502 icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1504 int num_slashes = 0;
1505 const char *p, *zone_tzid;
1508 if (!tzid || !tzid[0])
1511 if (strcmp (tzid, "UTC") == 0 || strcmp (tzid, "GMT") == 0) {
1512 return icaltimezone_get_builtin_timezone(tzid);
1515 /* Check that the TZID starts with our unique prefix. */
1516 if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
1519 /* Get the location, which is after the 3rd '/' character. */
1520 for (p = tzid; *p; p++) {
1523 if (num_slashes == 3)
1528 if (num_slashes != 3)
1533 /* Now we can use the function to get the builtin timezone from the
1535 zone = icaltimezone_get_builtin_timezone (p);
1539 /* Check that the builtin TZID matches exactly. We don't want to return
1540 a different version of the VTIMEZONE. */
1541 zone_tzid = icaltimezone_get_tzid (zone);
1542 if (!strcmp (zone_tzid, tzid))
1549 /** Returns the special UTC timezone. */
1551 icaltimezone_get_utc_timezone (void)
1553 if (!builtin_timezones)
1554 icaltimezone_init_builtin_timezones ();
1556 return &utc_timezone;
1561 /** This initializes the builtin timezone data, i.e. the
1562 builtin_timezones array and the special UTC timezone. It should be
1563 called before any code that uses the timezone functions. */
1565 icaltimezone_init_builtin_timezones (void)
1567 /* Initialize the special UTC timezone. */
1568 utc_timezone.tzid = (char *)"UTC";
1571 pthread_mutex_lock(&builtin_mutex);
1573 if (!builtin_timezones)
1574 icaltimezone_parse_zone_tab ();
1576 pthread_mutex_unlock(&builtin_mutex);
1581 parse_coord (char *coord,
1588 sscanf (coord + 1, "%2d%2d", degrees, minutes);
1590 sscanf (coord + 1, "%3d%2d", degrees, minutes);
1592 sscanf (coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1594 sscanf (coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1596 fprintf (stderr, "Invalid coordinate: %s\n", coord);
1600 if (coord [0] == '-')
1601 *degrees = -*degrees;
1605 fetch_lat_long_from_string (const char *str, int *latitude_degrees, int *latitude_minutes, int *latitude_seconds,
1606 int *longitude_degrees, int *longitude_minutes, int *longitude_seconds, char *location)
1609 char *sptr, *lat, *lon, *loc, *temp;
1611 /* We need to parse the latitude/longitude co-ordinates and location fields */
1612 sptr = (char *) str;
1613 while (*sptr != '\t')
1616 while (*sptr != '\t')
1619 lat = (char *) malloc (len + 1);
1620 lat = strncpy (lat, temp, len);
1622 while (*sptr != '\t')
1626 while (!isspace (*sptr))
1629 location = strncpy (location, loc, len);
1630 location [len] = '\0';
1632 #if defined(sun) && defined(__SVR4)
1633 /* Handle EET, MET and WET in zone_sun.tab. */
1634 if (!strcmp (location, "Europe/")) {
1635 while (*sptr != '\t')
1638 while (!isspace (*sptr))
1641 location = strncpy (location, loc, len);
1642 location [len] = '\0';
1647 while (*lon != '+' && *lon != '-')
1650 if (parse_coord (lat, (int)(lon - lat),
1653 latitude_seconds) == 1 ||
1654 parse_coord (lon, (int)strlen(lon),
1657 longitude_seconds) == 1) {
1667 /** This parses the zones.tab file containing the names and locations
1668 of the builtin timezones. It creates the builtin_timezones array
1669 which is an icalarray of icaltimezone structs. It only fills in the
1670 location, latitude and longtude fields; the rest are left
1671 blank. The VTIMEZONE component is loaded later if it is needed. The
1672 timezones in the zones.tab file are sorted by their name, which is
1673 useful for binary searches. */
1675 icaltimezone_parse_zone_tab (void)
1679 char buf[1024]; /* Used to store each line of zones.tab as it is read. */
1680 char location[1024]; /* Stores the city name when parsing buf. */
1681 size_t filename_len;
1682 int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0;
1683 int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0;
1686 icalerror_assert (builtin_timezones == NULL,
1687 "Parsing zones.tab file multiple times");
1689 builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1691 #ifndef USE_BUILTIN_TZDATA
1692 filename_len = strlen ((char *) icaltzutil_get_zone_directory()) + strlen (ZONES_TAB_SYSTEM_FILENAME)
1695 filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1699 filename = (char*) malloc (filename_len);
1701 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1704 #ifndef USE_BUILTIN_TZDATA
1705 snprintf (filename, filename_len, "%s/%s", icaltzutil_get_zone_directory (),
1706 ZONES_TAB_SYSTEM_FILENAME);
1708 snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1709 ZONES_TAB_FILENAME);
1712 fp = fopen (filename, "r");
1715 icalerror_set_errno(ICAL_FILE_ERROR);
1719 while (fgets (buf, sizeof(buf), fp)) {
1720 if (*buf == '#') continue;
1722 #ifdef USE_BUILTIN_TZDATA
1723 /* The format of each line is: "latitude longitude location". */
1724 if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
1725 &latitude_degrees, &latitude_minutes,
1727 &longitude_degrees, &longitude_minutes,
1730 fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1734 if (fetch_lat_long_from_string (buf, &latitude_degrees, &latitude_minutes,
1736 &longitude_degrees, &longitude_minutes, &longitude_seconds,
1738 fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1743 icaltimezone_init (&zone);
1744 zone.location = strdup (location);
1746 if (latitude_degrees >= 0)
1747 zone.latitude = (double) latitude_degrees
1748 + (double) latitude_minutes / 60
1749 + (double) latitude_seconds / 3600;
1751 zone.latitude = (double) latitude_degrees
1752 - (double) latitude_minutes / 60
1753 - (double) latitude_seconds / 3600;
1755 if (longitude_degrees >= 0)
1756 zone.longitude = (double) longitude_degrees
1757 + (double) longitude_minutes / 60
1758 + (double) longitude_seconds / 3600;
1760 zone.longitude = (double) longitude_degrees
1761 - (double) longitude_minutes / 60
1762 - (double) longitude_seconds / 3600;
1764 icalarray_append (builtin_timezones, &zone);
1767 printf ("Found zone: %s %f %f\n",
1768 location, zone.latitude, zone.longitude);
1776 icaltimezone_release_zone_tab (void)
1779 icalarray *mybuiltin_timezones = builtin_timezones;
1781 if (builtin_timezones == NULL)
1783 builtin_timezones = NULL;
1784 for (i = 0; i < mybuiltin_timezones->num_elements; i++)
1785 free ( ((icaltimezone*)icalarray_element_at(mybuiltin_timezones, i))->location);
1786 icalarray_free (mybuiltin_timezones);
1789 /** Loads the builtin VTIMEZONE data for the given timezone. */
1791 icaltimezone_load_builtin_timezone (icaltimezone *zone)
1793 icalcomponent *subcomp;
1795 /* If the location isn't set, it isn't a builtin timezone. */
1796 if (!zone->location || !zone->location[0])
1800 pthread_mutex_lock(&builtin_mutex);
1801 if (zone->component)
1804 #ifdef USE_BUILTIN_TZDATA
1805 if (zone->component)
1810 #ifdef USE_BUILTIN_TZDATA
1813 icalcomponent *comp;
1814 unsigned int filename_len;
1818 filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1820 filename = (char*) malloc (filename_len);
1822 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1826 snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1829 fp = fopen (filename, "r");
1832 icalerror_set_errno(ICAL_FILE_ERROR);
1837 /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1838 this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1839 fprintf(stderr, "** WARNING ** %s: %d %s\n", __FILE__, __LINE__, icalerror_strerror(icalerrno));
1842 parser = icalparser_new ();
1843 icalparser_set_gen_data (parser, fp);
1844 comp = icalparser_parse (parser, icaltimezone_load_get_line_fn);
1845 icalparser_free (parser);
1848 /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1849 subcomp = icalcomponent_get_first_component (comp,
1850 ICAL_VTIMEZONE_COMPONENT);
1852 subcomp = icaltzutil_fetch_timezone (zone->location);
1856 icalerror_set_errno(ICAL_PARSE_ERROR);
1860 icaltimezone_get_vtimezone_properties (zone, subcomp);
1862 #ifdef USE_BUILTIN_TZDATA
1863 icalcomponent_remove_component(comp,subcomp);
1864 icalcomponent_free(comp);
1870 pthread_mutex_unlock(&builtin_mutex);
1876 #ifdef USE_BUILTIN_TZDATA
1877 /** Callback used from icalparser_parse() */
1879 icaltimezone_load_get_line_fn (char *s,
1883 return fgets (s, (int)size, (FILE*) data);
1894 * This outputs a list of timezone changes for the given timezone to the
1895 * given file, up to the maximum year given. We compare this output with the
1896 * output from 'vzic --dump-changes' to make sure that we are consistent.
1897 * (vzic is the Olson timezone database to VTIMEZONE converter.)
1899 * The output format is:
1901 * Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1903 * The Date and Time fields specify the time change in UTC.
1905 * The UTC Offset is for local (wall-clock) time. It is the amount of time
1906 * to add to UTC to get local time.
1909 icaltimezone_dump_changes (icaltimezone *zone,
1913 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1914 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1915 icaltimezonechange *zone_change;
1919 /* Make sure the changes array is expanded up to the given time. */
1920 icaltimezone_ensure_coverage (zone, max_year);
1923 printf ("Num changes: %i\n", zone->changes->num_elements);
1926 for (change_num = 0; (unsigned int)change_num < zone->changes->num_elements; change_num++) {
1927 zone_change = icalarray_element_at (zone->changes, change_num);
1929 if (zone_change->year > max_year)
1932 fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1934 zone_change->day, months[zone_change->month - 1],
1936 zone_change->hour, zone_change->minute, zone_change->second);
1938 /* Wall Clock Time offset from UTC. */
1939 format_utc_offset (zone_change->utc_offset, buffer);
1940 fprintf (fp, "\t%s", buffer);
1948 /** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1949 buffer should have space for 8 characters. */
1951 format_utc_offset (int utc_offset,
1954 const char *sign = "+";
1955 int hours, minutes, seconds;
1957 if (utc_offset < 0) {
1958 utc_offset = -utc_offset;
1962 hours = utc_offset / 3600;
1963 minutes = (utc_offset % 3600) / 60;
1964 seconds = utc_offset % 60;
1966 /* Sanity check. Standard timezone offsets shouldn't be much more than 12
1967 hours, and daylight saving shouldn't change it by more than a few hours.
1968 (The maximum offset is 15 hours 56 minutes at present.) */
1969 if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
1970 || seconds < 0 || seconds >= 60) {
1971 fprintf (stderr, "Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1972 hours, minutes, seconds);
1976 snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
1978 snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
1981 static const char* get_zone_directory(void)
1984 return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1986 wchar_t wbuffer[1000];
1988 char buffer[1000], zoneinfodir[1000], dirname[1000];
1990 wchar_t zoneinfodir[1000], dirname[1000];
1993 static char *cache = NULL;
1995 char *dirslash, *zislash;
1997 wchar_t *dirslash, *zislash;
2001 if (zone_files_directory)
2002 return zone_files_directory;
2007 /* Get the filename of the application */
2008 if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
2009 return ZONEINFO_DIRECTORY;
2011 /*wince supports only unicode*/
2013 /* Convert to system codepage */
2014 if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
2015 NULL, &used_default) ||
2017 /* Failed, try 8.3 format */
2018 if (!GetShortPathNameW (wbuffer, wbuffer,
2019 sizeof (wbuffer) / sizeof (wbuffer[0])) ||
2020 !WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
2021 NULL, &used_default) ||
2023 return ZONEINFO_DIRECTORY;
2026 /* Look for the zoneinfo directory somewhere in the path where
2027 * the app is installed. If the path to the app is
2029 * C:\opt\evo-2.6\bin\evolution-2.6.exe
2031 * and the compile-time ZONEINFO_DIRECTORY is
2033 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
2035 * we check the pathnames:
2037 * C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2038 * C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
2039 * C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
2040 * C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo <===
2041 * C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
2042 * C:\opt\evo-2.6/zoneinfo
2043 * C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2044 * C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
2045 * C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
2046 * C:\opt/share/evolution-data-server-1.6/zoneinfo
2047 * C:\opt/evolution-data-server-1.6/zoneinfo
2049 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2050 * C:/target/evo/share/evolution-data-server-1.6/zoneinfo
2051 * C:/evo/share/evolution-data-server-1.6/zoneinfo
2052 * C:/share/evolution-data-server-1.6/zoneinfo
2053 * C:/evolution-data-server-1.6/zoneinfo
2056 * In Evolution's case, we would get a match already at the
2057 * fourth pathname check.
2060 /* Strip away basename of app .exe first */
2062 dirslash = _mbsrchr (buffer, '\\');
2064 dirslash = wcsrchr (wbuffer, L'\\');
2074 while ((dirslash = wcsrchr (wbuffer, '\\'))) {
2075 /* Strip one more directory from app .exe location */
2078 MultiByteToWideChar(CP_ACP,0,ZONEINFO_DIRECTORY,-1,zoneinfodir,1000);
2080 while ((zislash = wcschr (zoneinfodir, L'/'))) {
2082 wcscpy (dirname, wbuffer);
2083 wcscat (dirname, "/");
2084 wcscat (dirname, zislash + 1);
2085 if (stat (dirname, &st) == 0 &&
2086 S_ISDIR (st.st_mode)) {
2087 cache = wce_wctomb (dirname);
2093 while ((dirslash = _mbsrchr (buffer, '\\'))) {
2094 /* Strip one more directory from app .exe location */
2097 strcpy (zoneinfodir, ZONEINFO_DIRECTORY);
2098 while ((zislash = _mbschr (zoneinfodir, '/'))) {
2100 strcpy (dirname, buffer);
2101 strcat (dirname, "/");
2102 strcat (dirname, zislash + 1);
2103 if (stat (dirname, &st) == 0 &&
2104 S_ISDIR (st.st_mode)) {
2105 cache = strdup (dirname);
2111 return ZONEINFO_DIRECTORY;
2115 void set_zone_directory(char *path)
2117 if (zone_files_directory)
2118 free_zone_directory();
2119 zone_files_directory = malloc(strlen(path)+1);
2120 if ( zone_files_directory != NULL )
2122 strcpy(zone_files_directory,path);
2126 void free_zone_directory(void)
2128 if ( zone_files_directory != NULL )
2130 free(zone_files_directory);
2131 zone_files_directory = NULL;
2135 void icaltimezone_set_tzid_prefix(const char *new_prefix)
2138 ical_tzid_prefix = new_prefix;