bump to 1.0.0 and clean up spec file
[platform/upstream/libical.git] / src / libical / icaltimezone.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /*======================================================================
3  FILE: icaltimezone.c
4  CREATOR: Damon Chaplin 15 March 2001
5
6  $Id: icaltimezone.c,v 1.44 2008-02-03 16:10:46 dothebart Exp $
7  $Locker:  $
8
9  (C) COPYRIGHT 2001, Damon Chaplin
10
11  This program is free software; you can redistribute it and/or modify
12  it under the terms of either: 
13
14     The LGPL as published by the Free Software Foundation, version
15     2.1, available at: http://www.fsf.org/copyleft/lesser.html
16
17   Or:
18
19     The Mozilla Public License Version 1.0. You may obtain a copy of
20     the License at http://www.mozilla.org/MPL/
21
22
23 ======================================================================*/
24
25 /** @file icaltimezone.c
26  *  @brief implementation of timezone handling routines
27  **/
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
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"
45
46 #include <sys/stat.h>
47
48 #ifdef HAVE_PTHREAD
49 #include <pthread.h>
50 static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER;
51 #endif
52
53 #ifdef WIN32
54 #ifndef _WIN32_WCE
55 #include <mbstring.h>
56 #endif
57 #include <windows.h>
58 /* Undef the similar macro from pthread.h, it doesn't check if
59  * gmtime() returns NULL.
60  */
61 #undef gmtime_r
62
63 /* The gmtime() in Microsoft's C library is MT-safe */
64 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
65
66 // MSVC lacks the POSIX macro S_ISDIR, however it's a trivial one:
67 #ifndef S_ISDIR
68 #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
69 #endif
70 #endif
71
72 #if defined(_MSC_VER)
73 #define snprintf _snprintf
74 #define strcasecmp stricmp
75 #endif
76
77 /** This is the toplevel directory where the timezone data is installed in. */
78 #define ZONEINFO_DIRECTORY      PACKAGE_DATA_DIR "/zoneinfo"
79
80 /** The prefix we use to uniquely identify TZIDs.
81     It must begin and end with forward slashes.
82  */
83 const char *ical_tzid_prefix =  "/freeassociation.sourceforge.net/";
84
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"
88
89 /** This is the number of years of extra coverage we do when expanding
90     the timezone changes. */
91 #define ICALTIMEZONE_EXTRA_COVERAGE     5
92
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
96
97 typedef struct _icaltimezonechange      icaltimezonechange;
98
99 struct _icaltimezonechange {
100     int          utc_offset;
101     /**< The offset to add to UTC to get local time, in seconds. */
102
103     int          prev_utc_offset;
104     /**< The offset to add to UTC, before this change, in seconds. */
105
106     int          year;          /**< Actual year, e.g. 2001. */
107     int          month;         /**< 1 (Jan) to 12 (Dec). */
108     int          day;
109     int          hour;
110     int          minute;
111     int          second;
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. */
115
116     int          is_daylight;
117     /**< Whether this is STANDARD or DAYLIGHT time. */
118 };
119
120
121 /** An array of icaltimezones for the builtin timezones. */
122 static icalarray *builtin_timezones = NULL;
123
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 };
126
127 static char* zone_files_directory = NULL;
128
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,
133                                                  int             end_year);
134 static void  icaltimezone_expand_vtimezone      (icalcomponent  *comp,
135                                                  int             end_year,
136                                                  icalarray      *changes);
137 static int   icaltimezone_compare_change_fn     (const void     *elem1,
138                                                  const void     *elem2);
139
140 static int   icaltimezone_find_nearby_change    (icaltimezone *zone,
141                                                  icaltimezonechange *change);
142
143 static void  icaltimezone_adjust_change         (icaltimezonechange *tt,
144                                                  int             days,
145                                                  int             hours,
146                                                  int             minutes,
147                                                  int             seconds);
148
149 static void  icaltimezone_init                  (icaltimezone *zone);
150
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);
156
157
158 static void  icaltimezone_load_builtin_timezone (icaltimezone *zone);
159
160 static void  icaltimezone_ensure_coverage       (icaltimezone *zone,
161                                                  int             end_year);
162
163
164 static void  icaltimezone_init_builtin_timezones(void);
165
166 static void  icaltimezone_parse_zone_tab        (void);
167
168 #ifdef USE_BUILTIN_TZDATA
169 static char* icaltimezone_load_get_line_fn      (char           *s,
170                                                  size_t          size,
171                                                  void           *data);
172 #endif
173
174 static void  format_utc_offset                  (int             utc_offset,
175                                                  char           *buffer);
176 static const char* get_zone_directory(void);
177
178 /** Creates a new icaltimezone. */
179 icaltimezone*
180 icaltimezone_new                        (void)
181 {
182     icaltimezone *zone;
183
184     zone = (icaltimezone*) malloc (sizeof (icaltimezone));
185     if (!zone) {
186         icalerror_set_errno (ICAL_NEWFAILED_ERROR);
187         return NULL;
188     }
189
190     icaltimezone_init (zone);
191
192     return zone;
193 }
194
195 icaltimezone *
196 icaltimezone_copy                       (icaltimezone *originalzone)
197 {
198     icaltimezone *zone;
199
200     zone = (icaltimezone*) malloc (sizeof (icaltimezone));
201     if (!zone) {
202         icalerror_set_errno (ICAL_NEWFAILED_ERROR);
203         return NULL;
204     }
205
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);
215     
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;
219
220     return zone;
221 }
222
223 /** Frees all memory used for the icaltimezone. */
224 void
225 icaltimezone_free                       (icaltimezone *zone,
226                                          int           free_struct)
227 {
228     icaltimezone_reset (zone);
229     if (free_struct)
230         free (zone);
231 }
232
233
234 /** Resets the icaltimezone to the initial state, freeing most of the fields. */
235 static void
236 icaltimezone_reset                      (icaltimezone *zone)
237 {
238     if (zone->tzid)
239                 free (zone->tzid);
240     if (zone->location)
241                 free (zone->location);
242     if (zone->tznames)
243                 free (zone->tznames);
244     if (zone->component)
245                 icalcomponent_free (zone->component);
246     if (zone->changes)
247                 icalarray_free (zone->changes);
248         
249     icaltimezone_init (zone);
250 }
251
252
253 /** Initializes an icaltimezone. */
254 static void
255 icaltimezone_init                       (icaltimezone   *zone)
256 {
257     zone->tzid = NULL;
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;
264     zone->end_year = 0;
265     zone->changes = NULL;
266 }
267
268
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
273    any old values. */
274 static int
275 icaltimezone_get_vtimezone_properties   (icaltimezone *zone,
276                                          icalcomponent  *component)
277 {
278     icalproperty *prop;
279     const char *tzid;
280  
281     prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
282     if (!prop)
283         return 0;
284
285     /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
286     tzid = icalproperty_get_tzid (prop);
287     if (!tzid)
288         return 0;
289
290     if (zone->tzid) free(zone->tzid);
291     zone->tzid = strdup (tzid);
292
293     if (zone->component) icalcomponent_free(zone->component);
294     zone->component = component;
295
296     if (zone->location) free(zone->location);
297     zone->location = icaltimezone_get_location_from_vtimezone (component);
298
299     if (zone->tznames) free(zone->tznames);
300     zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
301
302     return 1;
303 }
304
305 /** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
306 static char*
307 icaltimezone_get_location_from_vtimezone (icalcomponent *component)
308 {
309     icalproperty *prop;
310     const char *location;
311     const char *name;
312
313     prop = icalcomponent_get_first_property (component,
314                                              ICAL_LOCATION_PROPERTY);
315     if (prop) {
316         location = icalproperty_get_location (prop);
317         if (location)
318             return strdup (location);
319     }
320
321     prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
322     while (prop) {
323         name = icalproperty_get_x_name (prop);
324         if (name && !strcasecmp (name, "X-LIC-LOCATION")) {
325             location = icalproperty_get_x (prop);
326             if (location)
327                 return strdup (location);
328         }
329         prop = icalcomponent_get_next_property (component,
330                                                 ICAL_X_PROPERTY);
331     }
332
333     return NULL;
334 }
335
336
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
341    the caller. */
342 static char*
343 icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
344 {
345     icalcomponent *comp;
346     icalcomponent_kind type;
347     icalproperty *prop;
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;
354
355     standard_max_date = icaltime_null_time();
356     daylight_max_date = icaltime_null_time();
357
358     /* Step through the STANDARD & DAYLIGHT subcomponents. */
359     comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
360     while (comp) {
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;
366
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);
370             while (prop) {
371                 switch (icalproperty_isa (prop)) {
372                 case ICAL_TZNAME_PROPERTY:
373                     current_tzname = icalproperty_get_tzname (prop);
374                     break;
375
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;
380
381                     break;
382
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;
387
388                     break;
389
390                 default:
391                     break;
392                 }
393
394                 prop = icalcomponent_get_next_property (comp,
395                                                         ICAL_ANY_PROPERTY);
396             }
397
398             if (current_tzname) {
399                 if (type == ICAL_XSTANDARD_COMPONENT) {
400                     if (!standard_tzname
401                         || icaltime_compare (current_max_date,
402                                              standard_max_date) > 0) {
403                         standard_max_date = current_max_date;
404                         standard_tzname = current_tzname;
405                     }
406                 } else {
407                     if (!daylight_tzname
408                         || icaltime_compare (current_max_date,
409                                              daylight_max_date) > 0) {
410                         daylight_max_date = current_max_date;
411                         daylight_tzname = current_tzname;
412                     }
413                 }
414             }
415         }
416
417         comp = icalcomponent_get_next_component (component,
418                                                  ICAL_ANY_COMPONENT);
419     }
420
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"))
424         return NULL;
425
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;
430         char *tznames;
431
432         if (!strcmp (standard_tzname, daylight_tzname))
433             return strdup (standard_tzname);
434
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);
441         return tznames;
442     } else {
443         const char *tznames;
444
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;
448     }
449 }
450
451
452 static void
453 icaltimezone_ensure_coverage            (icaltimezone *zone,
454                                          int             end_year)
455 {
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;
459
460     int changes_end_year;
461
462     icaltimezone_load_builtin_timezone (zone);
463
464     if (icaltimezone_minimum_expansion_year == -1) {
465         struct icaltimetype today = icaltime_today();
466         icaltimezone_minimum_expansion_year = today.year;
467     }
468
469     changes_end_year = end_year;
470     if (changes_end_year < icaltimezone_minimum_expansion_year)
471         changes_end_year = icaltimezone_minimum_expansion_year;
472
473     changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
474
475     if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
476         changes_end_year = ICALTIMEZONE_MAX_YEAR;
477
478     if (!zone->changes || zone->end_year < end_year)
479         icaltimezone_expand_changes (zone, changes_end_year);
480 }
481
482
483 static void
484 icaltimezone_expand_changes             (icaltimezone *zone,
485                                          int             end_year)
486 {
487     icalarray *changes;
488     icalcomponent *comp;
489
490 #if 0
491     printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
492 #endif
493
494     changes = icalarray_new (sizeof (icaltimezonechange), 32);
495     if (!changes)
496         return;
497
498     /* Scan the STANDARD and DAYLIGHT subcomponents. */
499     comp = icalcomponent_get_first_component (zone->component,
500                                               ICAL_ANY_COMPONENT);
501     while (comp) {
502         icaltimezone_expand_vtimezone (comp, end_year, changes);
503         comp = icalcomponent_get_next_component (zone->component,
504                                                  ICAL_ANY_COMPONENT);
505     }
506
507     /* Sort the changes. We may have duplicates but I don't think it will
508        matter. */
509     icalarray_sort (changes, icaltimezone_compare_change_fn);
510
511     if (zone->changes)
512         icalarray_free (zone->changes);
513
514     zone->changes = changes;
515     zone->end_year = end_year;
516 }
517
518
519 static void
520 icaltimezone_expand_vtimezone           (icalcomponent  *comp,
521                                          int             end_year,
522                                          icalarray      *changes)
523 {
524     icaltimezonechange change;
525     icalproperty *prop;
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;
532
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;
539     else 
540         return;
541
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);
546     while (prop) {
547         switch (icalproperty_isa (prop)) {
548         case ICAL_DTSTART_PROPERTY:
549             dtstart = icalproperty_get_dtstart (prop);
550             found_dtstart = 1;
551             break;
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;
556             break;
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;
561             break;
562         case ICAL_RDATE_PROPERTY:
563             has_rdate = 1;
564             break;
565         case ICAL_RRULE_PROPERTY:
566             has_rrule = 1;
567             break;
568         default:
569             /* Just ignore any other properties. */
570             break;
571         }
572
573         prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
574     }
575
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;
582     }
583
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)
587         return;
588
589 #if 0
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);
593 #endif
594
595     /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add
596        a single change for the DTSTART. */
597     if (!has_rrule) {
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;
604
605         /* Convert to UTC. */
606         icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
607
608 #if 0
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);
612 #endif
613
614         /* Add the change to the array. */
615         icalarray_append (changes, &change);
616     }
617
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)) {
621 #if 0
622         printf ("Expanding property...\n");
623 #endif
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
631                the DTSTART. */
632             if (icaltime_is_date(rdate.time)) {
633                 change.hour   = dtstart.hour;
634                 change.minute = dtstart.minute;
635                 change.second = dtstart.second;
636             } else {
637                 change.hour   = rdate.time.hour;
638                 change.minute = rdate.time.minute;
639                 change.second = rdate.time.second;
640
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);
647             }
648
649 #if 0
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);
653 #endif
654
655             icalarray_append (changes, &change);
656             break;
657         case ICAL_RRULE_PROPERTY:
658             rrule = icalproperty_get_rrule (prop);
659
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
662                it itself. */
663             if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
664 #if 0
665                 printf ("  Found RRULE UNTIL in UTC.\n");
666 #endif
667
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;
674             }
675
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;
684         
685 #if 0
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);
689 #endif
690         
691         icaltimezone_adjust_change (&change, 0, 0, 0,
692                                     -change.prev_utc_offset);
693         
694         icalarray_append (changes, &change);
695
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)
701             continue;
702                 if (occ.year > end_year || icaltime_is_null_time (occ))
703                     break;
704
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;
711
712 #if 0
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);
716 #endif
717
718                 icaltimezone_adjust_change (&change, 0, 0, 0,
719                                             -change.prev_utc_offset);
720
721                 icalarray_append (changes, &change);
722             }
723
724             icalrecur_iterator_free (rrule_iterator);
725             break;
726         default:
727             break;
728         }
729
730         prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
731     }
732 }
733
734
735 /** A function to compare 2 icaltimezonechange elements, used for qsort(). */
736 static int
737 icaltimezone_compare_change_fn          (const void     *elem1,
738                                          const void     *elem2)
739 {
740     const icaltimezonechange *change1, *change2;
741     int retval;
742
743     change1 = (const icaltimezonechange *)elem1;
744     change2 = (const icaltimezonechange *)elem2;
745
746     if (change1->year < change2->year)
747         retval = -1;
748     else if (change1->year > change2->year)
749         retval = 1;
750
751     else if (change1->month < change2->month)
752         retval = -1;
753     else if (change1->month > change2->month)
754         retval = 1;
755
756     else if (change1->day < change2->day)
757         retval = -1;
758     else if (change1->day > change2->day)
759         retval = 1;
760
761     else if (change1->hour < change2->hour)
762         retval = -1;
763     else if (change1->hour > change2->hour)
764         retval = 1;
765
766     else if (change1->minute < change2->minute)
767         retval = -1;
768     else if (change1->minute > change2->minute)
769         retval = 1;
770
771     else if (change1->second < change2->second)
772         retval = -1;
773     else if (change1->second > change2->second)
774         retval = 1;
775
776     else
777         retval = 0;
778
779     return retval;
780 }
781
782
783
784 void
785 icaltimezone_convert_time               (struct icaltimetype *tt,
786                                          icaltimezone *from_zone,
787                                          icaltimezone *to_zone)
788 {
789     int utc_offset, is_daylight;
790
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)
794         return;
795
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);
799
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,
803                                                           &is_daylight);
804     tt->is_daylight = is_daylight;
805     icaltime_adjust (tt, 0, 0, 0, utc_offset);
806 }
807
808
809
810
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. */
817 int
818 icaltimezone_get_utc_offset             (icaltimezone   *zone,
819                                          struct icaltimetype    *tt,
820                                          int            *is_daylight)
821 {
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;
825     int want_daylight;
826
827     if (tt == NULL)
828         return 0;
829
830     if (is_daylight)
831         *is_daylight = 0;
832
833     /* For local times and UTC return 0. */
834     if (zone == NULL || zone == &utc_timezone)
835         return 0;
836
837     /* Use the builtin icaltimezone if possible. */
838     if (zone->builtin_timezone)
839         zone = zone->builtin_timezone;
840
841     /* Make sure the changes array is expanded up to the given time. */
842     icaltimezone_ensure_coverage (zone, tt->year);
843
844     if (!zone->changes || zone->changes->num_elements == 0)
845         return 0;
846
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;
855
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);
859
860     /* Sanity check. */
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");
865
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);
869     step = 1;
870     change_num_to_use = -1;
871     for (;;) {
872         /* Copy the change, so we can adjust it. */
873         tmp_change = *zone_change;
874
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);
884         } else {
885             icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
886                                         tmp_change.prev_utc_offset);
887         }
888
889         cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
890
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
895            backwards. */
896         if (cmp >= 0)
897             change_num_to_use = change_num;
898         else
899             step = -1;
900
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
903            we exit the loop. */
904         if (step == -1 && change_num_to_use != -1)
905             break;
906
907         change_num += step;
908
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) {
912             if (is_daylight)
913                 *is_daylight = ! tmp_change.is_daylight;
914             return tmp_change.prev_utc_offset;
915         }
916
917         if ((unsigned int)change_num >= zone->changes->num_elements)
918             break;
919
920         zone_change = icalarray_element_at (zone->changes, change_num);
921     }
922
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");
926
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);
930
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);
936
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);
944
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;
950
951 #if 0
952             if (zone_change->is_daylight == prev_zone_change->is_daylight)
953                 printf (" **** Same is_daylight setting\n");
954 #endif
955
956             if (zone_change->is_daylight != want_daylight
957                 && prev_zone_change->is_daylight == want_daylight)
958                 zone_change = prev_zone_change;
959         }
960     }
961
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. */
964     if (is_daylight)
965         *is_daylight = zone_change->is_daylight;
966     return zone_change->utc_offset;
967 }
968
969
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. */
976 int
977 icaltimezone_get_utc_offset_of_utc_time (icaltimezone   *zone,
978                                          struct icaltimetype    *tt,
979                                          int            *is_daylight)
980 {
981     icaltimezonechange *zone_change, tt_change, tmp_change;
982     int change_num, step, change_num_to_use;
983
984     if (is_daylight)
985         *is_daylight = 0;
986
987     /* For local times and UTC return 0. */
988     if (zone == NULL || zone == &utc_timezone)
989         return 0;
990
991     /* Use the builtin icaltimezone if possible. */
992     if (zone->builtin_timezone)
993         zone = zone->builtin_timezone;
994
995     /* Make sure the changes array is expanded up to the given time. */
996     icaltimezone_ensure_coverage (zone, tt->year);
997
998     if (!zone->changes || zone->changes->num_elements == 0)
999         return 0;
1000
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;
1009
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);
1013
1014     /* Sanity check. */
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");
1019
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);
1023     step = 1;
1024     change_num_to_use = -1;
1025     for (;;) {
1026         /* Copy the change and adjust it to UTC. */
1027         tmp_change = *zone_change;
1028
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
1033            backwards. */
1034         if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
1035             change_num_to_use = change_num;
1036         else
1037             step = -1;
1038
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)
1043             break;
1044
1045         change_num += step;
1046
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. */
1049         if (change_num < 0)
1050             return 0;
1051
1052         if ((unsigned int)change_num >= zone->changes->num_elements)
1053             break;
1054
1055         zone_change = icalarray_element_at (zone->changes, change_num);
1056     }
1057
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");
1061
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);
1065     if (is_daylight)
1066         *is_daylight = zone_change->is_daylight;
1067
1068     return zone_change->utc_offset;
1069 }
1070
1071
1072 /** Returns the index of a timezone change which is close to the time
1073    given in change. */
1074 static int
1075 icaltimezone_find_nearby_change         (icaltimezone   *zone,
1076                                          icaltimezonechange     *change)
1077 {
1078     icaltimezonechange *zone_change;
1079     int lower, upper, middle, cmp;
1080                                          
1081     /* Do a simple binary search. */
1082     lower = middle = 0;
1083     upper = zone->changes->num_elements;
1084
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);
1089         if (cmp == 0)
1090             break;
1091         else if (cmp < 0)
1092             upper = middle;
1093         else
1094             lower = middle + 1;
1095     }
1096
1097     return middle;
1098 }
1099
1100
1101
1102
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. */
1106 static void
1107 icaltimezone_adjust_change              (icaltimezonechange *tt,
1108                                          int             days,
1109                                          int             hours,
1110                                          int             minutes,
1111                                          int             seconds)
1112 {
1113     int second, minute, hour, day;
1114     int minutes_overflow, hours_overflow, days_overflow;
1115     int days_in_month;
1116
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) {
1122         tt->second += 60;
1123         minutes_overflow--;
1124     }
1125
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) {
1131         tt->minute += 60;
1132         hours_overflow--;
1133     }
1134
1135     /* Add on the hours. */
1136     hour = tt->hour + hours + hours_overflow;
1137     tt->hour = hour % 24;
1138     days_overflow = hour / 24;
1139     if (tt->hour < 0) {
1140         tt->hour += 24;
1141         days_overflow--;
1142     }
1143
1144     /* Add on the days. */
1145     day = tt->day + days + days_overflow;
1146     if (day > 0) {
1147         for (;;) {
1148             days_in_month = icaltime_days_in_month (tt->month, tt->year);
1149             if (day <= days_in_month)
1150                 break;
1151
1152             tt->month++;
1153             if (tt->month >= 13) {
1154                 tt->year++;
1155                 tt->month = 1;
1156             }
1157
1158             day -= days_in_month;
1159         }
1160     } else {
1161         while (day <= 0) {
1162             if (tt->month == 1) {
1163                 tt->year--;
1164                 tt->month = 12;
1165             } else {
1166                 tt->month--;
1167             }
1168
1169             day += icaltime_days_in_month (tt->month, tt->year);
1170         }
1171     }
1172     tt->day = day;
1173 }
1174
1175
1176 const char*
1177 icaltimezone_get_tzid                   (icaltimezone *zone)
1178 {
1179     /* If this is a floating time, without a timezone, return NULL. */
1180     if (!zone)
1181         return NULL;
1182
1183     icaltimezone_load_builtin_timezone (zone);
1184
1185     return zone->tzid;
1186 }
1187
1188
1189 const char*
1190 icaltimezone_get_location               (icaltimezone *zone)
1191 {
1192     /* If this is a floating time, without a timezone, return NULL. */
1193     if (!zone)
1194         return NULL;
1195
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;
1199 }
1200
1201
1202 const char*
1203 icaltimezone_get_tznames                (icaltimezone *zone)
1204 {
1205     /* If this is a floating time, without a timezone, return NULL. */
1206     if (!zone)
1207         return NULL;
1208
1209     icaltimezone_load_builtin_timezone (zone);
1210
1211     return zone->tznames;
1212 }
1213
1214
1215 /** Returns the latitude of a builtin timezone. */
1216 double
1217 icaltimezone_get_latitude               (icaltimezone *zone)
1218 {
1219     /* If this is a floating time, without a timezone, return 0. */
1220     if (!zone)
1221         return 0.0;
1222
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;
1226 }
1227
1228
1229 /** Returns the longitude of a builtin timezone. */
1230 double
1231 icaltimezone_get_longitude              (icaltimezone *zone)
1232 {
1233     /* If this is a floating time, without a timezone, return 0. */
1234     if (!zone)
1235         return 0.0;
1236
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;
1240 }
1241
1242
1243 /** Returns the VTIMEZONE component of a timezone. */
1244 icalcomponent*
1245 icaltimezone_get_component              (icaltimezone *zone)
1246 {
1247     /* If this is a floating time, without a timezone, return NULL. */
1248     if (!zone)
1249         return NULL;
1250
1251     icaltimezone_load_builtin_timezone (zone);
1252
1253     return zone->component;
1254 }
1255
1256
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. */
1260 int
1261 icaltimezone_set_component              (icaltimezone *zone,
1262                                          icalcomponent  *comp)
1263 {
1264     icaltimezone_reset (zone);
1265     return icaltimezone_get_vtimezone_properties (zone, comp);
1266 }
1267
1268
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. */
1273 const char*
1274 icaltimezone_get_display_name           (icaltimezone   *zone)
1275 {
1276         const char *display_name;
1277
1278         display_name = icaltimezone_get_location (zone);
1279         if (!display_name)
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. */
1287                 if (display_name
1288                     && !strncmp (display_name, ical_tzid_prefix, strlen(ical_tzid_prefix))) {
1289                     /* Get the location, which is after the 3rd '/' char. */
1290                     const char *p;
1291                     int num_slashes = 0;
1292                     for (p = display_name; *p; p++) {
1293                         if (*p == '/') {
1294                             num_slashes++;
1295                             if (num_slashes == 3)
1296                                 return p + 1;
1297                         }
1298                     }
1299                 }
1300         }
1301
1302         return display_name;
1303 }
1304
1305 icalarray*
1306 icaltimezone_array_new                  (void)
1307 {
1308     return icalarray_new (sizeof (icaltimezone), 16);
1309 }
1310
1311
1312 void
1313 icaltimezone_array_append_from_vtimezone (icalarray     *timezones,
1314                                           icalcomponent *child)
1315 {
1316     icaltimezone zone;
1317
1318     icaltimezone_init (&zone);
1319     if (icaltimezone_get_vtimezone_properties (&zone, child))
1320         icalarray_append (timezones, &zone);
1321 }
1322
1323
1324 void
1325 icaltimezone_array_free                 (icalarray      *timezones)
1326 {
1327     icaltimezone *zone;
1328     int i;
1329
1330         if ( timezones )
1331         {
1332             for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1333                 zone = icalarray_element_at (timezones, i);
1334                 icaltimezone_free (zone, 0);
1335                 }
1336
1337                 icalarray_free (timezones);
1338         }
1339 }
1340
1341
1342 /*
1343  * BUILTIN TIMEZONE HANDLING
1344  */
1345
1346
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. */
1351 icalarray*
1352 icaltimezone_get_builtin_timezones      (void)
1353 {
1354     if (!builtin_timezones)
1355         icaltimezone_init_builtin_timezones ();
1356
1357     return builtin_timezones;
1358 }
1359
1360 /** Release builtin timezone memory */
1361 void
1362 icaltimezone_free_builtin_timezones(void)
1363 {
1364         icaltimezone_array_free(builtin_timezones);
1365         builtin_timezones = 0;
1366 }
1367
1368
1369 /** Returns a single builtin timezone, given its Olson city name. */
1370 icaltimezone*
1371 icaltimezone_get_builtin_timezone       (const char *location)
1372 {
1373     icalcomponent *comp;
1374     icaltimezone *zone;
1375     unsigned int lower;
1376     const char *zone_location;
1377
1378     if (!location || !location[0])
1379         return NULL;
1380
1381     if (!builtin_timezones)
1382         icaltimezone_init_builtin_timezones ();
1383
1384     if (strcmp (location, "UTC") == 0 || strcmp (location, "GMT") == 0)
1385         return &utc_timezone;
1386     
1387 #if 0
1388     /* Do a simple binary search. */
1389     lower = middle = 0;
1390     upper = builtin_timezones->num_elements;
1391
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);
1397         if (cmp == 0)
1398             return zone;
1399         else if (cmp < 0)
1400             upper = middle;
1401         else
1402             lower = middle + 1;
1403     }
1404 #endif
1405
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)
1412                 return zone;
1413     }
1414
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);
1418     if (comp) {
1419         icaltimezone tz;
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);
1424         } else {
1425             icalcomponent_free (comp);
1426         }
1427     }
1428
1429     return NULL;
1430 }
1431
1432 static struct icaltimetype
1433 tm_to_icaltimetype (struct tm *tm)
1434 {
1435         struct icaltimetype itt;
1436
1437         memset (&itt, 0, sizeof (struct icaltimetype));
1438
1439         itt.second = tm->tm_sec;
1440         itt.minute = tm->tm_min;
1441         itt.hour = tm->tm_hour;
1442
1443         itt.day = tm->tm_mday;
1444         itt.month = tm->tm_mon + 1;
1445         itt.year = tm->tm_year+ 1900;
1446     
1447         itt.is_utc = 0;
1448         itt.is_date = 0; 
1449         
1450         return itt;
1451 }
1452
1453 static int
1454 get_offset (icaltimezone *zone)
1455 {
1456     struct tm local;
1457     struct icaltimetype tt;
1458     int offset;
1459     time_t now = time(NULL);
1460         
1461     gmtime_r ((const time_t *) &now, &local);
1462     tt = tm_to_icaltimetype (&local);
1463     offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1464
1465     return offset;
1466 }
1467
1468 /** Returns a single builtin timezone, given its offset from UTC */
1469 icaltimezone*
1470 icaltimezone_get_builtin_timezone_from_offset   (int offset, const char *tzname)
1471 {
1472     icaltimezone *zone=NULL;
1473     int count, i;
1474     
1475     if (!builtin_timezones)
1476         icaltimezone_init_builtin_timezones ();
1477
1478     if (offset==0)
1479         return &utc_timezone;
1480
1481     if (!tzname)
1482         return NULL;
1483
1484     count = builtin_timezones->num_elements;
1485
1486     for (i=0; i<count; i++) {
1487         int z_offset;
1488         zone = icalarray_element_at (builtin_timezones, i);
1489         icaltimezone_load_builtin_timezone (zone);
1490         
1491         z_offset = get_offset(zone);
1492
1493         if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1494             return zone;
1495     }
1496     
1497     return NULL;
1498 }
1499
1500 /** Returns a single builtin timezone, given its TZID. */
1501 icaltimezone*
1502 icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1503 {
1504     int num_slashes = 0;
1505     const char *p, *zone_tzid;
1506     icaltimezone *zone;
1507
1508     if (!tzid || !tzid[0])
1509         return NULL;
1510
1511     if (strcmp (tzid, "UTC") == 0 || strcmp (tzid, "GMT") == 0) {
1512         return icaltimezone_get_builtin_timezone(tzid);
1513     }
1514
1515     /* Check that the TZID starts with our unique prefix. */
1516     if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
1517         return NULL;
1518
1519     /* Get the location, which is after the 3rd '/' character. */
1520     for (p = tzid; *p; p++) {
1521         if (*p == '/') {
1522             num_slashes++;
1523             if (num_slashes == 3)
1524                 break;
1525         }
1526     }
1527
1528     if (num_slashes != 3)
1529         return NULL;
1530
1531     p++;
1532
1533     /* Now we can use the function to get the builtin timezone from the
1534        location string. */
1535     zone = icaltimezone_get_builtin_timezone (p);
1536     if (!zone)
1537         return NULL;
1538
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))
1543         return zone;
1544     else
1545         return NULL;
1546 }
1547
1548
1549 /** Returns the special UTC timezone. */
1550 icaltimezone*
1551 icaltimezone_get_utc_timezone           (void)
1552 {
1553     if (!builtin_timezones)
1554         icaltimezone_init_builtin_timezones ();
1555
1556     return &utc_timezone;
1557 }
1558
1559
1560
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. */
1564 static void
1565 icaltimezone_init_builtin_timezones     (void)
1566 {
1567     /* Initialize the special UTC timezone. */
1568     utc_timezone.tzid = (char *)"UTC";
1569
1570 #ifdef HAVE_PTHREAD
1571     pthread_mutex_lock(&builtin_mutex);
1572 #endif
1573     if (!builtin_timezones)
1574         icaltimezone_parse_zone_tab ();
1575 #ifdef HAVE_PTHREAD
1576     pthread_mutex_unlock(&builtin_mutex);
1577 #endif
1578 }
1579
1580 static int
1581 parse_coord                     (char           *coord,
1582                                  int             len,
1583                                  int            *degrees, 
1584                                  int            *minutes,
1585                                  int            *seconds)
1586 {
1587         if (len == 5)
1588                 sscanf (coord + 1, "%2d%2d", degrees, minutes);
1589         else if (len == 6)
1590                 sscanf (coord + 1, "%3d%2d", degrees, minutes);
1591         else if (len == 7)
1592                 sscanf (coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1593         else if (len == 8)
1594                 sscanf (coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1595         else {
1596                 fprintf (stderr, "Invalid coordinate: %s\n", coord);
1597                 return 1;
1598         }
1599
1600         if (coord [0] == '-')
1601                 *degrees = -*degrees;
1602         return 0;
1603 }
1604 static int
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)
1607 {
1608         size_t len;
1609         char *sptr, *lat, *lon, *loc, *temp;
1610
1611         /* We need to parse the latitude/longitude co-ordinates and location fields  */
1612         sptr = (char *) str;
1613         while (*sptr != '\t')
1614                 sptr++;
1615         temp = ++sptr;
1616         while (*sptr != '\t')
1617                 sptr++;
1618         len = sptr-temp;
1619         lat = (char *) malloc (len + 1);
1620         lat = strncpy (lat, temp, len);
1621         lat [len] = '\0';
1622         while (*sptr != '\t')
1623                 sptr++;
1624         
1625         loc = ++sptr;
1626         while (!isspace (*sptr))
1627                 sptr++;
1628         len = sptr - loc;
1629         location = strncpy (location, loc, len);
1630         location [len] = '\0';
1631
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')
1636             sptr++;
1637         loc = ++sptr;
1638         while (!isspace (*sptr))
1639             sptr++;
1640         len = sptr - loc;
1641         location = strncpy (location, loc, len);
1642         location [len] = '\0';
1643     }
1644 #endif
1645
1646         lon = lat + 1;
1647         while (*lon != '+' && *lon != '-')
1648                 lon++;
1649
1650         if (parse_coord (lat, (int)(lon - lat),
1651                          latitude_degrees,
1652                          latitude_minutes,
1653                          latitude_seconds) == 1 ||
1654             parse_coord (lon, (int)strlen(lon),
1655                          longitude_degrees,
1656                          longitude_minutes,
1657                          longitude_seconds) == 1) {
1658             free(lat);
1659             return 1;
1660         }
1661         
1662         free (lat);
1663
1664         return 0;
1665 }
1666
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. */
1674 static void
1675 icaltimezone_parse_zone_tab             (void)
1676 {
1677     char *filename;
1678     FILE *fp;
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;
1684     icaltimezone zone;
1685
1686     icalerror_assert (builtin_timezones == NULL,
1687                       "Parsing zones.tab file multiple times");
1688
1689     builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1690
1691 #ifndef USE_BUILTIN_TZDATA
1692     filename_len = strlen ((char *) icaltzutil_get_zone_directory()) + strlen (ZONES_TAB_SYSTEM_FILENAME)
1693         + 2;
1694 #else    
1695     filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1696         + 2;
1697 #endif    
1698
1699     filename = (char*) malloc (filename_len);
1700     if (!filename) {
1701         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1702         return;
1703     }
1704 #ifndef USE_BUILTIN_TZDATA
1705     snprintf (filename, filename_len, "%s/%s", icaltzutil_get_zone_directory (),
1706               ZONES_TAB_SYSTEM_FILENAME);
1707 #else    
1708     snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1709               ZONES_TAB_FILENAME);
1710 #endif    
1711
1712     fp = fopen (filename, "r");
1713     free (filename);
1714     if (!fp) {
1715         icalerror_set_errno(ICAL_FILE_ERROR);
1716         return;
1717     }
1718
1719     while (fgets (buf, sizeof(buf), fp)) {
1720         if (*buf == '#') continue;
1721
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,
1726                     &latitude_seconds,
1727                     &longitude_degrees, &longitude_minutes,
1728                     &longitude_seconds,
1729                     location) != 7) {
1730             fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1731             continue;
1732         }
1733 #else 
1734         if (fetch_lat_long_from_string (buf, &latitude_degrees, &latitude_minutes, 
1735                                 &latitude_seconds,
1736                                 &longitude_degrees, &longitude_minutes, &longitude_seconds,
1737                                 location)) {
1738             fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1739             continue;
1740         }
1741 #endif  
1742
1743         icaltimezone_init (&zone);
1744         zone.location = strdup (location);
1745
1746         if (latitude_degrees >= 0)
1747             zone.latitude = (double) latitude_degrees
1748                 + (double) latitude_minutes / 60
1749                 + (double) latitude_seconds / 3600;
1750         else
1751             zone.latitude = (double) latitude_degrees
1752                 - (double) latitude_minutes / 60
1753                 - (double) latitude_seconds / 3600;
1754
1755         if (longitude_degrees >= 0)
1756             zone.longitude = (double) longitude_degrees
1757                 + (double) longitude_minutes / 60
1758                 + (double) longitude_seconds / 3600;
1759         else
1760             zone.longitude = (double) longitude_degrees
1761                 - (double) longitude_minutes / 60
1762                 - (double) longitude_seconds / 3600;
1763
1764         icalarray_append (builtin_timezones, &zone);
1765
1766 #if 0
1767         printf ("Found zone: %s %f %f\n",
1768                 location, zone.latitude, zone.longitude);
1769 #endif
1770     }
1771
1772     fclose (fp);
1773 }
1774
1775 void
1776 icaltimezone_release_zone_tab           (void)
1777 {
1778     unsigned int i;
1779     icalarray *mybuiltin_timezones = builtin_timezones;
1780
1781     if (builtin_timezones == NULL)
1782         return;
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);
1787 }
1788
1789 /** Loads the builtin VTIMEZONE data for the given timezone. */
1790 static void
1791 icaltimezone_load_builtin_timezone      (icaltimezone *zone)
1792 {
1793     icalcomponent *subcomp;
1794
1795             /* If the location isn't set, it isn't a builtin timezone. */
1796     if (!zone->location || !zone->location[0])
1797         return;
1798
1799 #ifdef HAVE_PTHREAD
1800     pthread_mutex_lock(&builtin_mutex);
1801     if (zone->component)
1802        goto out;
1803 #else
1804 #ifdef USE_BUILTIN_TZDATA
1805     if (zone->component)
1806        return;
1807 #endif
1808 #endif
1809
1810 #ifdef USE_BUILTIN_TZDATA
1811     {
1812     char *filename;
1813     icalcomponent *comp;
1814     unsigned int filename_len;
1815     FILE *fp;
1816     icalparser *parser;
1817
1818     filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1819
1820     filename = (char*) malloc (filename_len);
1821     if (!filename) {
1822         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1823         goto out;
1824     }
1825
1826     snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1827               zone->location);
1828
1829     fp = fopen (filename, "r");
1830     free (filename);
1831     if (!fp) {
1832         icalerror_set_errno(ICAL_FILE_ERROR);
1833         goto out;
1834     }
1835
1836         
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));
1840         */
1841
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);
1846         fclose (fp);
1847
1848     /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1849     subcomp = icalcomponent_get_first_component (comp,
1850                                                  ICAL_VTIMEZONE_COMPONENT);
1851 #else
1852         subcomp = icaltzutil_fetch_timezone (zone->location);
1853 #endif  
1854
1855     if (!subcomp) {
1856         icalerror_set_errno(ICAL_PARSE_ERROR);
1857         goto out;
1858     }
1859
1860     icaltimezone_get_vtimezone_properties (zone, subcomp);
1861
1862 #ifdef USE_BUILTIN_TZDATA
1863     icalcomponent_remove_component(comp,subcomp);
1864     icalcomponent_free(comp);
1865     }
1866 #endif 
1867
1868  out:
1869 #ifdef HAVE_PTHREAD
1870     pthread_mutex_unlock(&builtin_mutex);
1871 #endif
1872     return;
1873 }
1874
1875
1876 #ifdef USE_BUILTIN_TZDATA
1877 /** Callback used from icalparser_parse() */
1878 static char *
1879 icaltimezone_load_get_line_fn           (char           *s,
1880                                          size_t          size,
1881                                          void           *data)
1882 {
1883     return fgets (s, (int)size, (FILE*) data);
1884 }
1885 #endif
1886
1887
1888
1889 /*
1890  * DEBUGGING
1891  */
1892
1893 /**
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.)
1898  * 
1899  * The output format is:
1900  *
1901  *      Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1902  *
1903  * The Date and Time fields specify the time change in UTC.
1904  *
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.
1907  */
1908 int
1909 icaltimezone_dump_changes               (icaltimezone *zone,
1910                                          int             max_year,
1911                                          FILE           *fp)
1912 {
1913     static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1914                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1915     icaltimezonechange *zone_change;
1916     int change_num;
1917     char buffer[8];
1918
1919     /* Make sure the changes array is expanded up to the given time. */
1920     icaltimezone_ensure_coverage (zone, max_year);
1921
1922 #if 0
1923     printf ("Num changes: %i\n", zone->changes->num_elements);
1924 #endif
1925
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);
1928
1929         if (zone_change->year > max_year)
1930             break;
1931
1932         fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1933                 zone->location,
1934                 zone_change->day, months[zone_change->month - 1],
1935                 zone_change->year,
1936                 zone_change->hour, zone_change->minute, zone_change->second);
1937
1938         /* Wall Clock Time offset from UTC. */
1939         format_utc_offset (zone_change->utc_offset, buffer);
1940         fprintf (fp, "\t%s", buffer);
1941
1942         fprintf (fp, "\n");
1943     }
1944         return 1;
1945 }
1946
1947
1948 /** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1949    buffer should have space for 8 characters. */
1950 static void
1951 format_utc_offset                       (int             utc_offset,
1952                                          char           *buffer)
1953 {
1954   const char *sign = "+";
1955   int hours, minutes, seconds;
1956
1957   if (utc_offset < 0) {
1958     utc_offset = -utc_offset;
1959     sign = "-";
1960   }
1961
1962   hours = utc_offset / 3600;
1963   minutes = (utc_offset % 3600) / 60;
1964   seconds = utc_offset % 60;
1965
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);
1973   }
1974
1975   if (seconds == 0)
1976     snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
1977   else
1978     snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
1979 }
1980
1981 static const char* get_zone_directory(void)
1982 {
1983 #ifndef WIN32
1984         return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1985 #else
1986         wchar_t wbuffer[1000];
1987 #ifndef _WIN32_WCE
1988         char buffer[1000], zoneinfodir[1000], dirname[1000];
1989 #else
1990         wchar_t zoneinfodir[1000], dirname[1000];
1991 #endif
1992         int used_default;
1993         static char *cache = NULL;
1994 #ifndef _WIN32_WCE
1995         char *dirslash, *zislash;
1996 #else
1997         wchar_t *dirslash, *zislash;
1998 #endif
1999         struct stat st;
2000
2001         if (zone_files_directory)
2002             return zone_files_directory;
2003
2004         if (cache)
2005             return cache;
2006
2007         /* Get the filename of the application */
2008         if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
2009             return ZONEINFO_DIRECTORY;
2010
2011 /*wince supports only unicode*/
2012 #ifndef _WIN32_WCE
2013         /* Convert to system codepage */
2014         if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
2015                                   NULL, &used_default) ||
2016             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) ||
2022                 used_default)
2023                 return ZONEINFO_DIRECTORY;
2024         }
2025 #endif
2026         /* Look for the zoneinfo directory somewhere in the path where
2027          * the app is installed. If the path to the app is
2028          *
2029          *      C:\opt\evo-2.6\bin\evolution-2.6.exe 
2030          *
2031          * and the compile-time ZONEINFO_DIRECTORY is
2032          *
2033          *      C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
2034          *
2035          * we check the pathnames:
2036          *
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
2048          *      C:\opt/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
2054          *      C:/zoneinfo
2055          *
2056          * In Evolution's case, we would get a match already at the
2057          * fourth pathname check.
2058          */
2059
2060         /* Strip away basename of app .exe first */
2061 #ifndef _WIN32_WCE
2062         dirslash = _mbsrchr (buffer, '\\');
2063 #else
2064         dirslash = wcsrchr (wbuffer, L'\\');
2065 #endif
2066         if (dirslash)
2067 #ifndef _WIN32_WCE
2068             *dirslash = '\0';
2069 #else
2070             *dirslash = L'\0';
2071 #endif
2072
2073 #ifdef _WIN32_WCE
2074         while ((dirslash = wcsrchr (wbuffer, '\\'))) {
2075             /* Strip one more directory from app .exe location */
2076             *dirslash = L'\0';
2077             
2078         MultiByteToWideChar(CP_ACP,0,ZONEINFO_DIRECTORY,-1,zoneinfodir,1000);
2079         
2080             while ((zislash = wcschr (zoneinfodir, L'/'))) {
2081                 *zislash = 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);
2088                     return cache;
2089                 }
2090             }
2091         }
2092 #else
2093         while ((dirslash = _mbsrchr (buffer, '\\'))) {
2094             /* Strip one more directory from app .exe location */
2095             *dirslash = '\0';
2096             
2097             strcpy (zoneinfodir, ZONEINFO_DIRECTORY);
2098             while ((zislash = _mbschr (zoneinfodir, '/'))) {
2099                 *zislash = '.';
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);
2106                     return cache;
2107                 }
2108             }
2109         }
2110 #endif
2111         return ZONEINFO_DIRECTORY;
2112 #endif
2113 }
2114
2115 void set_zone_directory(char *path)
2116 {
2117         if (zone_files_directory)
2118                 free_zone_directory();
2119         zone_files_directory = malloc(strlen(path)+1);
2120         if ( zone_files_directory != NULL )
2121         {
2122                 strcpy(zone_files_directory,path);
2123         }
2124 }
2125
2126 void free_zone_directory(void)
2127 {
2128         if ( zone_files_directory != NULL )
2129         {
2130                 free(zone_files_directory);
2131                 zone_files_directory = NULL;
2132         }
2133 }
2134
2135 void icaltimezone_set_tzid_prefix(const char *new_prefix)
2136 {
2137         if (new_prefix) {
2138                 ical_tzid_prefix = new_prefix;
2139         }
2140 }