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)
279 const char *tzid, *tzname;
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_recurrence = 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:
563 case ICAL_RRULE_PROPERTY:
567 /* Just ignore any other properties. */
571 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
574 /* Microsoft Outlook for Mac (and possibly other versions) will create
575 timezones without a tzoffsetfrom property if it's a timezone that
576 doesn't change for DST. */
577 if (found_tzoffsetto && !found_tzoffsetfrom) {
578 change.prev_utc_offset = change.utc_offset;
579 found_tzoffsetfrom = 1;
582 /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
583 ignore the component. FIXME: Add an error property? */
584 if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
588 printf ("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
589 dtstart.year, dtstart.month, dtstart.day,
590 dtstart.hour, dtstart.minute, dtstart.second);
593 /* If the STANDARD/DAYLIGHT component has no recurrence data, we just add
594 a single change for the DTSTART. */
595 if (!has_recurrence) {
596 change.year = dtstart.year;
597 change.month = dtstart.month;
598 change.day = dtstart.day;
599 change.hour = dtstart.hour;
600 change.minute = dtstart.minute;
601 change.second = dtstart.second;
603 /* Convert to UTC. */
604 icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
607 printf (" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
608 change.year, change.month, change.day,
609 change.hour, change.minute, change.second);
612 /* Add the change to the array. */
613 icalarray_append (changes, &change);
617 /* The component has recurrence data, so we expand that now. */
618 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
621 printf ("Expanding property...\n");
623 switch (icalproperty_isa (prop)) {
624 case ICAL_RDATE_PROPERTY:
625 rdate = icalproperty_get_rdate (prop);
626 change.year = rdate.time.year;
627 change.month = rdate.time.month;
628 change.day = rdate.time.day;
629 /* RDATEs with a DATE value inherit the time from
631 if (icaltime_is_date(rdate.time)) {
632 change.hour = dtstart.hour;
633 change.minute = dtstart.minute;
634 change.second = dtstart.second;
636 change.hour = rdate.time.hour;
637 change.minute = rdate.time.minute;
638 change.second = rdate.time.second;
640 /* The spec was a bit vague about whether RDATEs were in local
641 time or UTC so we support both to be safe. So if it is in
642 UTC we have to add the UTC offset to get a local time. */
643 if (!icaltime_is_utc(rdate.time))
644 icaltimezone_adjust_change (&change, 0, 0, 0,
645 -change.prev_utc_offset);
649 printf (" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
650 change.year, change.month, change.day,
651 change.hour, change.minute, change.second);
654 icalarray_append (changes, &change);
656 case ICAL_RRULE_PROPERTY:
657 rrule = icalproperty_get_rrule (prop);
659 /* If the rrule UNTIL value is set and is in UTC, we convert it to
660 a local time, since the recurrence code has no way to convert
662 if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
664 printf (" Found RRULE UNTIL in UTC.\n");
667 /* To convert from UTC to a local time, we use the TZOFFSETFROM
668 since that is the offset from UTC that will be in effect
669 when each of the RRULE occurrences happens. */
670 icaltime_adjust (&rrule.until, 0, 0, 0,
671 change.prev_utc_offset);
672 rrule.until.is_utc = 0;
675 rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
676 for (;rrule_iterator;) {
677 occ = icalrecur_iterator_next (rrule_iterator);
678 if (occ.year > end_year || icaltime_is_null_time (occ))
681 change.year = occ.year;
682 change.month = occ.month;
683 change.day = occ.day;
684 change.hour = occ.hour;
685 change.minute = occ.minute;
686 change.second = occ.second;
689 printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
690 change.year, change.month, change.day,
691 change.hour, change.minute, change.second);
694 icaltimezone_adjust_change (&change, 0, 0, 0,
695 -change.prev_utc_offset);
697 icalarray_append (changes, &change);
700 icalrecur_iterator_free (rrule_iterator);
706 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
711 /** A function to compare 2 icaltimezonechange elements, used for qsort(). */
713 icaltimezone_compare_change_fn (const void *elem1,
716 const icaltimezonechange *change1, *change2;
719 change1 = (const icaltimezonechange *)elem1;
720 change2 = (const icaltimezonechange *)elem2;
722 if (change1->year < change2->year)
724 else if (change1->year > change2->year)
727 else if (change1->month < change2->month)
729 else if (change1->month > change2->month)
732 else if (change1->day < change2->day)
734 else if (change1->day > change2->day)
737 else if (change1->hour < change2->hour)
739 else if (change1->hour > change2->hour)
742 else if (change1->minute < change2->minute)
744 else if (change1->minute > change2->minute)
747 else if (change1->second < change2->second)
749 else if (change1->second > change2->second)
761 icaltimezone_convert_time (struct icaltimetype *tt,
762 icaltimezone *from_zone,
763 icaltimezone *to_zone)
765 int utc_offset, is_daylight;
767 /* If the time is a DATE value or both timezones are the same, or we are
768 converting a floating time, we don't need to do anything. */
769 if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
772 /* Convert the time to UTC by getting the UTC offset and subtracting it. */
773 utc_offset = icaltimezone_get_utc_offset (from_zone, tt, NULL);
774 icaltime_adjust (tt, 0, 0, 0, -utc_offset);
776 /* Now we convert the time to the new timezone by getting the UTC offset
777 of our UTC time and adding it. */
778 utc_offset = icaltimezone_get_utc_offset_of_utc_time (to_zone, tt,
780 tt->is_daylight = is_daylight;
781 icaltime_adjust (tt, 0, 0, 0, utc_offset);
787 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
788 timezone. Also, this takes a pointer instead of the struct. */
789 /* Calculates the UTC offset of a given local time in the given
790 timezone. It is the number of seconds to add to UTC to get local
791 time. The is_daylight flag is set to 1 if the time is in
792 daylight-savings time. */
794 icaltimezone_get_utc_offset (icaltimezone *zone,
795 struct icaltimetype *tt,
798 icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
799 int change_num, step, utc_offset_change, cmp;
800 int change_num_to_use;
809 /* For local times and UTC return 0. */
810 if (zone == NULL || zone == &utc_timezone)
813 /* Use the builtin icaltimezone if possible. */
814 if (zone->builtin_timezone)
815 zone = zone->builtin_timezone;
817 /* Make sure the changes array is expanded up to the given time. */
818 icaltimezone_ensure_coverage (zone, tt->year);
820 if (!zone->changes || zone->changes->num_elements == 0)
823 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
824 can use our comparison function on it. */
825 tt_change.year = tt->year;
826 tt_change.month = tt->month;
827 tt_change.day = tt->day;
828 tt_change.hour = tt->hour;
829 tt_change.minute = tt->minute;
830 tt_change.second = tt->second;
832 /* This should find a change close to the time, either the change before
833 it or the change after it. */
834 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
837 icalerror_assert (change_num >= 0,
838 "Negative timezone change index");
839 icalerror_assert (change_num < zone->changes->num_elements,
840 "Timezone change index out of bounds");
842 /* Now move backwards or forwards to find the timezone change that applies
843 to tt. It should only have to do 1 or 2 steps. */
844 zone_change = icalarray_element_at (zone->changes, change_num);
846 change_num_to_use = -1;
848 /* Copy the change, so we can adjust it. */
849 tmp_change = *zone_change;
851 /* If the clock is going backward, check if it is in the region of time
852 that is used twice. If it is, use the change with the daylight
853 setting which matches tt, or use standard if we don't know. */
854 if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
855 /* If the time change is at 2:00AM local time and the clock is
856 going back to 1:00AM we adjust the change to 1:00AM. We may
857 have the wrong change but we'll figure that out later. */
858 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
859 tmp_change.utc_offset);
861 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
862 tmp_change.prev_utc_offset);
865 cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
867 /* If the given time is on or after this change, then this change may
868 apply, but we continue as a later change may be the right one.
869 If the given time is before this change, then if we have already
870 found a change which applies we can use that, else we need to step
873 change_num_to_use = change_num;
877 /* If we are stepping backwards through the changes and we have found
878 a change that applies, then we know this is the change to use so
880 if (step == -1 && change_num_to_use != -1)
885 /* If we go past the start of the changes array, then we have no data
886 for this time so we return a UTC offset of 0. */
890 if ((unsigned int)change_num >= zone->changes->num_elements)
893 zone_change = icalarray_element_at (zone->changes, change_num);
896 /* If we didn't find a change to use, then we have a bug! */
897 icalerror_assert (change_num_to_use != -1,
898 "No applicable timezone change found");
900 /* Now we just need to check if the time is in the overlapped region of
901 time when clocks go back. */
902 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
904 utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
905 if (utc_offset_change < 0 && change_num_to_use > 0) {
906 tmp_change = *zone_change;
907 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
908 tmp_change.prev_utc_offset);
910 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
911 /* The time is in the overlapped region, so we may need to use
912 either the current zone_change or the previous one. If the
913 time has the is_daylight field set we use the matching change,
914 else we use the change with standard time. */
915 prev_zone_change = icalarray_element_at (zone->changes,
916 change_num_to_use - 1);
918 /* I was going to add an is_daylight flag to struct icaltimetype,
919 but iCalendar doesn't let us distinguish between standard and
920 daylight time anyway, so there's no point. So we just use the
921 standard time instead. */
922 want_daylight = (tt->is_daylight == 1) ? 1 : 0;
925 if (zone_change->is_daylight == prev_zone_change->is_daylight)
926 printf (" **** Same is_daylight setting\n");
929 if (zone_change->is_daylight != want_daylight
930 && prev_zone_change->is_daylight == want_daylight)
931 zone_change = prev_zone_change;
935 /* Now we know exactly which timezone change applies to the time, so
936 we can return the UTC offset and whether it is a daylight time. */
938 *is_daylight = zone_change->is_daylight;
939 return zone_change->utc_offset;
943 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
944 timezone. Also, this takes a pointer instead of the struct. */
945 /** Calculates the UTC offset of a given UTC time in the given
946 timezone. It is the number of seconds to add to UTC to get local
947 time. The is_daylight flag is set to 1 if the time is in
948 daylight-savings time. */
950 icaltimezone_get_utc_offset_of_utc_time (icaltimezone *zone,
951 struct icaltimetype *tt,
954 icaltimezonechange *zone_change, tt_change, tmp_change;
955 int change_num, step, change_num_to_use;
960 /* For local times and UTC return 0. */
961 if (zone == NULL || zone == &utc_timezone)
964 /* Use the builtin icaltimezone if possible. */
965 if (zone->builtin_timezone)
966 zone = zone->builtin_timezone;
968 /* Make sure the changes array is expanded up to the given time. */
969 icaltimezone_ensure_coverage (zone, tt->year);
971 if (!zone->changes || zone->changes->num_elements == 0)
974 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
975 can use our comparison function on it. */
976 tt_change.year = tt->year;
977 tt_change.month = tt->month;
978 tt_change.day = tt->day;
979 tt_change.hour = tt->hour;
980 tt_change.minute = tt->minute;
981 tt_change.second = tt->second;
983 /* This should find a change close to the time, either the change before
984 it or the change after it. */
985 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
988 icalerror_assert (change_num >= 0,
989 "Negative timezone change index");
990 icalerror_assert (change_num < zone->changes->num_elements,
991 "Timezone change index out of bounds");
993 /* Now move backwards or forwards to find the timezone change that applies
994 to tt. It should only have to do 1 or 2 steps. */
995 zone_change = icalarray_element_at (zone->changes, change_num);
997 change_num_to_use = -1;
999 /* Copy the change and adjust it to UTC. */
1000 tmp_change = *zone_change;
1002 /* If the given time is on or after this change, then this change may
1003 apply, but we continue as a later change may be the right one.
1004 If the given time is before this change, then if we have already
1005 found a change which applies we can use that, else we need to step
1007 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
1008 change_num_to_use = change_num;
1012 /* If we are stepping backwards through the changes and we have found
1013 a change that applies, then we know this is the change to use so
1014 we exit the loop. */
1015 if (step == -1 && change_num_to_use != -1)
1020 /* If we go past the start of the changes array, then we have no data
1021 for this time so we return a UTC offset of 0. */
1025 if ((unsigned int)change_num >= zone->changes->num_elements)
1028 zone_change = icalarray_element_at (zone->changes, change_num);
1031 /* If we didn't find a change to use, then we have a bug! */
1032 icalerror_assert (change_num_to_use != -1,
1033 "No applicable timezone change found");
1035 /* Now we know exactly which timezone change applies to the time, so
1036 we can return the UTC offset and whether it is a daylight time. */
1037 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
1039 *is_daylight = zone_change->is_daylight;
1041 return zone_change->utc_offset;
1045 /** Returns the index of a timezone change which is close to the time
1048 icaltimezone_find_nearby_change (icaltimezone *zone,
1049 icaltimezonechange *change)
1051 icaltimezonechange *zone_change;
1052 int lower, upper, middle, cmp;
1054 /* Do a simple binary search. */
1056 upper = zone->changes->num_elements;
1058 while (lower < upper) {
1059 middle = (lower + upper) / 2;
1060 zone_change = icalarray_element_at (zone->changes, middle);
1061 cmp = icaltimezone_compare_change_fn (change, zone_change);
1076 /** Adds (or subtracts) a time from a icaltimezonechange. NOTE: This
1077 function is exactly the same as icaltime_adjust() except for the
1078 type of the first parameter. */
1080 icaltimezone_adjust_change (icaltimezonechange *tt,
1086 int second, minute, hour, day;
1087 int minutes_overflow, hours_overflow, days_overflow;
1090 /* Add on the seconds. */
1091 second = tt->second + seconds;
1092 tt->second = second % 60;
1093 minutes_overflow = second / 60;
1094 if (tt->second < 0) {
1099 /* Add on the minutes. */
1100 minute = tt->minute + minutes + minutes_overflow;
1101 tt->minute = minute % 60;
1102 hours_overflow = minute / 60;
1103 if (tt->minute < 0) {
1108 /* Add on the hours. */
1109 hour = tt->hour + hours + hours_overflow;
1110 tt->hour = hour % 24;
1111 days_overflow = hour / 24;
1117 /* Add on the days. */
1118 day = tt->day + days + days_overflow;
1121 days_in_month = icaltime_days_in_month (tt->month, tt->year);
1122 if (day <= days_in_month)
1126 if (tt->month >= 13) {
1131 day -= days_in_month;
1135 if (tt->month == 1) {
1142 day += icaltime_days_in_month (tt->month, tt->year);
1150 icaltimezone_get_tzid (icaltimezone *zone)
1152 /* If this is a floating time, without a timezone, return NULL. */
1156 icaltimezone_load_builtin_timezone (zone);
1163 icaltimezone_get_location (icaltimezone *zone)
1165 /* If this is a floating time, without a timezone, return NULL. */
1169 /* Note that for builtin timezones this comes from zones.tab so we don't
1170 need to check the timezone is loaded here. */
1171 return zone->location;
1176 icaltimezone_get_tznames (icaltimezone *zone)
1178 /* If this is a floating time, without a timezone, return NULL. */
1182 icaltimezone_load_builtin_timezone (zone);
1184 return zone->tznames;
1188 /** Returns the latitude of a builtin timezone. */
1190 icaltimezone_get_latitude (icaltimezone *zone)
1192 /* If this is a floating time, without a timezone, return 0. */
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->latitude;
1202 /** Returns the longitude of a builtin timezone. */
1204 icaltimezone_get_longitude (icaltimezone *zone)
1206 /* If this is a floating time, without a timezone, return 0. */
1210 /* Note that for builtin timezones this comes from zones.tab so we don't
1211 need to check the timezone is loaded here. */
1212 return zone->longitude;
1216 /** Returns the VTIMEZONE component of a timezone. */
1218 icaltimezone_get_component (icaltimezone *zone)
1220 /* If this is a floating time, without a timezone, return NULL. */
1224 icaltimezone_load_builtin_timezone (zone);
1226 return zone->component;
1230 /** Sets the VTIMEZONE component of an icaltimezone, initializing the
1231 tzid, location & tzname fields. It returns 1 on success or 0 on
1232 failure, i.e. no TZID was found. */
1234 icaltimezone_set_component (icaltimezone *zone,
1235 icalcomponent *comp)
1237 icaltimezone_reset (zone);
1238 return icaltimezone_get_vtimezone_properties (zone, comp);
1242 /* Returns the timezone name to display to the user. We prefer to use the
1243 Olson city name, but fall back on the TZNAME, or finally the TZID. We don't
1244 want to use "" as it may be wrongly interpreted as a floating time.
1245 Do not free the returned string. */
1247 icaltimezone_get_display_name (icaltimezone *zone)
1249 const char *display_name;
1251 display_name = icaltimezone_get_location (zone);
1253 display_name = icaltimezone_get_tznames (zone);
1254 if (!display_name) {
1255 display_name = icaltimezone_get_tzid (zone);
1256 /* Outlook will strip out X-LIC-LOCATION property and so all
1257 we get back in the iTIP replies is the TZID. So we see if
1258 this is one of our TZIDs and if so we jump to the city name
1259 at the end of it. */
1261 && !strncmp (display_name, ical_tzid_prefix, strlen(ical_tzid_prefix))) {
1262 /* Get the location, which is after the 3rd '/' char. */
1264 int num_slashes = 0;
1265 for (p = display_name; *p; p++) {
1268 if (num_slashes == 3)
1275 return display_name;
1279 icaltimezone_array_new (void)
1281 return icalarray_new (sizeof (icaltimezone), 16);
1286 icaltimezone_array_append_from_vtimezone (icalarray *timezones,
1287 icalcomponent *child)
1291 icaltimezone_init (&zone);
1292 if (icaltimezone_get_vtimezone_properties (&zone, child))
1293 icalarray_append (timezones, &zone);
1298 icaltimezone_array_free (icalarray *timezones)
1305 for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1306 zone = icalarray_element_at (timezones, i);
1307 icaltimezone_free (zone, 0);
1310 icalarray_free (timezones);
1316 * BUILTIN TIMEZONE HANDLING
1320 /** Returns an icalarray of icaltimezone structs, one for each builtin
1321 timezone. This will load and parse the zones.tab file to get the
1322 timezone names and their coordinates. It will not load the
1323 VTIMEZONE data for any timezones. */
1325 icaltimezone_get_builtin_timezones (void)
1327 if (!builtin_timezones)
1328 icaltimezone_init_builtin_timezones ();
1330 return builtin_timezones;
1333 /** Release builtin timezone memory */
1335 icaltimezone_free_builtin_timezones(void)
1337 icaltimezone_array_free(builtin_timezones);
1338 builtin_timezones = 0;
1342 /** Returns a single builtin timezone, given its Olson city name. */
1344 icaltimezone_get_builtin_timezone (const char *location)
1346 icalcomponent *comp;
1349 const char *zone_location;
1351 if (!location || !location[0])
1354 if (!builtin_timezones)
1355 icaltimezone_init_builtin_timezones ();
1357 if (!strcmp (location, "UTC"))
1358 return &utc_timezone;
1361 /* Do a simple binary search. */
1363 upper = builtin_timezones->num_elements;
1365 while (lower < upper) {
1366 middle = (lower + upper) / 2;
1367 zone = icalarray_element_at (builtin_timezones, middle);
1368 zone_location = icaltimezone_get_location (zone);
1369 cmp = strcmp (location, zone_location);
1379 /* The zones from the system are not stored in alphabetical order,
1380 so we just do a sequential search */
1381 for (lower = 0; lower < builtin_timezones->num_elements; lower++) {
1382 zone = icalarray_element_at (builtin_timezones, lower);
1383 zone_location = icaltimezone_get_location (zone);
1384 if (strcmp (location, zone_location) == 0)
1388 /* Check whether file exists, but is not mentioned in zone.tab.
1389 It means it's a deprecated timezone, but still available. */
1390 comp = icaltzutil_fetch_timezone (location);
1393 icaltimezone_init (&tz);
1394 if (icaltimezone_set_component (&tz, comp)) {
1395 icalarray_append (builtin_timezones, &tz);
1396 return icalarray_element_at (builtin_timezones, builtin_timezones->num_elements - 1);
1398 icalcomponent_free (comp);
1405 static struct icaltimetype
1406 tm_to_icaltimetype (struct tm *tm)
1408 struct icaltimetype itt;
1410 memset (&itt, 0, sizeof (struct icaltimetype));
1412 itt.second = tm->tm_sec;
1413 itt.minute = tm->tm_min;
1414 itt.hour = tm->tm_hour;
1416 itt.day = tm->tm_mday;
1417 itt.month = tm->tm_mon + 1;
1418 itt.year = tm->tm_year+ 1900;
1427 get_offset (icaltimezone *zone)
1430 struct icaltimetype tt;
1432 time_t now = time(NULL);
1434 gmtime_r ((const time_t *) &now, &local);
1435 tt = tm_to_icaltimetype (&local);
1436 offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1441 /** Returns a single builtin timezone, given its offset from UTC */
1443 icaltimezone_get_builtin_timezone_from_offset (int offset, const char *tzname)
1445 icaltimezone *zone=NULL;
1448 if (!builtin_timezones)
1449 icaltimezone_init_builtin_timezones ();
1452 return &utc_timezone;
1457 count = builtin_timezones->num_elements;
1459 for (i=0; i<count; i++) {
1461 zone = icalarray_element_at (builtin_timezones, i);
1462 icaltimezone_load_builtin_timezone (zone);
1464 z_offset = get_offset(zone);
1466 if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1473 /** Returns a single builtin timezone, given its TZID. */
1475 icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1477 int num_slashes = 0;
1478 const char *p, *zone_tzid;
1481 if (!tzid || !tzid[0])
1484 /* Check that the TZID starts with our unique prefix. */
1485 if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
1488 /* Get the location, which is after the 3rd '/' character. */
1490 for (p = tzid; *p; p++) {
1493 if (num_slashes == 3)
1498 if (num_slashes != 3)
1503 /* Now we can use the function to get the builtin timezone from the
1505 zone = icaltimezone_get_builtin_timezone (p);
1509 /* Check that the builtin TZID matches exactly. We don't want to return
1510 a different version of the VTIMEZONE. */
1511 zone_tzid = icaltimezone_get_tzid (zone);
1512 if (!strcmp (zone_tzid, tzid))
1519 /** Returns the special UTC timezone. */
1521 icaltimezone_get_utc_timezone (void)
1523 if (!builtin_timezones)
1524 icaltimezone_init_builtin_timezones ();
1526 return &utc_timezone;
1531 /** This initializes the builtin timezone data, i.e. the
1532 builtin_timezones array and the special UTC timezone. It should be
1533 called before any code that uses the timezone functions. */
1535 icaltimezone_init_builtin_timezones (void)
1537 /* Initialize the special UTC timezone. */
1538 utc_timezone.tzid = (char *)"UTC";
1541 pthread_mutex_lock(&builtin_mutex);
1543 if (!builtin_timezones)
1544 icaltimezone_parse_zone_tab ();
1546 pthread_mutex_unlock(&builtin_mutex);
1551 parse_coord (char *coord,
1558 sscanf (coord + 1, "%2d%2d", degrees, minutes);
1560 sscanf (coord + 1, "%3d%2d", degrees, minutes);
1562 sscanf (coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1564 sscanf (coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1566 fprintf (stderr, "Invalid coordinate: %s\n", coord);
1570 if (coord [0] == '-')
1571 *degrees = -*degrees;
1575 fetch_lat_long_from_string (const char *str, int *latitude_degrees, int *latitude_minutes, int *latitude_seconds,
1576 int *longitude_degrees, int *longitude_minutes, int *longitude_seconds, char *location)
1579 char *sptr, *lat, *lon, *loc, *temp;
1581 /* We need to parse the latitude/longitude co-ordinates and location fields */
1582 sptr = (char *) str;
1583 while (*sptr != '\t')
1586 while (*sptr != '\t')
1589 lat = (char *) malloc (len + 1);
1590 lat = strncpy (lat, temp, len);
1592 while (*sptr != '\t')
1596 while (!isspace (*sptr))
1599 location = strncpy (location, loc, len);
1600 location [len] = '\0';
1602 #if defined(sun) && defined(__SVR4)
1603 /* Handle EET, MET and WET in zone_sun.tab. */
1604 if (!strcmp (location, "Europe/")) {
1605 while (*sptr != '\t')
1608 while (!isspace (*sptr))
1611 location = strncpy (location, loc, len);
1612 location [len] = '\0';
1617 while (*lon != '+' && *lon != '-')
1620 if (parse_coord (lat, (int)(lon - lat),
1623 latitude_seconds) == 1 ||
1624 parse_coord (lon, (int)strlen(lon),
1627 longitude_seconds) == 1) {
1637 /** This parses the zones.tab file containing the names and locations
1638 of the builtin timezones. It creates the builtin_timezones array
1639 which is an icalarray of icaltimezone structs. It only fills in the
1640 location, latitude and longtude fields; the rest are left
1641 blank. The VTIMEZONE component is loaded later if it is needed. The
1642 timezones in the zones.tab file are sorted by their name, which is
1643 useful for binary searches. */
1645 icaltimezone_parse_zone_tab (void)
1649 char buf[1024]; /* Used to store each line of zones.tab as it is read. */
1650 char location[1024]; /* Stores the city name when parsing buf. */
1651 size_t filename_len;
1652 int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0;
1653 int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0;
1656 icalerror_assert (builtin_timezones == NULL,
1657 "Parsing zones.tab file multiple times");
1659 builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1661 #ifndef USE_BUILTIN_TZDATA
1662 filename_len = strlen ((char *) icaltzutil_get_zone_directory()) + strlen (ZONES_TAB_SYSTEM_FILENAME)
1665 filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1669 filename = (char*) malloc (filename_len);
1671 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1674 #ifndef USE_BUILTIN_TZDATA
1675 snprintf (filename, filename_len, "%s/%s", icaltzutil_get_zone_directory (),
1676 ZONES_TAB_SYSTEM_FILENAME);
1678 snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1679 ZONES_TAB_FILENAME);
1682 fp = fopen (filename, "r");
1685 icalerror_set_errno(ICAL_FILE_ERROR);
1689 while (fgets (buf, sizeof(buf), fp)) {
1690 if (*buf == '#') continue;
1692 #ifdef USE_BUILTIN_TZDATA
1693 /* The format of each line is: "latitude longitude location". */
1694 if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
1695 &latitude_degrees, &latitude_minutes,
1697 &longitude_degrees, &longitude_minutes,
1700 fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1704 if (fetch_lat_long_from_string (buf, &latitude_degrees, &latitude_minutes,
1706 &longitude_degrees, &longitude_minutes, &longitude_seconds,
1708 fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1713 icaltimezone_init (&zone);
1714 zone.location = strdup (location);
1716 if (latitude_degrees >= 0)
1717 zone.latitude = (double) latitude_degrees
1718 + (double) latitude_minutes / 60
1719 + (double) latitude_seconds / 3600;
1721 zone.latitude = (double) latitude_degrees
1722 - (double) latitude_minutes / 60
1723 - (double) latitude_seconds / 3600;
1725 if (longitude_degrees >= 0)
1726 zone.longitude = (double) longitude_degrees
1727 + (double) longitude_minutes / 60
1728 + (double) longitude_seconds / 3600;
1730 zone.longitude = (double) longitude_degrees
1731 - (double) longitude_minutes / 60
1732 - (double) longitude_seconds / 3600;
1734 icalarray_append (builtin_timezones, &zone);
1737 printf ("Found zone: %s %f %f\n",
1738 location, zone.latitude, zone.longitude);
1746 icaltimezone_release_zone_tab (void)
1749 icalarray *mybuiltin_timezones = builtin_timezones;
1751 if (builtin_timezones == NULL)
1753 builtin_timezones = NULL;
1754 for (i = 0; i < mybuiltin_timezones->num_elements; i++)
1755 free ( ((icaltimezone*)icalarray_element_at(mybuiltin_timezones, i))->location);
1756 icalarray_free (mybuiltin_timezones);
1759 /** Loads the builtin VTIMEZONE data for the given timezone. */
1761 icaltimezone_load_builtin_timezone (icaltimezone *zone)
1763 icalcomponent *subcomp;
1765 /* If the location isn't set, it isn't a builtin timezone. */
1766 if (!zone->location || !zone->location[0])
1770 pthread_mutex_lock(&builtin_mutex);
1771 if (zone->component)
1775 #ifdef USE_BUILTIN_TZDATA
1778 icalcomponent *comp;
1779 unsigned int filename_len;
1783 filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1785 filename = (char*) malloc (filename_len);
1787 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1791 snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1794 fp = fopen (filename, "r");
1797 icalerror_set_errno(ICAL_FILE_ERROR);
1802 /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1803 this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1804 fprintf(stderr, "** WARNING ** %s: %d %s\n", __FILE__, __LINE__, icalerror_strerror(icalerrno));
1807 parser = icalparser_new ();
1808 icalparser_set_gen_data (parser, fp);
1809 comp = icalparser_parse (parser, icaltimezone_load_get_line_fn);
1810 icalparser_free (parser);
1813 /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1814 subcomp = icalcomponent_get_first_component (comp,
1815 ICAL_VTIMEZONE_COMPONENT);
1817 subcomp = icaltzutil_fetch_timezone (zone->location);
1821 icalerror_set_errno(ICAL_PARSE_ERROR);
1825 icaltimezone_get_vtimezone_properties (zone, subcomp);
1827 #ifdef USE_BUILTIN_TZDATA
1828 icalcomponent_remove_component(comp,subcomp);
1829 icalcomponent_free(comp);
1834 pthread_mutex_unlock(&builtin_mutex);
1839 #ifdef USE_BUILTIN_TZDATA
1840 /** Callback used from icalparser_parse() */
1842 icaltimezone_load_get_line_fn (char *s,
1846 return fgets (s, (int)size, (FILE*) data);
1857 * This outputs a list of timezone changes for the given timezone to the
1858 * given file, up to the maximum year given. We compare this output with the
1859 * output from 'vzic --dump-changes' to make sure that we are consistent.
1860 * (vzic is the Olson timezone database to VTIMEZONE converter.)
1862 * The output format is:
1864 * Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1866 * The Date and Time fields specify the time change in UTC.
1868 * The UTC Offset is for local (wall-clock) time. It is the amount of time
1869 * to add to UTC to get local time.
1872 icaltimezone_dump_changes (icaltimezone *zone,
1876 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1877 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1878 icaltimezonechange *zone_change;
1882 /* Make sure the changes array is expanded up to the given time. */
1883 icaltimezone_ensure_coverage (zone, max_year);
1886 printf ("Num changes: %i\n", zone->changes->num_elements);
1890 for (change_num = 0; (unsigned int)change_num < zone->changes->num_elements; change_num++) {
1891 zone_change = icalarray_element_at (zone->changes, change_num);
1893 if (zone_change->year > max_year)
1896 fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1898 zone_change->day, months[zone_change->month - 1],
1900 zone_change->hour, zone_change->minute, zone_change->second);
1902 /* Wall Clock Time offset from UTC. */
1903 format_utc_offset (zone_change->utc_offset, buffer);
1904 fprintf (fp, "\t%s", buffer);
1912 /** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1913 buffer should have space for 8 characters. */
1915 format_utc_offset (int utc_offset,
1918 const char *sign = "+";
1919 int hours, minutes, seconds;
1921 if (utc_offset < 0) {
1922 utc_offset = -utc_offset;
1926 hours = utc_offset / 3600;
1927 minutes = (utc_offset % 3600) / 60;
1928 seconds = utc_offset % 60;
1930 /* Sanity check. Standard timezone offsets shouldn't be much more than 12
1931 hours, and daylight saving shouldn't change it by more than a few hours.
1932 (The maximum offset is 15 hours 56 minutes at present.) */
1933 if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
1934 || seconds < 0 || seconds >= 60) {
1935 fprintf (stderr, "Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1936 hours, minutes, seconds);
1940 snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
1942 snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
1945 static const char* get_zone_directory(void)
1948 return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1950 wchar_t wbuffer[1000];
1952 char buffer[1000], zoneinfodir[1000], dirname[1000];
1954 wchar_t zoneinfodir[1000], dirname[1000];
1957 static char *cache = NULL;
1959 char *dirslash, *zislash;
1961 wchar_t *dirslash, *zislash;
1965 if (zone_files_directory)
1966 return zone_files_directory;
1971 /* Get the filename of the application */
1972 if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
1973 return ZONEINFO_DIRECTORY;
1975 /*wince supports only unicode*/
1977 /* Convert to system codepage */
1978 if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
1979 NULL, &used_default) ||
1981 /* Failed, try 8.3 format */
1982 if (!GetShortPathNameW (wbuffer, wbuffer,
1983 sizeof (wbuffer) / sizeof (wbuffer[0])) ||
1984 !WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
1985 NULL, &used_default) ||
1987 return ZONEINFO_DIRECTORY;
1990 /* Look for the zoneinfo directory somewhere in the path where
1991 * the app is installed. If the path to the app is
1993 * C:\opt\evo-2.6\bin\evolution-2.6.exe
1995 * and the compile-time ZONEINFO_DIRECTORY is
1997 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
1999 * we check the pathnames:
2001 * C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2002 * C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
2003 * C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
2004 * C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo <===
2005 * C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
2006 * C:\opt\evo-2.6/zoneinfo
2007 * C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2008 * C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
2009 * C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
2010 * C:\opt/share/evolution-data-server-1.6/zoneinfo
2011 * C:\opt/evolution-data-server-1.6/zoneinfo
2013 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2014 * C:/target/evo/share/evolution-data-server-1.6/zoneinfo
2015 * C:/evo/share/evolution-data-server-1.6/zoneinfo
2016 * C:/share/evolution-data-server-1.6/zoneinfo
2017 * C:/evolution-data-server-1.6/zoneinfo
2020 * In Evolution's case, we would get a match already at the
2021 * fourth pathname check.
2024 /* Strip away basename of app .exe first */
2026 dirslash = _mbsrchr (buffer, '\\');
2028 dirslash = wcsrchr (wbuffer, L'\\');
2038 while ((dirslash = wcsrchr (wbuffer, '\\'))) {
2039 /* Strip one more directory from app .exe location */
2042 MultiByteToWideChar(CP_ACP,0,ZONEINFO_DIRECTORY,-1,zoneinfodir,1000);
2044 while ((zislash = wcschr (zoneinfodir, L'/'))) {
2046 wcscpy (dirname, wbuffer);
2047 wcscat (dirname, "/");
2048 wcscat (dirname, zislash + 1);
2049 if (stat (dirname, &st) == 0 &&
2050 S_ISDIR (st.st_mode)) {
2051 cache = wce_wctomb (dirname);
2057 while ((dirslash = _mbsrchr (buffer, '\\'))) {
2058 /* Strip one more directory from app .exe location */
2061 strcpy (zoneinfodir, ZONEINFO_DIRECTORY);
2062 while ((zislash = _mbschr (zoneinfodir, '/'))) {
2064 strcpy (dirname, buffer);
2065 strcat (dirname, "/");
2066 strcat (dirname, zislash + 1);
2067 if (stat (dirname, &st) == 0 &&
2068 S_ISDIR (st.st_mode)) {
2069 cache = strdup (dirname);
2075 return ZONEINFO_DIRECTORY;
2079 void set_zone_directory(char *path)
2081 if (zone_files_directory)
2082 free_zone_directory();
2083 zone_files_directory = malloc(strlen(path)+1);
2084 if ( zone_files_directory != NULL )
2086 strcpy(zone_files_directory,path);
2090 void free_zone_directory(void)
2092 if ( zone_files_directory != NULL )
2094 free(zone_files_directory);
2095 zone_files_directory = NULL;
2099 void icaltimezone_set_tzid_prefix(const char *new_prefix)
2102 ical_tzid_prefix = new_prefix;