Imported Upstream version 0.48
[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, *tzname;
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_recurrence = 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         case ICAL_RRULE_PROPERTY:
564             has_recurrence = 1;
565             break;
566         default:
567             /* Just ignore any other properties. */
568             break;
569         }
570
571         prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
572     }
573
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;
580     }
581
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)
585         return;
586
587 #if 0
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);
591 #endif
592
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;
602
603         /* Convert to UTC. */
604         icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
605
606 #if 0
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);
610 #endif
611
612         /* Add the change to the array. */
613         icalarray_append (changes, &change);
614         return;
615     }
616
617     /* The component has recurrence data, so we expand that now. */
618     prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
619     while (prop) {
620 #if 0
621         printf ("Expanding property...\n");
622 #endif
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
630                the DTSTART. */
631             if (icaltime_is_date(rdate.time)) {
632                 change.hour   = dtstart.hour;
633                 change.minute = dtstart.minute;
634                 change.second = dtstart.second;
635             } else {
636                 change.hour   = rdate.time.hour;
637                 change.minute = rdate.time.minute;
638                 change.second = rdate.time.second;
639
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);
646             }
647
648 #if 0
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);
652 #endif
653
654             icalarray_append (changes, &change);
655             break;
656         case ICAL_RRULE_PROPERTY:
657             rrule = icalproperty_get_rrule (prop);
658
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
661                it itself. */
662             if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
663 #if 0
664                 printf ("  Found RRULE UNTIL in UTC.\n");
665 #endif
666
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;
673             }
674
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))
679                     break;
680
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;
687
688 #if 0
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);
692 #endif
693
694                 icaltimezone_adjust_change (&change, 0, 0, 0,
695                                             -change.prev_utc_offset);
696
697                 icalarray_append (changes, &change);
698             }
699
700             icalrecur_iterator_free (rrule_iterator);
701             break;
702         default:
703             break;
704         }
705
706         prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
707     }
708 }
709
710
711 /** A function to compare 2 icaltimezonechange elements, used for qsort(). */
712 static int
713 icaltimezone_compare_change_fn          (const void     *elem1,
714                                          const void     *elem2)
715 {
716     const icaltimezonechange *change1, *change2;
717     int retval;
718
719     change1 = (const icaltimezonechange *)elem1;
720     change2 = (const icaltimezonechange *)elem2;
721
722     if (change1->year < change2->year)
723         retval = -1;
724     else if (change1->year > change2->year)
725         retval = 1;
726
727     else if (change1->month < change2->month)
728         retval = -1;
729     else if (change1->month > change2->month)
730         retval = 1;
731
732     else if (change1->day < change2->day)
733         retval = -1;
734     else if (change1->day > change2->day)
735         retval = 1;
736
737     else if (change1->hour < change2->hour)
738         retval = -1;
739     else if (change1->hour > change2->hour)
740         retval = 1;
741
742     else if (change1->minute < change2->minute)
743         retval = -1;
744     else if (change1->minute > change2->minute)
745         retval = 1;
746
747     else if (change1->second < change2->second)
748         retval = -1;
749     else if (change1->second > change2->second)
750         retval = 1;
751
752     else
753         retval = 0;
754
755     return retval;
756 }
757
758
759
760 void
761 icaltimezone_convert_time               (struct icaltimetype *tt,
762                                          icaltimezone *from_zone,
763                                          icaltimezone *to_zone)
764 {
765     int utc_offset, is_daylight;
766
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)
770         return;
771
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);
775
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,
779                                                           &is_daylight);
780     tt->is_daylight = is_daylight;
781     icaltime_adjust (tt, 0, 0, 0, utc_offset);
782 }
783
784
785
786
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. */
793 int
794 icaltimezone_get_utc_offset             (icaltimezone   *zone,
795                                          struct icaltimetype    *tt,
796                                          int            *is_daylight)
797 {
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;
801     int want_daylight;
802
803     if (tt == NULL)
804         return 0;
805
806     if (is_daylight)
807         *is_daylight = 0;
808
809     /* For local times and UTC return 0. */
810     if (zone == NULL || zone == &utc_timezone)
811         return 0;
812
813     /* Use the builtin icaltimezone if possible. */
814     if (zone->builtin_timezone)
815         zone = zone->builtin_timezone;
816
817     /* Make sure the changes array is expanded up to the given time. */
818     icaltimezone_ensure_coverage (zone, tt->year);
819
820     if (!zone->changes || zone->changes->num_elements == 0)
821         return 0;
822
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;
831
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);
835
836     /* Sanity check. */
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");
841
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);
845     step = 1;
846     change_num_to_use = -1;
847     for (;;) {
848         /* Copy the change, so we can adjust it. */
849         tmp_change = *zone_change;
850
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);
860         } else {
861             icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
862                                         tmp_change.prev_utc_offset);
863         }
864
865         cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
866
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
871            backwards. */
872         if (cmp >= 0)
873             change_num_to_use = change_num;
874         else
875             step = -1;
876
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
879            we exit the loop. */
880         if (step == -1 && change_num_to_use != -1)
881             break;
882
883         change_num += step;
884
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. */
887         if (change_num < 0)
888             return 0;
889
890         if ((unsigned int)change_num >= zone->changes->num_elements)
891             break;
892
893         zone_change = icalarray_element_at (zone->changes, change_num);
894     }
895
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");
899
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);
903
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);
909
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);
917
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;
923
924 #if 0
925             if (zone_change->is_daylight == prev_zone_change->is_daylight)
926                 printf (" **** Same is_daylight setting\n");
927 #endif
928
929             if (zone_change->is_daylight != want_daylight
930                 && prev_zone_change->is_daylight == want_daylight)
931                 zone_change = prev_zone_change;
932         }
933     }
934
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. */
937     if (is_daylight)
938         *is_daylight = zone_change->is_daylight;
939     return zone_change->utc_offset;
940 }
941
942
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. */
949 int
950 icaltimezone_get_utc_offset_of_utc_time (icaltimezone   *zone,
951                                          struct icaltimetype    *tt,
952                                          int            *is_daylight)
953 {
954     icaltimezonechange *zone_change, tt_change, tmp_change;
955     int change_num, step, change_num_to_use;
956
957     if (is_daylight)
958         *is_daylight = 0;
959
960     /* For local times and UTC return 0. */
961     if (zone == NULL || zone == &utc_timezone)
962         return 0;
963
964     /* Use the builtin icaltimezone if possible. */
965     if (zone->builtin_timezone)
966         zone = zone->builtin_timezone;
967
968     /* Make sure the changes array is expanded up to the given time. */
969     icaltimezone_ensure_coverage (zone, tt->year);
970
971     if (!zone->changes || zone->changes->num_elements == 0)
972         return 0;
973
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;
982
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);
986
987     /* Sanity check. */
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");
992
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);
996     step = 1;
997     change_num_to_use = -1;
998     for (;;) {
999         /* Copy the change and adjust it to UTC. */
1000         tmp_change = *zone_change;
1001
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
1006            backwards. */
1007         if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
1008             change_num_to_use = change_num;
1009         else
1010             step = -1;
1011
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)
1016             break;
1017
1018         change_num += step;
1019
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. */
1022         if (change_num < 0)
1023             return 0;
1024
1025         if ((unsigned int)change_num >= zone->changes->num_elements)
1026             break;
1027
1028         zone_change = icalarray_element_at (zone->changes, change_num);
1029     }
1030
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");
1034
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);
1038     if (is_daylight)
1039         *is_daylight = zone_change->is_daylight;
1040
1041     return zone_change->utc_offset;
1042 }
1043
1044
1045 /** Returns the index of a timezone change which is close to the time
1046    given in change. */
1047 static int
1048 icaltimezone_find_nearby_change         (icaltimezone   *zone,
1049                                          icaltimezonechange     *change)
1050 {
1051     icaltimezonechange *zone_change;
1052     int lower, upper, middle, cmp;
1053                                          
1054     /* Do a simple binary search. */
1055     lower = middle = 0;
1056     upper = zone->changes->num_elements;
1057
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);
1062         if (cmp == 0)
1063             break;
1064         else if (cmp < 0)
1065             upper = middle;
1066         else
1067             lower = middle + 1;
1068     }
1069
1070     return middle;
1071 }
1072
1073
1074
1075
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. */
1079 static void
1080 icaltimezone_adjust_change              (icaltimezonechange *tt,
1081                                          int             days,
1082                                          int             hours,
1083                                          int             minutes,
1084                                          int             seconds)
1085 {
1086     int second, minute, hour, day;
1087     int minutes_overflow, hours_overflow, days_overflow;
1088     int days_in_month;
1089
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) {
1095         tt->second += 60;
1096         minutes_overflow--;
1097     }
1098
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) {
1104         tt->minute += 60;
1105         hours_overflow--;
1106     }
1107
1108     /* Add on the hours. */
1109     hour = tt->hour + hours + hours_overflow;
1110     tt->hour = hour % 24;
1111     days_overflow = hour / 24;
1112     if (tt->hour < 0) {
1113         tt->hour += 24;
1114         days_overflow--;
1115     }
1116
1117     /* Add on the days. */
1118     day = tt->day + days + days_overflow;
1119     if (day > 0) {
1120         for (;;) {
1121             days_in_month = icaltime_days_in_month (tt->month, tt->year);
1122             if (day <= days_in_month)
1123                 break;
1124
1125             tt->month++;
1126             if (tt->month >= 13) {
1127                 tt->year++;
1128                 tt->month = 1;
1129             }
1130
1131             day -= days_in_month;
1132         }
1133     } else {
1134         while (day <= 0) {
1135             if (tt->month == 1) {
1136                 tt->year--;
1137                 tt->month = 12;
1138             } else {
1139                 tt->month--;
1140             }
1141
1142             day += icaltime_days_in_month (tt->month, tt->year);
1143         }
1144     }
1145     tt->day = day;
1146 }
1147
1148
1149 const char*
1150 icaltimezone_get_tzid                   (icaltimezone *zone)
1151 {
1152     /* If this is a floating time, without a timezone, return NULL. */
1153     if (!zone)
1154         return NULL;
1155
1156     icaltimezone_load_builtin_timezone (zone);
1157
1158     return zone->tzid;
1159 }
1160
1161
1162 const char*
1163 icaltimezone_get_location               (icaltimezone *zone)
1164 {
1165     /* If this is a floating time, without a timezone, return NULL. */
1166     if (!zone)
1167         return NULL;
1168
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;
1172 }
1173
1174
1175 const char*
1176 icaltimezone_get_tznames                (icaltimezone *zone)
1177 {
1178     /* If this is a floating time, without a timezone, return NULL. */
1179     if (!zone)
1180         return NULL;
1181
1182     icaltimezone_load_builtin_timezone (zone);
1183
1184     return zone->tznames;
1185 }
1186
1187
1188 /** Returns the latitude of a builtin timezone. */
1189 double
1190 icaltimezone_get_latitude               (icaltimezone *zone)
1191 {
1192     /* If this is a floating time, without a timezone, return 0. */
1193     if (!zone)
1194         return 0.0;
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->latitude;
1199 }
1200
1201
1202 /** Returns the longitude of a builtin timezone. */
1203 double
1204 icaltimezone_get_longitude              (icaltimezone *zone)
1205 {
1206     /* If this is a floating time, without a timezone, return 0. */
1207     if (!zone)
1208         return 0.0;
1209
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;
1213 }
1214
1215
1216 /** Returns the VTIMEZONE component of a timezone. */
1217 icalcomponent*
1218 icaltimezone_get_component              (icaltimezone *zone)
1219 {
1220     /* If this is a floating time, without a timezone, return NULL. */
1221     if (!zone)
1222         return NULL;
1223
1224     icaltimezone_load_builtin_timezone (zone);
1225
1226     return zone->component;
1227 }
1228
1229
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. */
1233 int
1234 icaltimezone_set_component              (icaltimezone *zone,
1235                                          icalcomponent  *comp)
1236 {
1237     icaltimezone_reset (zone);
1238     return icaltimezone_get_vtimezone_properties (zone, comp);
1239 }
1240
1241
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. */
1246 const char*
1247 icaltimezone_get_display_name           (icaltimezone   *zone)
1248 {
1249         const char *display_name;
1250
1251         display_name = icaltimezone_get_location (zone);
1252         if (!display_name)
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. */
1260                 if (display_name
1261                     && !strncmp (display_name, ical_tzid_prefix, strlen(ical_tzid_prefix))) {
1262                     /* Get the location, which is after the 3rd '/' char. */
1263                     const char *p;
1264                     int num_slashes = 0;
1265                     for (p = display_name; *p; p++) {
1266                         if (*p == '/') {
1267                             num_slashes++;
1268                             if (num_slashes == 3)
1269                                 return p + 1;
1270                         }
1271                     }
1272                 }
1273         }
1274
1275         return display_name;
1276 }
1277
1278 icalarray*
1279 icaltimezone_array_new                  (void)
1280 {
1281     return icalarray_new (sizeof (icaltimezone), 16);
1282 }
1283
1284
1285 void
1286 icaltimezone_array_append_from_vtimezone (icalarray     *timezones,
1287                                           icalcomponent *child)
1288 {
1289     icaltimezone zone;
1290
1291     icaltimezone_init (&zone);
1292     if (icaltimezone_get_vtimezone_properties (&zone, child))
1293         icalarray_append (timezones, &zone);
1294 }
1295
1296
1297 void
1298 icaltimezone_array_free                 (icalarray      *timezones)
1299 {
1300     icaltimezone *zone;
1301     int i;
1302
1303         if ( timezones )
1304         {
1305             for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1306                 zone = icalarray_element_at (timezones, i);
1307                 icaltimezone_free (zone, 0);
1308                 }
1309
1310                 icalarray_free (timezones);
1311         }
1312 }
1313
1314
1315 /*
1316  * BUILTIN TIMEZONE HANDLING
1317  */
1318
1319
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. */
1324 icalarray*
1325 icaltimezone_get_builtin_timezones      (void)
1326 {
1327     if (!builtin_timezones)
1328         icaltimezone_init_builtin_timezones ();
1329
1330     return builtin_timezones;
1331 }
1332
1333 /** Release builtin timezone memory */
1334 void
1335 icaltimezone_free_builtin_timezones(void)
1336 {
1337         icaltimezone_array_free(builtin_timezones);
1338         builtin_timezones = 0;
1339 }
1340
1341
1342 /** Returns a single builtin timezone, given its Olson city name. */
1343 icaltimezone*
1344 icaltimezone_get_builtin_timezone       (const char *location)
1345 {
1346     icalcomponent *comp;
1347     icaltimezone *zone;
1348     unsigned int lower;
1349     const char *zone_location;
1350
1351     if (!location || !location[0])
1352         return NULL;
1353
1354     if (!builtin_timezones)
1355         icaltimezone_init_builtin_timezones ();
1356
1357     if (!strcmp (location, "UTC"))
1358         return &utc_timezone;
1359     
1360 #if 0
1361     /* Do a simple binary search. */
1362     lower = middle = 0;
1363     upper = builtin_timezones->num_elements;
1364
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);
1370         if (cmp == 0)
1371             return zone;
1372         else if (cmp < 0)
1373             upper = middle;
1374         else
1375             lower = middle + 1;
1376     }
1377 #endif
1378
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)
1385                 return zone;
1386     }
1387
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);
1391     if (comp) {
1392         icaltimezone tz;
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);
1397         } else {
1398             icalcomponent_free (comp);
1399         }
1400     }
1401
1402     return NULL;
1403 }
1404
1405 static struct icaltimetype
1406 tm_to_icaltimetype (struct tm *tm)
1407 {
1408         struct icaltimetype itt;
1409
1410         memset (&itt, 0, sizeof (struct icaltimetype));
1411
1412         itt.second = tm->tm_sec;
1413         itt.minute = tm->tm_min;
1414         itt.hour = tm->tm_hour;
1415
1416         itt.day = tm->tm_mday;
1417         itt.month = tm->tm_mon + 1;
1418         itt.year = tm->tm_year+ 1900;
1419     
1420         itt.is_utc = 0;
1421         itt.is_date = 0; 
1422         
1423         return itt;
1424 }
1425
1426 static int
1427 get_offset (icaltimezone *zone)
1428 {
1429     struct tm local;
1430     struct icaltimetype tt;
1431     int offset;
1432     time_t now = time(NULL);
1433         
1434     gmtime_r ((const time_t *) &now, &local);
1435     tt = tm_to_icaltimetype (&local);
1436     offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1437
1438     return offset;
1439 }
1440
1441 /** Returns a single builtin timezone, given its offset from UTC */
1442 icaltimezone*
1443 icaltimezone_get_builtin_timezone_from_offset   (int offset, const char *tzname)
1444 {
1445     icaltimezone *zone=NULL;
1446     int count, i;
1447     
1448     if (!builtin_timezones)
1449         icaltimezone_init_builtin_timezones ();
1450
1451     if (offset==0)
1452         return &utc_timezone;
1453
1454     if (!tzname)
1455         return NULL;
1456
1457     count = builtin_timezones->num_elements;
1458
1459     for (i=0; i<count; i++) {
1460         int z_offset;
1461         zone = icalarray_element_at (builtin_timezones, i);
1462         icaltimezone_load_builtin_timezone (zone);
1463         
1464         z_offset = get_offset(zone);
1465
1466         if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1467             return zone;
1468     }
1469     
1470     return NULL;
1471 }
1472
1473 /** Returns a single builtin timezone, given its TZID. */
1474 icaltimezone*
1475 icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1476 {
1477     int num_slashes = 0;
1478     const char *p, *zone_tzid;
1479     icaltimezone *zone;
1480
1481     if (!tzid || !tzid[0])
1482         return NULL;
1483
1484     /* Check that the TZID starts with our unique prefix. */
1485     if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
1486         return NULL;
1487
1488     /* Get the location, which is after the 3rd '/' character. */
1489     p = tzid;
1490     for (p = tzid; *p; p++) {
1491         if (*p == '/') {
1492             num_slashes++;
1493             if (num_slashes == 3)
1494                 break;
1495         }
1496     }
1497
1498     if (num_slashes != 3)
1499         return NULL;
1500
1501     p++;
1502
1503     /* Now we can use the function to get the builtin timezone from the
1504        location string. */
1505     zone = icaltimezone_get_builtin_timezone (p);
1506     if (!zone)
1507         return NULL;
1508
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))
1513         return zone;
1514     else
1515         return NULL;
1516 }
1517
1518
1519 /** Returns the special UTC timezone. */
1520 icaltimezone*
1521 icaltimezone_get_utc_timezone           (void)
1522 {
1523     if (!builtin_timezones)
1524         icaltimezone_init_builtin_timezones ();
1525
1526     return &utc_timezone;
1527 }
1528
1529
1530
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. */
1534 static void
1535 icaltimezone_init_builtin_timezones     (void)
1536 {
1537     /* Initialize the special UTC timezone. */
1538     utc_timezone.tzid = (char *)"UTC";
1539
1540 #ifdef HAVE_PTHREAD
1541     pthread_mutex_lock(&builtin_mutex);
1542 #endif
1543     if (!builtin_timezones)
1544         icaltimezone_parse_zone_tab ();
1545 #ifdef HAVE_PTHREAD
1546     pthread_mutex_unlock(&builtin_mutex);
1547 #endif
1548 }
1549
1550 static int
1551 parse_coord                     (char           *coord,
1552                                  int             len,
1553                                  int            *degrees, 
1554                                  int            *minutes,
1555                                  int            *seconds)
1556 {
1557         if (len == 5)
1558                 sscanf (coord + 1, "%2d%2d", degrees, minutes);
1559         else if (len == 6)
1560                 sscanf (coord + 1, "%3d%2d", degrees, minutes);
1561         else if (len == 7)
1562                 sscanf (coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1563         else if (len == 8)
1564                 sscanf (coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1565         else {
1566                 fprintf (stderr, "Invalid coordinate: %s\n", coord);
1567                 return 1;
1568         }
1569
1570         if (coord [0] == '-')
1571                 *degrees = -*degrees;
1572         return 0;
1573 }
1574 static int
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)
1577 {
1578         size_t len;
1579         char *sptr, *lat, *lon, *loc, *temp;
1580
1581         /* We need to parse the latitude/longitude co-ordinates and location fields  */
1582         sptr = (char *) str;
1583         while (*sptr != '\t')
1584                 sptr++;
1585         temp = ++sptr;
1586         while (*sptr != '\t')
1587                 sptr++;
1588         len = sptr-temp;
1589         lat = (char *) malloc (len + 1);
1590         lat = strncpy (lat, temp, len);
1591         lat [len] = '\0';
1592         while (*sptr != '\t')
1593                 sptr++;
1594         
1595         loc = ++sptr;
1596         while (!isspace (*sptr))
1597                 sptr++;
1598         len = sptr - loc;
1599         location = strncpy (location, loc, len);
1600         location [len] = '\0';
1601
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')
1606             sptr++;
1607         loc = ++sptr;
1608         while (!isspace (*sptr))
1609             sptr++;
1610         len = sptr - loc;
1611         location = strncpy (location, loc, len);
1612         location [len] = '\0';
1613     }
1614 #endif
1615
1616         lon = lat + 1;
1617         while (*lon != '+' && *lon != '-')
1618                 lon++;
1619
1620         if (parse_coord (lat, (int)(lon - lat),
1621                          latitude_degrees,
1622                          latitude_minutes,
1623                          latitude_seconds) == 1 ||
1624             parse_coord (lon, (int)strlen(lon),
1625                          longitude_degrees,
1626                          longitude_minutes,
1627                          longitude_seconds) == 1) {
1628             free(lat);
1629             return 1;
1630         }
1631         
1632         free (lat);
1633
1634         return 0;
1635 }
1636
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. */
1644 static void
1645 icaltimezone_parse_zone_tab             (void)
1646 {
1647     char *filename;
1648     FILE *fp;
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;
1654     icaltimezone zone;
1655
1656     icalerror_assert (builtin_timezones == NULL,
1657                       "Parsing zones.tab file multiple times");
1658
1659     builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1660
1661 #ifndef USE_BUILTIN_TZDATA
1662     filename_len = strlen ((char *) icaltzutil_get_zone_directory()) + strlen (ZONES_TAB_SYSTEM_FILENAME)
1663         + 2;
1664 #else    
1665     filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1666         + 2;
1667 #endif    
1668
1669     filename = (char*) malloc (filename_len);
1670     if (!filename) {
1671         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1672         return;
1673     }
1674 #ifndef USE_BUILTIN_TZDATA
1675     snprintf (filename, filename_len, "%s/%s", icaltzutil_get_zone_directory (),
1676               ZONES_TAB_SYSTEM_FILENAME);
1677 #else    
1678     snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1679               ZONES_TAB_FILENAME);
1680 #endif    
1681
1682     fp = fopen (filename, "r");
1683     free (filename);
1684     if (!fp) {
1685         icalerror_set_errno(ICAL_FILE_ERROR);
1686         return;
1687     }
1688
1689     while (fgets (buf, sizeof(buf), fp)) {
1690         if (*buf == '#') continue;
1691
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,
1696                     &latitude_seconds,
1697                     &longitude_degrees, &longitude_minutes,
1698                     &longitude_seconds,
1699                     location) != 7) {
1700             fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1701             continue;
1702         }
1703 #else 
1704         if (fetch_lat_long_from_string (buf, &latitude_degrees, &latitude_minutes, 
1705                                 &latitude_seconds,
1706                                 &longitude_degrees, &longitude_minutes, &longitude_seconds,
1707                                 location)) {
1708             fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1709             continue;
1710         }
1711 #endif  
1712
1713         icaltimezone_init (&zone);
1714         zone.location = strdup (location);
1715
1716         if (latitude_degrees >= 0)
1717             zone.latitude = (double) latitude_degrees
1718                 + (double) latitude_minutes / 60
1719                 + (double) latitude_seconds / 3600;
1720         else
1721             zone.latitude = (double) latitude_degrees
1722                 - (double) latitude_minutes / 60
1723                 - (double) latitude_seconds / 3600;
1724
1725         if (longitude_degrees >= 0)
1726             zone.longitude = (double) longitude_degrees
1727                 + (double) longitude_minutes / 60
1728                 + (double) longitude_seconds / 3600;
1729         else
1730             zone.longitude = (double) longitude_degrees
1731                 - (double) longitude_minutes / 60
1732                 - (double) longitude_seconds / 3600;
1733
1734         icalarray_append (builtin_timezones, &zone);
1735
1736 #if 0
1737         printf ("Found zone: %s %f %f\n",
1738                 location, zone.latitude, zone.longitude);
1739 #endif
1740     }
1741
1742     fclose (fp);
1743 }
1744
1745 void
1746 icaltimezone_release_zone_tab           (void)
1747 {
1748     unsigned int i;
1749     icalarray *mybuiltin_timezones = builtin_timezones;
1750
1751     if (builtin_timezones == NULL)
1752         return;
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);
1757 }
1758
1759 /** Loads the builtin VTIMEZONE data for the given timezone. */
1760 static void
1761 icaltimezone_load_builtin_timezone      (icaltimezone *zone)
1762 {
1763     icalcomponent *subcomp;
1764
1765             /* If the location isn't set, it isn't a builtin timezone. */
1766     if (!zone->location || !zone->location[0])
1767         return;
1768
1769 #ifdef HAVE_PTHREAD
1770     pthread_mutex_lock(&builtin_mutex);
1771     if (zone->component)
1772        goto out;
1773 #endif
1774
1775 #ifdef USE_BUILTIN_TZDATA
1776     {
1777     char *filename;
1778     icalcomponent *comp;
1779     unsigned int filename_len;
1780     FILE *fp;
1781     icalparser *parser;
1782
1783     filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1784
1785     filename = (char*) malloc (filename_len);
1786     if (!filename) {
1787         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1788         return;
1789     }
1790
1791     snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1792               zone->location);
1793
1794     fp = fopen (filename, "r");
1795     free (filename);
1796     if (!fp) {
1797         icalerror_set_errno(ICAL_FILE_ERROR);
1798         return;
1799     }
1800
1801         
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));
1805         */
1806
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);
1811         fclose (fp);
1812
1813     /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1814     subcomp = icalcomponent_get_first_component (comp,
1815                                                  ICAL_VTIMEZONE_COMPONENT);
1816 #else
1817         subcomp = icaltzutil_fetch_timezone (zone->location);
1818 #endif  
1819
1820     if (!subcomp) {
1821         icalerror_set_errno(ICAL_PARSE_ERROR);
1822         return;
1823     }
1824
1825     icaltimezone_get_vtimezone_properties (zone, subcomp);
1826
1827 #ifdef USE_BUILTIN_TZDATA
1828     icalcomponent_remove_component(comp,subcomp);
1829     icalcomponent_free(comp);
1830     }
1831 #endif 
1832 #ifdef HAVE_PTHREAD
1833  out:
1834     pthread_mutex_unlock(&builtin_mutex);
1835 #endif
1836 }
1837
1838
1839 #ifdef USE_BUILTIN_TZDATA
1840 /** Callback used from icalparser_parse() */
1841 static char *
1842 icaltimezone_load_get_line_fn           (char           *s,
1843                                          size_t          size,
1844                                          void           *data)
1845 {
1846     return fgets (s, (int)size, (FILE*) data);
1847 }
1848 #endif
1849
1850
1851
1852 /*
1853  * DEBUGGING
1854  */
1855
1856 /**
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.)
1861  * 
1862  * The output format is:
1863  *
1864  *      Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1865  *
1866  * The Date and Time fields specify the time change in UTC.
1867  *
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.
1870  */
1871 int
1872 icaltimezone_dump_changes               (icaltimezone *zone,
1873                                          int             max_year,
1874                                          FILE           *fp)
1875 {
1876     static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1877                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1878     icaltimezonechange *zone_change;
1879     int change_num;
1880     char buffer[8];
1881
1882     /* Make sure the changes array is expanded up to the given time. */
1883     icaltimezone_ensure_coverage (zone, max_year);
1884
1885 #if 0
1886     printf ("Num changes: %i\n", zone->changes->num_elements);
1887 #endif
1888
1889     change_num = 0;
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);
1892
1893         if (zone_change->year > max_year)
1894             break;
1895
1896         fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1897                 zone->location,
1898                 zone_change->day, months[zone_change->month - 1],
1899                 zone_change->year,
1900                 zone_change->hour, zone_change->minute, zone_change->second);
1901
1902         /* Wall Clock Time offset from UTC. */
1903         format_utc_offset (zone_change->utc_offset, buffer);
1904         fprintf (fp, "\t%s", buffer);
1905
1906         fprintf (fp, "\n");
1907     }
1908         return 1;
1909 }
1910
1911
1912 /** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1913    buffer should have space for 8 characters. */
1914 static void
1915 format_utc_offset                       (int             utc_offset,
1916                                          char           *buffer)
1917 {
1918   const char *sign = "+";
1919   int hours, minutes, seconds;
1920
1921   if (utc_offset < 0) {
1922     utc_offset = -utc_offset;
1923     sign = "-";
1924   }
1925
1926   hours = utc_offset / 3600;
1927   minutes = (utc_offset % 3600) / 60;
1928   seconds = utc_offset % 60;
1929
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);
1937   }
1938
1939   if (seconds == 0)
1940     snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
1941   else
1942     snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
1943 }
1944
1945 static const char* get_zone_directory(void)
1946 {
1947 #ifndef WIN32
1948         return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1949 #else
1950         wchar_t wbuffer[1000];
1951 #ifndef _WIN32_WCE
1952         char buffer[1000], zoneinfodir[1000], dirname[1000];
1953 #else
1954         wchar_t zoneinfodir[1000], dirname[1000];
1955 #endif
1956         int used_default;
1957         static char *cache = NULL;
1958 #ifndef _WIN32_WCE
1959         char *dirslash, *zislash;
1960 #else
1961         wchar_t *dirslash, *zislash;
1962 #endif
1963         struct stat st;
1964
1965         if (zone_files_directory)
1966             return zone_files_directory;
1967
1968         if (cache)
1969             return cache;
1970
1971         /* Get the filename of the application */
1972         if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
1973             return ZONEINFO_DIRECTORY;
1974
1975 /*wince supports only unicode*/
1976 #ifndef _WIN32_WCE
1977         /* Convert to system codepage */
1978         if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
1979                                   NULL, &used_default) ||
1980             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) ||
1986                 used_default)
1987                 return ZONEINFO_DIRECTORY;
1988         }
1989 #endif
1990         /* Look for the zoneinfo directory somewhere in the path where
1991          * the app is installed. If the path to the app is
1992          *
1993          *      C:\opt\evo-2.6\bin\evolution-2.6.exe 
1994          *
1995          * and the compile-time ZONEINFO_DIRECTORY is
1996          *
1997          *      C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
1998          *
1999          * we check the pathnames:
2000          *
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
2012          *      C:\opt/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
2018          *      C:/zoneinfo
2019          *
2020          * In Evolution's case, we would get a match already at the
2021          * fourth pathname check.
2022          */
2023
2024         /* Strip away basename of app .exe first */
2025 #ifndef _WIN32_WCE
2026         dirslash = _mbsrchr (buffer, '\\');
2027 #else
2028         dirslash = wcsrchr (wbuffer, L'\\');
2029 #endif
2030         if (dirslash)
2031 #ifndef _WIN32_WCE
2032             *dirslash = '\0';
2033 #else
2034             *dirslash = L'\0';
2035 #endif
2036
2037 #ifdef _WIN32_WCE
2038         while ((dirslash = wcsrchr (wbuffer, '\\'))) {
2039             /* Strip one more directory from app .exe location */
2040             *dirslash = L'\0';
2041             
2042         MultiByteToWideChar(CP_ACP,0,ZONEINFO_DIRECTORY,-1,zoneinfodir,1000);
2043         
2044             while ((zislash = wcschr (zoneinfodir, L'/'))) {
2045                 *zislash = 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);
2052                     return cache;
2053                 }
2054             }
2055         }
2056 #else
2057         while ((dirslash = _mbsrchr (buffer, '\\'))) {
2058             /* Strip one more directory from app .exe location */
2059             *dirslash = '\0';
2060             
2061             strcpy (zoneinfodir, ZONEINFO_DIRECTORY);
2062             while ((zislash = _mbschr (zoneinfodir, '/'))) {
2063                 *zislash = '.';
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);
2070                     return cache;
2071                 }
2072             }
2073         }
2074 #endif
2075         return ZONEINFO_DIRECTORY;
2076 #endif
2077 }
2078
2079 void set_zone_directory(char *path)
2080 {
2081         if (zone_files_directory)
2082                 free_zone_directory();
2083         zone_files_directory = malloc(strlen(path)+1);
2084         if ( zone_files_directory != NULL )
2085         {
2086                 strcpy(zone_files_directory,path);
2087         }
2088 }
2089
2090 void free_zone_directory(void)
2091 {
2092         if ( zone_files_directory != NULL )
2093         {
2094                 free(zone_files_directory);
2095                 zone_files_directory = NULL;
2096         }
2097 }
2098
2099 void icaltimezone_set_tzid_prefix(const char *new_prefix)
2100 {
2101         if (new_prefix) {
2102                 ical_tzid_prefix = new_prefix;
2103         }
2104 }