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 #ifndef NO_ZONES_TAB
45 #include "icaltz-util.h"
46 #endif
47 
48 #include <sys/stat.h>
49 
50 #ifdef WIN32
51 #include <mbstring.h>
52 #include <windows.h>
53 /* Undef the similar macro from pthread.h, it doesn't check if
54  * gmtime() returns NULL.
55  */
56 #undef gmtime_r
57 
58 /* The gmtime() in Microsoft's C library is MT-safe */
59 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
60 
61 // MSVC lacks the POSIX macro S_ISDIR, however it's a trivial one:
62 #ifndef S_ISDIR
63 #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
64 #endif
65 #if defined(_MSC_VER) && (_MSC_VER < 1900)
66 #define snprintf _snprintf
67 #endif
68 #define strcasecmp      stricmp
69 #endif
70 
71 /** This is the toplevel directory where the timezone data is installed in. */
72 #define ZONEINFO_DIRECTORY	PACKAGE_DATA_DIR "/zoneinfo"
73 
74 /** The prefix we use to uniquely identify TZIDs.
75     It must begin and end with forward slashes.
76  */
77 const char *ical_tzid_prefix =	"/freeassociation.sourceforge.net/";
78 
79 /** This is the filename of the file containing the city names and
80     coordinates of all the builtin timezones. */
81 #define ZONES_TAB_FILENAME	"zones.tab"
82 
83 /** This is the number of years of extra coverage we do when expanding
84     the timezone changes. */
85 #define ICALTIMEZONE_EXTRA_COVERAGE	5
86 
87 /** This is the maximum year we will expand to. time_t values only go up to
88     somewhere around 2037. */
89 #define ICALTIMEZONE_MAX_YEAR		2035
90 
91 typedef struct _icaltimezonechange	icaltimezonechange;
92 
93 struct _icaltimezonechange {
94     int		 utc_offset;
95     /**< The offset to add to UTC to get local time, in seconds. */
96 
97     int		 prev_utc_offset;
98     /**< The offset to add to UTC, before this change, in seconds. */
99 
100     int		 year;		/**< Actual year, e.g. 2001. */
101     int		 month;		/**< 1 (Jan) to 12 (Dec). */
102     int		 day;
103     int		 hour;
104     int		 minute;
105     int		 second;
106     /**< The time that the change came into effect, in UTC.
107        Note that the prev_utc_offset applies to this local time,
108        since we haven't changed to the new offset yet. */
109 
110     int		 is_daylight;
111     /**< Whether this is STANDARD or DAYLIGHT time. */
112 };
113 
114 
115 /** An array of icaltimezones for the builtin timezones. */
116 static icalarray *s_builtin_timezones = NULL;
117 
118 /** This is the special UTC timezone, which isn't in s_builtin_timezones. */
119 static icaltimezone utc_timezone = { (char *) "UTC", NULL, NULL, 0.0, 0.0, NULL, NULL, 0, NULL };
120 
121 static char* zone_files_directory = NULL;
122 
123 static void  icaltimezone_reset			(icaltimezone *zone);
124 static char* icaltimezone_get_location_from_vtimezone (icalcomponent *component);
125 static char* icaltimezone_get_tznames_from_vtimezone (icalcomponent *component);
126 static void  icaltimezone_expand_changes	(icaltimezone	*zone,
127 						 int		 end_year);
128 static void  icaltimezone_expand_vtimezone	(icalcomponent	*comp,
129 						 int		 end_year,
130 						 icalarray	*changes);
131 static int   icaltimezone_compare_change_fn	(const void	*elem1,
132 						 const void	*elem2);
133 
134 static int   icaltimezone_find_nearby_change	(icaltimezone *zone,
135 						 icaltimezonechange *change);
136 
137 static void  icaltimezone_adjust_change		(icaltimezonechange *tt,
138 						 int		 days,
139 						 int		 hours,
140 						 int		 minutes,
141 						 int		 seconds);
142 
143 static void  icaltimezone_init			(icaltimezone *zone);
144 
145 /** Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from the
146    VTIMEZONE component and places them in the icaltimezone. It returns 1 on
147    success, or 0 if the TZID can't be found. */
148 static int   icaltimezone_get_vtimezone_properties (icaltimezone *zone,
149 						    icalcomponent *component);
150 
151 
152 static void  icaltimezone_load_builtin_timezone	(icaltimezone *zone);
153 
154 static void  icaltimezone_ensure_coverage	(icaltimezone *zone,
155 						 int		 end_year);
156 
157 
158 static void  icaltimezone_parse_zone_tab	(void);
159 
160 #ifdef USE_BUILTIN_TZDATA
161 static char* icaltimezone_load_get_line_fn	(char		*s,
162 						 size_t		 size,
163 						 void		*data);
164 #endif
165 
166 static void  format_utc_offset			(int		 utc_offset,
167 						 char		*buffer, size_t buffer_size);
168 static const char* get_zone_directory(void);
169 
170 /** Creates a new icaltimezone. */
171 icaltimezone*
icaltimezone_new(void)172 icaltimezone_new			(void)
173 {
174     icaltimezone *zone;
175 
176     zone = (icaltimezone*) malloc (sizeof (icaltimezone));
177     if (!zone) {
178 	icalerror_set_errno (ICAL_NEWFAILED_ERROR);
179 	return NULL;
180     }
181 
182     icaltimezone_init (zone);
183 
184     return zone;
185 }
186 
187 icaltimezone *
icaltimezone_copy(icaltimezone * originalzone)188 icaltimezone_copy			(icaltimezone *originalzone)
189 {
190     icaltimezone *zone;
191 
192     zone = (icaltimezone*) malloc (sizeof (icaltimezone));
193     if (!zone) {
194 	icalerror_set_errno (ICAL_NEWFAILED_ERROR);
195 	return NULL;
196     }
197 
198     memcpy (zone, originalzone, sizeof (icaltimezone));
199     if (zone->tzid != NULL)
200 	zone->tzid = strdup (zone->tzid);
201     if (zone->location != NULL)
202 	zone->location = strdup (zone->location);
203     if (zone->tznames != NULL)
204 	zone->tznames = strdup (zone->tznames);
205     if (zone->changes != NULL)
206         zone->changes = icalarray_copy(zone->changes);
207 
208     /* Let the caller set the component because then they will
209        know to be careful not to free this reference twice. */
210     zone->component = NULL;
211 
212     return zone;
213 }
214 
215 /** Frees all memory used for the icaltimezone. */
216 void
icaltimezone_free(icaltimezone * zone,int free_struct)217 icaltimezone_free			(icaltimezone *zone,
218 					 int	       free_struct)
219 {
220     icaltimezone_reset (zone);
221     if (free_struct)
222 	free (zone);
223 }
224 
225 
226 /** Resets the icaltimezone to the initial state, freeing most of the fields. */
227 static void
icaltimezone_reset(icaltimezone * zone)228 icaltimezone_reset			(icaltimezone *zone)
229 {
230     if (zone->tzid)
231 		free (zone->tzid);
232     if (zone->location)
233 		free (zone->location);
234     if (zone->tznames)
235 		free (zone->tznames);
236     if (zone->component)
237 		icalcomponent_free (zone->component);
238     if (zone->changes)
239 		icalarray_free (zone->changes);
240 
241     icaltimezone_init (zone);
242 }
243 
244 
245 /** Initializes an icaltimezone. */
246 static void
icaltimezone_init(icaltimezone * zone)247 icaltimezone_init			(icaltimezone	*zone)
248 {
249     zone->tzid = NULL;
250     zone->location = NULL;
251     zone->tznames = NULL;
252     zone->latitude = 0.0;
253     zone->longitude = 0.0;
254     zone->component = NULL;
255     zone->builtin_timezone = NULL;
256     zone->end_year = 0;
257     zone->changes = NULL;
258 }
259 
260 
261 /** Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of
262    the VTIMEZONE component and stores them in the icaltimezone.  It
263    returns 1 on success, or 0 if the TZID can't be found.  Note that
264    it expects the zone to be initialized or reset - it doesn't free
265    any old values. */
266 static int
icaltimezone_get_vtimezone_properties(icaltimezone * zone,icalcomponent * component)267 icaltimezone_get_vtimezone_properties	(icaltimezone *zone,
268 					 icalcomponent	*component)
269 {
270     icalproperty *prop;
271     const char *tzid, *tzname;
272 
273     prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
274     if (!prop)
275 	return 0;
276 
277     /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
278     tzid = icalproperty_get_tzid (prop);
279     if (!tzid)
280 	return 0;
281 
282     prop = icalcomponent_get_first_property (component, ICAL_TZNAME_PROPERTY);
283     if (prop) {
284 	tzname = icalproperty_get_tzname (prop);
285 	zone->tznames = strdup(tzname);
286     } else
287 	zone->tznames = NULL;
288 
289     zone->tzid = strdup (tzid);
290     zone->component = component;
291 	if ( zone->location != 0 ) free ( zone->location );
292     zone->location = icaltimezone_get_location_from_vtimezone (component);
293     zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
294 
295     return 1;
296 }
297 
298 /** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
299 static char*
icaltimezone_get_location_from_vtimezone(icalcomponent * component)300 icaltimezone_get_location_from_vtimezone (icalcomponent *component)
301 {
302     icalproperty *prop;
303     const char *location;
304     const char *name;
305 
306     prop = icalcomponent_get_first_property (component,
307 					     ICAL_LOCATION_PROPERTY);
308     if (prop) {
309 	location = icalproperty_get_location (prop);
310 	if (location)
311 	    return strdup (location);
312     }
313 
314     prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
315     while (prop) {
316 	name = icalproperty_get_x_name (prop);
317 	if (name && !strcasecmp (name, "X-LIC-LOCATION")) {
318 	    location = icalproperty_get_x (prop);
319 	    if (location)
320 		return strdup (location);
321 	}
322 	prop = icalcomponent_get_next_property (component,
323 						ICAL_X_PROPERTY);
324     }
325 
326     return NULL;
327 }
328 
329 
330 /** Gets the TZNAMEs used for the last STANDARD & DAYLIGHT components
331    in a VTIMEZONE. If both STANDARD and DAYLIGHT components use the
332    same TZNAME, it returns that. If they use different TZNAMEs, it
333    formats them like "EST/EDT". The returned string should be freed by
334    the caller. */
335 static char*
icaltimezone_get_tznames_from_vtimezone(icalcomponent * component)336 icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
337 {
338     icalcomponent *comp;
339     icalcomponent_kind type;
340     icalproperty *prop;
341     struct icaltimetype dtstart;
342     struct icaldatetimeperiodtype rdate;
343     const char *current_tzname;
344     const char *standard_tzname = NULL, *daylight_tzname = NULL;
345     struct icaltimetype standard_max_date, daylight_max_date;
346     struct icaltimetype current_max_date;
347 
348     standard_max_date = icaltime_null_time();
349     daylight_max_date = icaltime_null_time();
350 
351     /* Step through the STANDARD & DAYLIGHT subcomponents. */
352     comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
353     while (comp) {
354 	type = icalcomponent_isa (comp);
355 	if (type == ICAL_XSTANDARD_COMPONENT
356 	    || type == ICAL_XDAYLIGHT_COMPONENT) {
357 	    current_max_date = icaltime_null_time ();
358 	    current_tzname = NULL;
359 
360 	    /* Step through the properties. We want to find the TZNAME, and
361 	       the largest DTSTART or RDATE. */
362 	    prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
363 	    while (prop) {
364 		switch (icalproperty_isa (prop)) {
365 		case ICAL_TZNAME_PROPERTY:
366 		    current_tzname = icalproperty_get_tzname (prop);
367 		    break;
368 
369 		case ICAL_DTSTART_PROPERTY:
370 		    dtstart = icalproperty_get_dtstart (prop);
371 		    if (icaltime_compare (dtstart, current_max_date) > 0)
372 			current_max_date = dtstart;
373 
374 		    break;
375 
376 		case ICAL_RDATE_PROPERTY:
377 		    rdate = icalproperty_get_rdate (prop);
378 		    if (icaltime_compare (rdate.time, current_max_date) > 0)
379 			current_max_date = rdate.time;
380 
381 		    break;
382 
383 		default:
384 		    break;
385 		}
386 
387 		prop = icalcomponent_get_next_property (comp,
388 							ICAL_ANY_PROPERTY);
389 	    }
390 
391 	    if (current_tzname) {
392 		if (type == ICAL_XSTANDARD_COMPONENT) {
393 		    if (!standard_tzname
394 			|| icaltime_compare (current_max_date,
395 					     standard_max_date) > 0) {
396 			standard_max_date = current_max_date;
397 			standard_tzname = current_tzname;
398 		    }
399 		} else {
400 		    if (!daylight_tzname
401 			|| icaltime_compare (current_max_date,
402 					     daylight_max_date) > 0) {
403 			daylight_max_date = current_max_date;
404 			daylight_tzname = current_tzname;
405 		    }
406 		}
407 	    }
408 	}
409 
410         comp = icalcomponent_get_next_component (component,
411 						 ICAL_ANY_COMPONENT);
412     }
413 
414     /* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
415        strings, which is totally useless. So we return NULL in that case. */
416     if (standard_tzname && !strcmp (standard_tzname, "Standard Time"))
417 	return NULL;
418 
419     /* If both standard and daylight TZNAMEs were found, if they are the same
420        we return just one, else we format them like "EST/EDT". */
421     if (standard_tzname && daylight_tzname) {
422 	unsigned int standard_len, daylight_len;
423 	char *tznames;
424 
425 	if (!strcmp (standard_tzname, daylight_tzname))
426 	    return strdup (standard_tzname);
427 
428 	standard_len = strlen (standard_tzname);
429 	daylight_len = strlen (daylight_tzname);
430 	tznames = malloc (standard_len + daylight_len + 2);
431 	strcpy (tznames, standard_tzname);
432 	tznames[standard_len] = '/';
433 	strcpy (tznames + standard_len + 1, daylight_tzname);
434 	return tznames;
435     } else {
436 	const char *tznames;
437 
438 	/* If either of the TZNAMEs was found just return that, else NULL. */
439 	tznames = standard_tzname ? standard_tzname : daylight_tzname;
440 	return tznames ? strdup (tznames) : NULL;
441     }
442 }
443 
444 
445 static void
icaltimezone_ensure_coverage(icaltimezone * zone,int end_year)446 icaltimezone_ensure_coverage		(icaltimezone *zone,
447 					 int		 end_year)
448 {
449     /* When we expand timezone changes we always expand at least up to this
450        year, plus ICALTIMEZONE_EXTRA_COVERAGE. */
451     static int icaltimezone_minimum_expansion_year = -1;
452 
453     int changes_end_year;
454 
455     if (!zone->component)
456 	icaltimezone_load_builtin_timezone (zone);
457 
458     if (icaltimezone_minimum_expansion_year == -1) {
459 	struct icaltimetype today = icaltime_today();
460 	icaltimezone_minimum_expansion_year = today.year;
461     }
462 
463     changes_end_year = end_year;
464     if (changes_end_year < icaltimezone_minimum_expansion_year)
465 	changes_end_year = icaltimezone_minimum_expansion_year;
466 
467     changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
468 
469     if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
470 	changes_end_year = ICALTIMEZONE_MAX_YEAR;
471 
472     if (!zone->changes || zone->end_year < end_year)
473 	icaltimezone_expand_changes (zone, changes_end_year);
474 }
475 
476 
477 static void
icaltimezone_expand_changes(icaltimezone * zone,int end_year)478 icaltimezone_expand_changes		(icaltimezone *zone,
479 					 int		 end_year)
480 {
481     icalarray *changes;
482     icalcomponent *comp;
483 
484 #if 0
485     printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
486 #endif
487 
488     changes = icalarray_new (sizeof (icaltimezonechange), 32);
489     if (!changes)
490 	return;
491 
492     /* Scan the STANDARD and DAYLIGHT subcomponents. */
493     comp = icalcomponent_get_first_component (zone->component,
494 					      ICAL_ANY_COMPONENT);
495     while (comp) {
496 	icaltimezone_expand_vtimezone (comp, end_year, changes);
497 	comp = icalcomponent_get_next_component (zone->component,
498 						 ICAL_ANY_COMPONENT);
499     }
500 
501     /* Sort the changes. We may have duplicates but I don't think it will
502        matter. */
503     icalarray_sort (changes, icaltimezone_compare_change_fn);
504 
505     if (zone->changes)
506 	icalarray_free (zone->changes);
507 
508     zone->changes = changes;
509     zone->end_year = end_year;
510 }
511 
512 
513 static void
icaltimezone_expand_vtimezone(icalcomponent * comp,int end_year,icalarray * changes)514 icaltimezone_expand_vtimezone		(icalcomponent	*comp,
515 					 int		 end_year,
516 					 icalarray	*changes)
517 {
518     icaltimezonechange change;
519     icalproperty *prop;
520     struct icaltimetype dtstart, occ;
521     struct icalrecurrencetype rrule;
522     icalrecur_iterator* rrule_iterator;
523     struct icaldatetimeperiodtype rdate;
524     int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
525     int has_recurrence = 0;
526 
527     /* First we check if it is a STANDARD or DAYLIGHT component, and
528        just return if it isn't. */
529     if (icalcomponent_isa (comp) == ICAL_XSTANDARD_COMPONENT)
530 	change.is_daylight = 0;
531     else if (icalcomponent_isa (comp) == ICAL_XDAYLIGHT_COMPONENT)
532 	change.is_daylight = 1;
533     else
534 	return;
535 
536     /* Step through each of the properties to find the DTSTART,
537        TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
538        since we need these properties before we can do that. */
539     prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
540     while (prop) {
541 	switch (icalproperty_isa (prop)) {
542 	case ICAL_DTSTART_PROPERTY:
543 	    dtstart = icalproperty_get_dtstart (prop);
544 	    found_dtstart = 1;
545 	    break;
546 	case ICAL_TZOFFSETTO_PROPERTY:
547 	    change.utc_offset = icalproperty_get_tzoffsetto (prop);
548 	    /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset);*/
549 	    found_tzoffsetto = 1;
550 	    break;
551 	case ICAL_TZOFFSETFROM_PROPERTY:
552 	    change.prev_utc_offset = icalproperty_get_tzoffsetfrom (prop);
553 	    /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset);*/
554 	    found_tzoffsetfrom = 1;
555 	    break;
556 	case ICAL_RDATE_PROPERTY:
557 	case ICAL_RRULE_PROPERTY:
558 	    has_recurrence = 1;
559 	    break;
560 	default:
561 	    /* Just ignore any other properties. */
562 	    break;
563 	}
564 
565 	prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
566     }
567 
568     /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
569        ignore the component. FIXME: Add an error property? */
570     if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
571 	return;
572 
573 #if 0
574     printf ("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
575 	    dtstart.year, dtstart.month, dtstart.day,
576 	    dtstart.hour, dtstart.minute, dtstart.second);
577 #endif
578 
579     /* If the STANDARD/DAYLIGHT component has no recurrence data, we just add
580        a single change for the DTSTART. */
581     if (!has_recurrence) {
582 	change.year   = dtstart.year;
583 	change.month  = dtstart.month;
584 	change.day    = dtstart.day;
585 	change.hour   = dtstart.hour;
586 	change.minute = dtstart.minute;
587 	change.second = dtstart.second;
588 
589 	/* Convert to UTC. */
590 	icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
591 
592 #if 0
593 	printf ("  Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
594 		change.year, change.month, change.day,
595 		change.hour, change.minute, change.second);
596 #endif
597 
598 	/* Add the change to the array. */
599 	icalarray_append (changes, &change);
600 	return;
601     }
602 
603     /* The component has recurrence data, so we expand that now. */
604     prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
605     while (prop) {
606 #if 0
607 	printf ("Expanding property...\n");
608 #endif
609 	switch (icalproperty_isa (prop)) {
610 	case ICAL_RDATE_PROPERTY:
611 	    rdate = icalproperty_get_rdate (prop);
612 	    change.year   = rdate.time.year;
613 	    change.month  = rdate.time.month;
614 	    change.day    = rdate.time.day;
615 	    /* RDATEs with a DATE value inherit the time from
616 	       the DTSTART. */
617 	    if (icaltime_is_date(rdate.time)) {
618 		change.hour   = dtstart.hour;
619 		change.minute = dtstart.minute;
620 		change.second = dtstart.second;
621 	    } else {
622 		change.hour   = rdate.time.hour;
623 		change.minute = rdate.time.minute;
624 		change.second = rdate.time.second;
625 
626 		/* The spec was a bit vague about whether RDATEs were in local
627 		   time or UTC so we support both to be safe. So if it is in
628 		   UTC we have to add the UTC offset to get a local time. */
629 		if (!icaltime_is_utc(rdate.time))
630 		    icaltimezone_adjust_change (&change, 0, 0, 0,
631 						-change.prev_utc_offset);
632 	    }
633 
634 #if 0
635 	    printf ("  Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
636 		    change.year, change.month, change.day,
637 		    change.hour, change.minute, change.second);
638 #endif
639 
640 	    icalarray_append (changes, &change);
641 	    break;
642 	case ICAL_RRULE_PROPERTY:
643 	    rrule = icalproperty_get_rrule (prop);
644 
645 	    /* If the rrule UNTIL value is set and is in UTC, we convert it to
646 	       a local time, since the recurrence code has no way to convert
647 	       it itself. */
648 	    if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
649 #if 0
650 		printf ("  Found RRULE UNTIL in UTC.\n");
651 #endif
652 
653 		/* To convert from UTC to a local time, we use the TZOFFSETFROM
654 		   since that is the offset from UTC that will be in effect
655 		   when each of the RRULE occurrences happens. */
656 		icaltime_adjust (&rrule.until, 0, 0, 0,
657 				 change.prev_utc_offset);
658 		rrule.until.is_utc = 0;
659 	    }
660 
661 	    rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
662 	    for (;rrule_iterator;) {
663 		occ = icalrecur_iterator_next (rrule_iterator);
664 		if (occ.year > end_year || icaltime_is_null_time (occ))
665 		    break;
666 
667 		change.year   = occ.year;
668 		change.month  = occ.month;
669 		change.day    = occ.day;
670 		change.hour   = occ.hour;
671 		change.minute = occ.minute;
672 		change.second = occ.second;
673 
674 #if 0
675 		printf ("  Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
676 			change.year, change.month, change.day,
677 			change.hour, change.minute, change.second);
678 #endif
679 
680 		icaltimezone_adjust_change (&change, 0, 0, 0,
681 					    -change.prev_utc_offset);
682 
683 		icalarray_append (changes, &change);
684 	    }
685 
686 	    icalrecur_iterator_free (rrule_iterator);
687 	    break;
688 	default:
689 	    break;
690 	}
691 
692 	prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
693     }
694 }
695 
696 
697 /** A function to compare 2 icaltimezonechange elements, used for qsort(). */
698 static int
icaltimezone_compare_change_fn(const void * elem1,const void * elem2)699 icaltimezone_compare_change_fn		(const void	*elem1,
700 					 const void	*elem2)
701 {
702     const icaltimezonechange *change1, *change2;
703     int retval;
704 
705     change1 = (const icaltimezonechange *)elem1;
706     change2 = (const icaltimezonechange *)elem2;
707 
708     if (change1->year < change2->year)
709 	retval = -1;
710     else if (change1->year > change2->year)
711 	retval = 1;
712 
713     else if (change1->month < change2->month)
714 	retval = -1;
715     else if (change1->month > change2->month)
716 	retval = 1;
717 
718     else if (change1->day < change2->day)
719 	retval = -1;
720     else if (change1->day > change2->day)
721 	retval = 1;
722 
723     else if (change1->hour < change2->hour)
724 	retval = -1;
725     else if (change1->hour > change2->hour)
726 	retval = 1;
727 
728     else if (change1->minute < change2->minute)
729 	retval = -1;
730     else if (change1->minute > change2->minute)
731 	retval = 1;
732 
733     else if (change1->second < change2->second)
734 	retval = -1;
735     else if (change1->second > change2->second)
736 	retval = 1;
737 
738     else
739 	retval = 0;
740 
741     return retval;
742 }
743 
744 
745 
746 void
icaltimezone_convert_time(struct icaltimetype * tt,icaltimezone * from_zone,icaltimezone * to_zone)747 icaltimezone_convert_time		(struct icaltimetype *tt,
748 					 icaltimezone *from_zone,
749 					 icaltimezone *to_zone)
750 {
751     int utc_offset, is_daylight;
752 
753     /* If the time is a DATE value or both timezones are the same, or we are
754        converting a floating time, we don't need to do anything. */
755     if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
756 	return;
757 
758     /* Convert the time to UTC by getting the UTC offset and subtracting it. */
759     utc_offset = icaltimezone_get_utc_offset (from_zone, tt, NULL);
760     icaltime_adjust (tt, 0, 0, 0, -utc_offset);
761 
762     /* Now we convert the time to the new timezone by getting the UTC offset
763        of our UTC time and adding it. */
764     utc_offset = icaltimezone_get_utc_offset_of_utc_time (to_zone, tt,
765 							  &is_daylight);
766     tt->is_daylight = is_daylight;
767     icaltime_adjust (tt, 0, 0, 0, utc_offset);
768 }
769 
770 
771 
772 
773 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
774     timezone. Also, this takes a pointer instead of the struct. */
775 /* Calculates the UTC offset of a given local time in the given
776    timezone.  It is the number of seconds to add to UTC to get local
777    time.  The is_daylight flag is set to 1 if the time is in
778    daylight-savings time. */
779 int
icaltimezone_get_utc_offset(icaltimezone * zone,struct icaltimetype * tt,int * is_daylight)780 icaltimezone_get_utc_offset		(icaltimezone	*zone,
781 					 struct icaltimetype	*tt,
782 					 int		*is_daylight)
783 {
784     icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
785     int change_num, step, utc_offset_change, cmp;
786     int change_num_to_use;
787     int want_daylight;
788 
789     if (tt == NULL)
790 	return 0;
791 
792     if (is_daylight)
793 	*is_daylight = 0;
794 
795     /* For local times and UTC return 0. */
796     if (zone == NULL || zone == &utc_timezone)
797 	return 0;
798 
799     /* Use the builtin icaltimezone if possible. */
800     if (zone->builtin_timezone)
801 	zone = zone->builtin_timezone;
802 
803     /* Make sure the changes array is expanded up to the given time. */
804     icaltimezone_ensure_coverage (zone, tt->year);
805 
806     if (!zone->changes || zone->changes->num_elements == 0)
807 	return 0;
808 
809     /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
810        can use our comparison function on it. */
811     tt_change.year   = tt->year;
812     tt_change.month  = tt->month;
813     tt_change.day    = tt->day;
814     tt_change.hour   = tt->hour;
815     tt_change.minute = tt->minute;
816     tt_change.second = tt->second;
817 
818     /* This should find a change close to the time, either the change before
819        it or the change after it. */
820     change_num = icaltimezone_find_nearby_change (zone, &tt_change);
821 
822     /* Sanity check. */
823     icalerror_assert (change_num >= 0,
824 		      "Negative timezone change index");
825     icalerror_assert (change_num < zone->changes->num_elements,
826 		      "Timezone change index out of bounds");
827 
828     /* Now move backwards or forwards to find the timezone change that applies
829        to tt. It should only have to do 1 or 2 steps. */
830     zone_change = icalarray_element_at (zone->changes, change_num);
831     step = 1;
832     change_num_to_use = -1;
833     for (;;) {
834 	/* Copy the change, so we can adjust it. */
835 	tmp_change = *zone_change;
836 
837 	/* If the clock is going backward, check if it is in the region of time
838 	   that is used twice. If it is, use the change with the daylight
839 	   setting which matches tt, or use standard if we don't know. */
840 	if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
841 	    /* If the time change is at 2:00AM local time and the clock is
842 	       going back to 1:00AM we adjust the change to 1:00AM. We may
843 	       have the wrong change but we'll figure that out later. */
844 	    icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
845 					tmp_change.utc_offset);
846 	} else {
847 	    icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
848 					tmp_change.prev_utc_offset);
849 	}
850 
851 	cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
852 
853 	/* If the given time is on or after this change, then this change may
854 	   apply, but we continue as a later change may be the right one.
855 	   If the given time is before this change, then if we have already
856 	   found a change which applies we can use that, else we need to step
857 	   backwards. */
858 	if (cmp >= 0)
859 	    change_num_to_use = change_num;
860 	else
861 	    step = -1;
862 
863 	/* If we are stepping backwards through the changes and we have found
864 	   a change that applies, then we know this is the change to use so
865 	   we exit the loop. */
866 	if (step == -1 && change_num_to_use != -1)
867 	    break;
868 
869 	change_num += step;
870 
871 	/* If we go past the start of the changes array, then we have no data
872 	   for this time so we return a UTC offset of 0. */
873 	if (change_num < 0)
874 	    return 0;
875 
876 	if ((unsigned int)change_num >= zone->changes->num_elements)
877 	    break;
878 
879 	zone_change = icalarray_element_at (zone->changes, change_num);
880     }
881 
882     /* If we didn't find a change to use, then we have a bug! */
883     icalerror_assert (change_num_to_use != -1,
884 		      "No applicable timezone change found");
885 
886     /* Now we just need to check if the time is in the overlapped region of
887        time when clocks go back. */
888     zone_change = icalarray_element_at (zone->changes, change_num_to_use);
889 
890     utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
891     if (utc_offset_change < 0 && change_num_to_use > 0) {
892 	tmp_change = *zone_change;
893 	icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
894 				    tmp_change.prev_utc_offset);
895 
896 	if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
897 	    /* The time is in the overlapped region, so we may need to use
898 	       either the current zone_change or the previous one. If the
899 	       time has the is_daylight field set we use the matching change,
900 	       else we use the change with standard time. */
901 	    prev_zone_change = icalarray_element_at (zone->changes,
902 						     change_num_to_use - 1);
903 
904 	    /* I was going to add an is_daylight flag to struct icaltimetype,
905 	       but iCalendar doesn't let us distinguish between standard and
906 	       daylight time anyway, so there's no point. So we just use the
907 	       standard time instead. */
908 	    want_daylight = (tt->is_daylight == 1) ? 1 : 0;
909 
910 #if 0
911 	    if (zone_change->is_daylight == prev_zone_change->is_daylight)
912 		printf (" **** Same is_daylight setting\n");
913 #endif
914 
915 	    if (zone_change->is_daylight != want_daylight
916 		&& prev_zone_change->is_daylight == want_daylight)
917 		zone_change = prev_zone_change;
918 	}
919     }
920 
921     /* Now we know exactly which timezone change applies to the time, so
922        we can return the UTC offset and whether it is a daylight time. */
923     if (is_daylight)
924 	*is_daylight = zone_change->is_daylight;
925     return zone_change->utc_offset;
926 }
927 
928 
929 /** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
930     timezone. Also, this takes a pointer instead of the struct. */
931 /** Calculates the UTC offset of a given UTC time in the given
932    timezone.  It is the number of seconds to add to UTC to get local
933    time.  The is_daylight flag is set to 1 if the time is in
934    daylight-savings time. */
935 int
icaltimezone_get_utc_offset_of_utc_time(icaltimezone * zone,struct icaltimetype * tt,int * is_daylight)936 icaltimezone_get_utc_offset_of_utc_time	(icaltimezone	*zone,
937 					 struct icaltimetype	*tt,
938 					 int		*is_daylight)
939 {
940     icaltimezonechange *zone_change, tt_change, tmp_change;
941     int change_num, step, change_num_to_use;
942 
943     if (is_daylight)
944 	*is_daylight = 0;
945 
946     /* For local times and UTC return 0. */
947     if (zone == NULL || zone == &utc_timezone)
948 	return 0;
949 
950     /* Use the builtin icaltimezone if possible. */
951     if (zone->builtin_timezone)
952 	zone = zone->builtin_timezone;
953 
954     /* Make sure the changes array is expanded up to the given time. */
955     icaltimezone_ensure_coverage (zone, tt->year);
956 
957     if (!zone->changes || zone->changes->num_elements == 0)
958 	return 0;
959 
960     /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
961        can use our comparison function on it. */
962     tt_change.year   = tt->year;
963     tt_change.month  = tt->month;
964     tt_change.day    = tt->day;
965     tt_change.hour   = tt->hour;
966     tt_change.minute = tt->minute;
967     tt_change.second = tt->second;
968 
969     /* This should find a change close to the time, either the change before
970        it or the change after it. */
971     change_num = icaltimezone_find_nearby_change (zone, &tt_change);
972 
973     /* Sanity check. */
974     icalerror_assert (change_num >= 0,
975 		      "Negative timezone change index");
976     icalerror_assert (change_num < zone->changes->num_elements,
977 		      "Timezone change index out of bounds");
978 
979     /* Now move backwards or forwards to find the timezone change that applies
980        to tt. It should only have to do 1 or 2 steps. */
981     zone_change = icalarray_element_at (zone->changes, change_num);
982     step = 1;
983     change_num_to_use = -1;
984     for (;;) {
985 	/* Copy the change and adjust it to UTC. */
986 	tmp_change = *zone_change;
987 
988 	/* If the given time is on or after this change, then this change may
989 	   apply, but we continue as a later change may be the right one.
990 	   If the given time is before this change, then if we have already
991 	   found a change which applies we can use that, else we need to step
992 	   backwards. */
993 	if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
994 	    change_num_to_use = change_num;
995 	else
996 	    step = -1;
997 
998 	/* If we are stepping backwards through the changes and we have found
999 	   a change that applies, then we know this is the change to use so
1000 	   we exit the loop. */
1001 	if (step == -1 && change_num_to_use != -1)
1002 	    break;
1003 
1004 	change_num += step;
1005 
1006 	/* If we go past the start of the changes array, then we have no data
1007 	   for this time so we return a UTC offset of 0. */
1008 	if (change_num < 0)
1009 	    return 0;
1010 
1011 	if ((unsigned int)change_num >= zone->changes->num_elements)
1012 	    break;
1013 
1014 	zone_change = icalarray_element_at (zone->changes, change_num);
1015     }
1016 
1017     /* If we didn't find a change to use, then we have a bug! */
1018     icalerror_assert (change_num_to_use != -1,
1019 		      "No applicable timezone change found");
1020 
1021     /* Now we know exactly which timezone change applies to the time, so
1022        we can return the UTC offset and whether it is a daylight time. */
1023     zone_change = icalarray_element_at (zone->changes, change_num_to_use);
1024     if (is_daylight)
1025 	*is_daylight = zone_change->is_daylight;
1026 
1027     return zone_change->utc_offset;
1028 }
1029 
1030 
1031 /** Returns the index of a timezone change which is close to the time
1032    given in change. */
1033 static int
icaltimezone_find_nearby_change(icaltimezone * zone,icaltimezonechange * change)1034 icaltimezone_find_nearby_change		(icaltimezone	*zone,
1035 					 icaltimezonechange	*change)
1036 {
1037     icaltimezonechange *zone_change;
1038     int lower, upper, middle, cmp;
1039 
1040     /* Do a simple binary search. */
1041     lower = middle = 0;
1042     upper = zone->changes->num_elements;
1043 
1044     while (lower < upper) {
1045 	middle = (lower + upper) / 2;
1046 	zone_change = icalarray_element_at (zone->changes, middle);
1047 	cmp = icaltimezone_compare_change_fn (change, zone_change);
1048 	if (cmp == 0)
1049 	    break;
1050 	else if (cmp < 0)
1051 	    upper = middle;
1052 	else
1053 	    lower = middle + 1;
1054     }
1055 
1056     return middle;
1057 }
1058 
1059 
1060 
1061 
1062 /** Adds (or subtracts) a time from a icaltimezonechange.  NOTE: This
1063    function is exactly the same as icaltime_adjust() except for the
1064    type of the first parameter. */
1065 static void
icaltimezone_adjust_change(icaltimezonechange * tt,int days,int hours,int minutes,int seconds)1066 icaltimezone_adjust_change		(icaltimezonechange *tt,
1067 					 int		 days,
1068 					 int		 hours,
1069 					 int		 minutes,
1070 					 int		 seconds)
1071 {
1072     int second, minute, hour, day;
1073     int minutes_overflow, hours_overflow, days_overflow;
1074     int days_in_month;
1075 
1076     /* Add on the seconds. */
1077     second = tt->second + seconds;
1078     tt->second = second % 60;
1079     minutes_overflow = second / 60;
1080     if (tt->second < 0) {
1081 	tt->second += 60;
1082 	minutes_overflow--;
1083     }
1084 
1085     /* Add on the minutes. */
1086     minute = tt->minute + minutes + minutes_overflow;
1087     tt->minute = minute % 60;
1088     hours_overflow = minute / 60;
1089     if (tt->minute < 0) {
1090 	tt->minute += 60;
1091 	hours_overflow--;
1092     }
1093 
1094     /* Add on the hours. */
1095     hour = tt->hour + hours + hours_overflow;
1096     tt->hour = hour % 24;
1097     days_overflow = hour / 24;
1098     if (tt->hour < 0) {
1099 	tt->hour += 24;
1100 	days_overflow--;
1101     }
1102 
1103     /* Add on the days. */
1104     day = tt->day + days + days_overflow;
1105     if (day > 0) {
1106 	for (;;) {
1107 	    days_in_month = icaltime_days_in_month (tt->month, tt->year);
1108 	    if (day <= days_in_month)
1109 		break;
1110 
1111 	    tt->month++;
1112 	    if (tt->month >= 13) {
1113 		tt->year++;
1114 		tt->month = 1;
1115 	    }
1116 
1117 	    day -= days_in_month;
1118 	}
1119     } else {
1120 	while (day <= 0) {
1121 	    if (tt->month == 1) {
1122 		tt->year--;
1123 		tt->month = 12;
1124 	    } else {
1125 		tt->month--;
1126 	    }
1127 
1128 	    day += icaltime_days_in_month (tt->month, tt->year);
1129 	}
1130     }
1131     tt->day = day;
1132 }
1133 
1134 
1135 const char*
icaltimezone_get_tzid(icaltimezone * zone)1136 icaltimezone_get_tzid			(icaltimezone *zone)
1137 {
1138     /* If this is a floating time, without a timezone, return NULL. */
1139     if (!zone)
1140 	return NULL;
1141 
1142     if (!zone->tzid)
1143 	icaltimezone_load_builtin_timezone (zone);
1144 
1145     return zone->tzid;
1146 }
1147 
1148 
1149 const char*
icaltimezone_get_location(icaltimezone * zone)1150 icaltimezone_get_location		(icaltimezone *zone)
1151 {
1152     /* If this is a floating time, without a timezone, return NULL. */
1153     if (!zone)
1154 	return NULL;
1155 
1156     /* Note that for builtin timezones this comes from zones.tab so we don't
1157        need to check the timezone is loaded here. */
1158     return zone->location;
1159 }
1160 
1161 
1162 const char*
icaltimezone_get_tznames(icaltimezone * zone)1163 icaltimezone_get_tznames		(icaltimezone *zone)
1164 {
1165     /* If this is a floating time, without a timezone, return NULL. */
1166     if (!zone)
1167 	return NULL;
1168 
1169     if (!zone->component)
1170 	icaltimezone_load_builtin_timezone (zone);
1171 
1172     return zone->tznames;
1173 }
1174 
1175 
1176 /** Returns the latitude of a builtin timezone. */
1177 double
icaltimezone_get_latitude(icaltimezone * zone)1178 icaltimezone_get_latitude		(icaltimezone *zone)
1179 {
1180     /* If this is a floating time, without a timezone, return 0. */
1181     if (!zone)
1182 	return 0.0;
1183 
1184     /* Note that for builtin timezones this comes from zones.tab so we don't
1185        need to check the timezone is loaded here. */
1186     return zone->latitude;
1187 }
1188 
1189 
1190 /** Returns the longitude of a builtin timezone. */
1191 double
icaltimezone_get_longitude(icaltimezone * zone)1192 icaltimezone_get_longitude		(icaltimezone *zone)
1193 {
1194     /* If this is a floating time, without a timezone, return 0. */
1195     if (!zone)
1196 	return 0.0;
1197 
1198     /* Note that for builtin timezones this comes from zones.tab so we don't
1199        need to check the timezone is loaded here. */
1200     return zone->longitude;
1201 }
1202 
1203 
1204 /** Returns the VTIMEZONE component of a timezone. */
1205 icalcomponent*
icaltimezone_get_component(icaltimezone * zone)1206 icaltimezone_get_component		(icaltimezone *zone)
1207 {
1208     /* If this is a floating time, without a timezone, return NULL. */
1209     if (!zone)
1210 	return NULL;
1211 
1212     if (!zone->component)
1213 	icaltimezone_load_builtin_timezone (zone);
1214 
1215     return zone->component;
1216 }
1217 
1218 
1219 /** Sets the VTIMEZONE component of an icaltimezone, initializing the
1220    tzid, location & tzname fields. It returns 1 on success or 0 on
1221    failure, i.e.  no TZID was found. */
1222 int
icaltimezone_set_component(icaltimezone * zone,icalcomponent * comp)1223 icaltimezone_set_component		(icaltimezone *zone,
1224 					 icalcomponent	*comp)
1225 {
1226     icaltimezone_reset (zone);
1227     return icaltimezone_get_vtimezone_properties (zone, comp);
1228 }
1229 
1230 
1231 /* Returns the timezone name to display to the user. We prefer to use the
1232    Olson city name, but fall back on the TZNAME, or finally the TZID. We don't
1233    want to use "" as it may be wrongly interpreted as a floating time.
1234    Do not free the returned string. */
1235 const char*
icaltimezone_get_display_name(icaltimezone * zone)1236 icaltimezone_get_display_name		(icaltimezone	*zone)
1237 {
1238 	const char *display_name;
1239 
1240 	display_name = icaltimezone_get_location (zone);
1241 	if (!display_name)
1242 		display_name = icaltimezone_get_tznames (zone);
1243 	if (!display_name) {
1244 		display_name = icaltimezone_get_tzid (zone);
1245 		/* Outlook will strip out X-LIC-LOCATION property and so all
1246 		   we get back in the iTIP replies is the TZID. So we see if
1247 		   this is one of our TZIDs and if so we jump to the city name
1248 		   at the end of it. */
1249 		if (display_name
1250 		    && !strncmp (display_name, ical_tzid_prefix, strlen(ical_tzid_prefix))) {
1251 		    /* Get the location, which is after the 3rd '/' char. */
1252 		    const char *p;
1253 		    int num_slashes = 0;
1254 		    for (p = display_name; *p; p++) {
1255 			if (*p == '/') {
1256 			    num_slashes++;
1257 			    if (num_slashes == 3)
1258 				return p + 1;
1259 			}
1260 		    }
1261 		}
1262 	}
1263 
1264 	return display_name;
1265 }
1266 
1267 icalarray*
icaltimezone_array_new(void)1268 icaltimezone_array_new			(void)
1269 {
1270     return icalarray_new (sizeof (icaltimezone), 16);
1271 }
1272 
1273 
1274 void
icaltimezone_array_append_from_vtimezone(icalarray * timezones,icalcomponent * child)1275 icaltimezone_array_append_from_vtimezone (icalarray	*timezones,
1276 					  icalcomponent	*child)
1277 {
1278     icaltimezone zone;
1279 
1280     icaltimezone_init (&zone);
1281     if (icaltimezone_get_vtimezone_properties (&zone, child))
1282 	icalarray_append (timezones, &zone);
1283 }
1284 
1285 
1286 void
icaltimezone_array_free(icalarray * timezones)1287 icaltimezone_array_free			(icalarray	*timezones)
1288 {
1289     icaltimezone *zone;
1290     int i;
1291 
1292 	if ( timezones )
1293 	{
1294 	    for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1295 		zone = icalarray_element_at (timezones, i);
1296 		icaltimezone_free (zone, 0);
1297 		}
1298 
1299 		icalarray_free (timezones);
1300 	}
1301 }
1302 
1303 
1304 /*
1305  * BUILTIN TIMEZONE HANDLING
1306  */
1307 
1308 
1309 /** Returns an icalarray of icaltimezone structs, one for each builtin
1310    timezone.  This will load and parse the zones.tab file to get the
1311    timezone names and their coordinates. It will not load the
1312    VTIMEZONE data for any timezones. */
1313 icalarray*
icaltimezone_get_builtin_timezones(void)1314 icaltimezone_get_builtin_timezones	(void)
1315 {
1316 #ifndef NO_ZONES_TAB
1317     if (!s_builtin_timezones) {
1318 	icaltimezone_parse_zone_tab ();
1319     }
1320 #endif
1321     return s_builtin_timezones;
1322 }
1323 
1324 /** Release builtin timezone memory */
1325 void
icaltimezone_free_builtin_timezones(void)1326 icaltimezone_free_builtin_timezones(void)
1327 {
1328 	icaltimezone_array_free(s_builtin_timezones);
1329 	s_builtin_timezones = 0;
1330 }
1331 
1332 
1333 /** Returns a single builtin timezone, given its Olson city name. */
1334 icaltimezone*
icaltimezone_get_builtin_timezone(const char * location)1335 icaltimezone_get_builtin_timezone	(const char *location)
1336 {
1337 #ifndef NO_ZONES_TAB
1338     icalcomponent *comp;
1339 #endif
1340     icaltimezone *zone;
1341     unsigned int lower;
1342     const char *zone_location;
1343     icalarray * builtin_timezones;
1344 
1345     if (!location || !location[0])
1346 	return NULL;
1347 
1348     if (!strcmp (location, "UTC"))
1349 	return &utc_timezone;
1350 
1351     builtin_timezones = icaltimezone_get_builtin_timezones();
1352     if (!builtin_timezones) {
1353 	return NULL;
1354     }
1355 
1356 #if 0
1357     /* Do a simple binary search. */
1358     lower = middle = 0;
1359     upper = builtin_timezones->num_elements;
1360 
1361     while (lower < upper) {
1362 	middle = (lower + upper) / 2;
1363 	zone = icalarray_element_at (builtin_timezones, middle);
1364 	zone_location = icaltimezone_get_location (zone);
1365 	cmp = strcmp (location, zone_location);
1366 	if (cmp == 0)
1367 	    return zone;
1368 	else if (cmp < 0)
1369 	    upper = middle;
1370 	else
1371 	    lower = middle + 1;
1372     }
1373 #endif
1374 
1375     /* The zones from the system are not stored in alphabetical order,
1376        so we just do a sequential search */
1377     for (lower = 0; lower < builtin_timezones->num_elements; lower++) {
1378 	zone = icalarray_element_at (builtin_timezones, lower);
1379 	zone_location = icaltimezone_get_location (zone);
1380 	if (strcmp (location, zone_location) == 0)
1381 		return zone;
1382     }
1383 #ifndef NO_ZONES_TAB
1384     /* Check whether file exists, but is not mentioned in zone.tab.
1385        It means it's a deprecated timezone, but still available. */
1386     comp = icaltzutil_fetch_timezone (location);
1387     if (comp) {
1388 	icaltimezone tz;
1389 	icaltimezone_init (&tz);
1390 	if (icaltimezone_set_component (&tz, comp)) {
1391 	    icalarray_append (builtin_timezones, &tz);
1392 	    return icalarray_element_at (builtin_timezones, builtin_timezones->num_elements - 1);
1393 	} else {
1394 	    icalcomponent_free (comp);
1395 	}
1396     }
1397 #endif
1398 
1399     return NULL;
1400 }
1401 
1402 static struct icaltimetype
tm_to_icaltimetype(struct tm * tm)1403 tm_to_icaltimetype (struct tm *tm)
1404 {
1405 	struct icaltimetype itt;
1406 
1407 	memset (&itt, 0, sizeof (struct icaltimetype));
1408 
1409 	itt.second = tm->tm_sec;
1410 	itt.minute = tm->tm_min;
1411 	itt.hour = tm->tm_hour;
1412 
1413 	itt.day = tm->tm_mday;
1414 	itt.month = tm->tm_mon + 1;
1415 	itt.year = tm->tm_year+ 1900;
1416 
1417 	itt.is_utc = 0;
1418 	itt.is_date = 0;
1419 
1420 	return itt;
1421 }
1422 
1423 static int
get_offset(icaltimezone * zone)1424 get_offset (icaltimezone *zone)
1425 {
1426     struct tm local;
1427     struct icaltimetype tt;
1428     int offset;
1429     time_t now = time(NULL);
1430 
1431     gmtime_r ((const time_t *) &now, &local);
1432     tt = tm_to_icaltimetype (&local);
1433     offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1434 
1435     return offset;
1436 }
1437 
1438 /** Returns a single builtin timezone, given its offset from UTC */
1439 icaltimezone*
icaltimezone_get_builtin_timezone_from_offset(int offset,const char * tzname)1440 icaltimezone_get_builtin_timezone_from_offset	(int offset, const char *tzname)
1441 {
1442     icaltimezone *zone=NULL;
1443     int count, i;
1444 
1445     icalarray * builtin_timezones;
1446 
1447     if (offset==0)
1448 	return &utc_timezone;
1449 
1450     if (!tzname)
1451 	return NULL;
1452 
1453     builtin_timezones = icaltimezone_get_builtin_timezones();
1454     if (!builtin_timezones) {
1455 	return NULL;
1456     }
1457 
1458     count = builtin_timezones->num_elements;
1459 
1460     for (i=0; i<count; i++) {
1461 	int z_offset;
1462 	zone = icalarray_element_at (builtin_timezones, i);
1463 	if (!zone->component)
1464 	    icaltimezone_load_builtin_timezone (zone);
1465 
1466 	z_offset = get_offset(zone);
1467 
1468 	if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames))
1469 	    return zone;
1470     }
1471 
1472     return NULL;
1473 }
1474 
1475 /** Returns a single builtin timezone, given its TZID. */
1476 icaltimezone*
icaltimezone_get_builtin_timezone_from_tzid(const char * tzid)1477 icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1478 {
1479     int num_slashes = 0;
1480     const char *p, *zone_tzid;
1481     icaltimezone *zone;
1482 
1483     if (!tzid || !tzid[0])
1484 	return NULL;
1485 
1486     /* Check that the TZID starts with our unique prefix. */
1487     if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
1488 	return NULL;
1489 
1490     /* Get the location, which is after the 3rd '/' character. */
1491     p = tzid;
1492     for (p = tzid; *p; p++) {
1493 	if (*p == '/') {
1494 	    num_slashes++;
1495 	    if (num_slashes == 3)
1496 		break;
1497 	}
1498     }
1499 
1500     if (num_slashes != 3)
1501 	return NULL;
1502 
1503     p++;
1504 
1505     /* Now we can use the function to get the builtin timezone from the
1506        location string. */
1507     zone = icaltimezone_get_builtin_timezone (p);
1508     if (!zone)
1509 	return NULL;
1510 
1511     /* Check that the builtin TZID matches exactly. We don't want to return
1512        a different version of the VTIMEZONE. */
1513     zone_tzid = icaltimezone_get_tzid (zone);
1514     if (!strcmp (zone_tzid, tzid))
1515 	return zone;
1516     else
1517 	return NULL;
1518 }
1519 
1520 
1521 /** Returns the special UTC timezone. */
1522 icaltimezone*
icaltimezone_get_utc_timezone(void)1523 icaltimezone_get_utc_timezone		(void)
1524 {
1525     return &utc_timezone;
1526 }
1527 
1528 
1529 
1530 static int
parse_coord(char * coord,int len,int * degrees,int * minutes,int * seconds)1531 parse_coord			(char		*coord,
1532 				 int		 len,
1533 				 int		*degrees,
1534 				 int 		*minutes,
1535 				 int 		*seconds)
1536 {
1537 	if (len == 5)
1538 		sscanf (coord + 1, "%2d%2d", degrees, minutes);
1539 	else if (len == 6)
1540 		sscanf (coord + 1, "%3d%2d", degrees, minutes);
1541 	else if (len == 7)
1542 		sscanf (coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1543 	else if (len == 8)
1544 		sscanf (coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1545 	else {
1546 		fprintf (stderr, "Invalid coordinate: %s\n", coord);
1547 		return 1;
1548 	}
1549 
1550 	if (coord [0] == '-')
1551 		*degrees = -*degrees;
1552 	return 0;
1553 }
1554 static int
fetch_lat_long_from_string(const char * str,int * latitude_degrees,int * latitude_minutes,int * latitude_seconds,int * longitude_degrees,int * longitude_minutes,int * longitude_seconds,char * location)1555 fetch_lat_long_from_string  (const char *str, int *latitude_degrees, int *latitude_minutes, int *latitude_seconds,
1556 		int *longitude_degrees, int *longitude_minutes, int *longitude_seconds, char *location)
1557 {
1558 	size_t len;
1559 	char *sptr, *lat, *lon, *loc, *temp;
1560 
1561 	/* We need to parse the latitude/longitude co-ordinates and location fields  */
1562 	sptr = (char *) str;
1563 	while (*sptr != '\t')
1564 		sptr++;
1565 	temp = ++sptr;
1566 	while (*sptr != '\t')
1567 		sptr++;
1568 	len = sptr-temp;
1569 	lat = (char *) malloc (len + 1);
1570 	lat = strncpy (lat, temp, len);
1571 	lat [len] = '\0';
1572 	while (*sptr != '\t')
1573 		sptr++;
1574 
1575 	loc = ++sptr;
1576 	while (!isspace (*sptr))
1577 		sptr++;
1578 	len = sptr - loc;
1579 	location = strncpy (location, loc, len);
1580 	location [len] = '\0';
1581 
1582 #if defined(sun) && defined(__SVR4)
1583     /* Handle EET, MET and WET in zone_sun.tab. */
1584     if (!strcmp (location, "Europe/")) {
1585         while (*sptr != '\t')
1586             sptr++;
1587         loc = ++sptr;
1588         while (!isspace (*sptr))
1589             sptr++;
1590         len = sptr - loc;
1591         location = strncpy (location, loc, len);
1592         location [len] = '\0';
1593     }
1594 #endif
1595 
1596 	lon = lat + 1;
1597 	while (*lon != '+' && *lon != '-')
1598 		lon++;
1599 
1600 	if (parse_coord (lat, lon - lat, latitude_degrees, latitude_minutes, latitude_seconds) == 1 ||
1601 		       	parse_coord (lon, strlen (lon), longitude_degrees, longitude_minutes, longitude_seconds)
1602 			== 1) {
1603 				free(lat);
1604 				return 1;
1605 			}
1606 
1607 	free (lat);
1608 
1609 	return 0;
1610 }
1611 
1612 /** This parses the zones.tab file containing the names and locations
1613    of the builtin timezones. It creates the builtin_timezones array
1614    which is an icalarray of icaltimezone structs. It only fills in the
1615    location, latitude and longtude fields; the rest are left
1616    blank. The VTIMEZONE component is loaded later if it is needed. The
1617    timezones in the zones.tab file are sorted by their name, which is
1618    useful for binary searches. */
1619 static void
icaltimezone_parse_zone_tab(void)1620 icaltimezone_parse_zone_tab		(void)
1621 {
1622 #ifndef NO_ZONES_TAB
1623     char *filename;
1624     FILE *fp;
1625     char buf[1024];  /* Used to store each line of zones.tab as it is read. */
1626     char location[1024]; /* Stores the city name when parsing buf. */
1627     unsigned int filename_len;
1628     int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0;
1629     int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0;
1630     icaltimezone zone;
1631 
1632     icalerror_assert (s_builtin_timezones == NULL,
1633 		      "Parsing zones.tab file multiple times");
1634 
1635     s_builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1636 
1637 #ifndef USE_BUILTIN_TZDATA
1638     filename_len = strlen ((char *) icaltzutil_get_zone_directory()) + strlen (ZONES_TAB_SYSTEM_FILENAME)
1639 	+ 2;
1640 #else
1641     filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1642 	+ 2;
1643 #endif
1644 
1645     filename = (char*) malloc (filename_len);
1646     if (!filename) {
1647 	icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1648 	return;
1649     }
1650 #ifndef USE_BUILTIN_TZDATA
1651     snprintf (filename, filename_len, "%s/%s", icaltzutil_get_zone_directory (),
1652 	      ZONES_TAB_SYSTEM_FILENAME);
1653 #else
1654     snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1655 	      ZONES_TAB_FILENAME);
1656 #endif
1657 
1658     fp = fopen (filename, "r");
1659     free (filename);
1660     if (!fp) {
1661 	icalerror_set_errno(ICAL_FILE_ERROR);
1662 	return;
1663     }
1664 
1665     while (fgets (buf, sizeof(buf), fp)) {
1666 	if (*buf == '#') continue;
1667 
1668 #ifdef USE_BUILTIN_TZDATA
1669 	/* The format of each line is: "latitude longitude location". */
1670 	if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
1671 		    &latitude_degrees, &latitude_minutes,
1672 		    &latitude_seconds,
1673 		    &longitude_degrees, &longitude_minutes,
1674 		    &longitude_seconds,
1675 		    location) != 7) {
1676 	    fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1677 	    continue;
1678 	}
1679 #else
1680 	if (fetch_lat_long_from_string (buf, &latitude_degrees, &latitude_minutes,
1681 				&latitude_seconds,
1682 				&longitude_degrees, &longitude_minutes, &longitude_seconds,
1683 				location)) {
1684 	    fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1685 	    continue;
1686 	}
1687 #endif
1688 
1689 	icaltimezone_init (&zone);
1690 	zone.location = strdup (location);
1691 
1692 	if (latitude_degrees >= 0)
1693 	    zone.latitude = (double) latitude_degrees
1694 		+ (double) latitude_minutes / 60
1695 		+ (double) latitude_seconds / 3600;
1696 	else
1697 	    zone.latitude = (double) latitude_degrees
1698 		- (double) latitude_minutes / 60
1699 		- (double) latitude_seconds / 3600;
1700 
1701 	if (longitude_degrees >= 0)
1702 	    zone.longitude = (double) longitude_degrees
1703 		+ (double) longitude_minutes / 60
1704 		+ (double) longitude_seconds / 3600;
1705 	else
1706 	    zone.longitude = (double) longitude_degrees
1707 		- (double) longitude_minutes / 60
1708 		- (double) longitude_seconds / 3600;
1709 
1710 	icalarray_append (s_builtin_timezones, &zone);
1711 
1712 #if 0
1713 	printf ("Found zone: %s %f %f\n",
1714 		location, zone.latitude, zone.longitude);
1715 #endif
1716     }
1717 
1718     fclose (fp);
1719 #endif /* NO_ZONES_TAB */
1720 }
1721 
1722 void
icaltimezone_release_zone_tab(void)1723 icaltimezone_release_zone_tab		(void)
1724 {
1725     unsigned int i;
1726     icalarray *mybuiltin_timezones = s_builtin_timezones;
1727     if (s_builtin_timezones == NULL)
1728 	return;
1729     s_builtin_timezones = NULL;
1730     for (i = 0; i < mybuiltin_timezones->num_elements; i++)
1731 	free ( ((icaltimezone*)icalarray_element_at(mybuiltin_timezones, i))->location);
1732     icalarray_free (mybuiltin_timezones);
1733 }
1734 
1735 /** Loads the builtin VTIMEZONE data for the given timezone. */
1736 static void
icaltimezone_load_builtin_timezone(icaltimezone * zone)1737 icaltimezone_load_builtin_timezone	(icaltimezone *zone)
1738 {
1739 #ifndef NO_ZONES_TAB
1740     icalcomponent *subcomp;
1741 
1742 	    /* If the location isn't set, it isn't a builtin timezone. */
1743     if (!zone->location || !zone->location[0])
1744 	return;
1745 
1746 #ifdef USE_BUILTIN_TZDATA
1747     {
1748     char *filename;
1749     icalcomponent *comp;
1750     unsigned int filename_len;
1751     FILE *fp;
1752     icalparser *parser;
1753 
1754     filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1755 
1756     filename = (char*) malloc (filename_len);
1757     if (!filename) {
1758 	icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1759 	return;
1760     }
1761 
1762     snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1763 	      zone->location);
1764 
1765     fp = fopen (filename, "r");
1766     free (filename);
1767     if (!fp) {
1768 	icalerror_set_errno(ICAL_FILE_ERROR);
1769 	return;
1770     }
1771 
1772 
1773 	/* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1774 	this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1775 	fprintf(stderr, "** WARNING ** %s: %d %s\n", __FILE__, __LINE__, icalerror_strerror(icalerrno));
1776 	*/
1777 
1778     parser = icalparser_new ();
1779 	icalparser_set_gen_data (parser, fp);
1780 	comp = icalparser_parse (parser, icaltimezone_load_get_line_fn);
1781     icalparser_free (parser);
1782 	fclose (fp);
1783 
1784     /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1785     subcomp = icalcomponent_get_first_component (comp,
1786 						 ICAL_VTIMEZONE_COMPONENT);
1787 #else
1788 	subcomp = icaltzutil_fetch_timezone (zone->location);
1789 #endif
1790 
1791     if (!subcomp) {
1792 	icalerror_set_errno(ICAL_PARSE_ERROR);
1793 	return;
1794     }
1795 
1796     icaltimezone_get_vtimezone_properties (zone, subcomp);
1797 
1798 #ifdef USE_BUILTIN_TZDATA
1799     icalcomponent_remove_component(comp,subcomp);
1800     icalcomponent_free(comp);
1801     }
1802 #endif
1803 #endif /* NO_ZONES_TAB */
1804 }
1805 
1806 
1807 #ifdef USE_BUILTIN_TZDATA
1808 /** Callback used from icalparser_parse() */
1809 static char *
icaltimezone_load_get_line_fn(char * s,size_t size,void * data)1810 icaltimezone_load_get_line_fn		(char		*s,
1811 					 size_t		 size,
1812 					 void		*data)
1813 {
1814     return fgets (s, (int)size, (FILE*) data);
1815 }
1816 #endif
1817 
1818 
1819 
1820 /*
1821  * DEBUGGING
1822  */
1823 
1824 /**
1825  * This outputs a list of timezone changes for the given timezone to the
1826  * given file, up to the maximum year given. We compare this output with the
1827  * output from 'vzic --dump-changes' to make sure that we are consistent.
1828  * (vzic is the Olson timezone database to VTIMEZONE converter.)
1829  *
1830  * The output format is:
1831  *
1832  *	Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1833  *
1834  * The Date and Time fields specify the time change in UTC.
1835  *
1836  * The UTC Offset is for local (wall-clock) time. It is the amount of time
1837  * to add to UTC to get local time.
1838  */
1839 int
icaltimezone_dump_changes(icaltimezone * zone,int max_year,FILE * fp)1840 icaltimezone_dump_changes		(icaltimezone *zone,
1841 					 int		 max_year,
1842 					 FILE		*fp)
1843 {
1844     static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1845 			      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1846     icaltimezonechange *zone_change;
1847     int change_num;
1848     char buffer[8];
1849 
1850     /* Make sure the changes array is expanded up to the given time. */
1851     icaltimezone_ensure_coverage (zone, max_year);
1852 
1853 #if 0
1854     printf ("Num changes: %i\n", zone->changes->num_elements);
1855 #endif
1856 
1857     change_num = 0;
1858     for (change_num = 0; (unsigned int)change_num < zone->changes->num_elements; change_num++) {
1859 	zone_change = icalarray_element_at (zone->changes, change_num);
1860 
1861 	if (zone_change->year > max_year)
1862 	    break;
1863 
1864 	fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1865 		zone->location,
1866 		zone_change->day, months[zone_change->month - 1],
1867 		zone_change->year,
1868 		zone_change->hour, zone_change->minute, zone_change->second);
1869 
1870 	/* Wall Clock Time offset from UTC. */
1871 	format_utc_offset (zone_change->utc_offset, buffer, sizeof(buffer));
1872 	fprintf (fp, "\t%s", buffer);
1873 
1874 	fprintf (fp, "\n");
1875     }
1876 	return 1;
1877 }
1878 
1879 
1880 /** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1881    buffer should have space for 8 characters. */
1882 static void
format_utc_offset(int utc_offset,char * buffer,size_t buffer_size)1883 format_utc_offset			(int		 utc_offset,
1884 					 char		*buffer, size_t buffer_size)
1885 {
1886   const char *sign = "+";
1887   int hours, minutes, seconds;
1888 
1889   if (utc_offset < 0) {
1890     utc_offset = -utc_offset;
1891     sign = "-";
1892   }
1893 
1894   hours = utc_offset / 3600;
1895   minutes = (utc_offset % 3600) / 60;
1896   seconds = utc_offset % 60;
1897 
1898   /* Sanity check. Standard timezone offsets shouldn't be much more than 12
1899      hours, and daylight saving shouldn't change it by more than a few hours.
1900      (The maximum offset is 15 hours 56 minutes at present.) */
1901   if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
1902       || seconds < 0 || seconds >= 60) {
1903     fprintf (stderr, "Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1904 	     hours, minutes, seconds);
1905   }
1906 
1907   if (seconds == 0)
1908     snprintf (buffer, buffer_size, "%s%02i%02i", sign, hours, minutes);
1909   else
1910     snprintf (buffer, buffer_size, "%s%02i%02i%02i", sign, hours, minutes, seconds);
1911 }
1912 
get_zone_directory(void)1913 static const char* get_zone_directory(void)
1914 {
1915 #ifndef WIN32
1916 	return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1917 #else
1918 	wchar_t wbuffer[1000];
1919 	char buffer[1000], zoneinfodir[1000], dirname[1000];
1920 	int used_default;
1921 	static char *cache = NULL;
1922 	char *dirslash, *zislash;
1923 	struct stat st;
1924 
1925 	if (zone_files_directory)
1926 	    return zone_files_directory;
1927 
1928 	if (cache)
1929 	    return cache;
1930 
1931 	/* Get the filename of the application */
1932 	if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
1933 	    return ZONEINFO_DIRECTORY;
1934 
1935 	/* Convert to system codepage */
1936 	if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
1937 				  NULL, &used_default) ||
1938 	    used_default) {
1939 	    /* Failed, try 8.3 format */
1940 	    if (!GetShortPathNameW (wbuffer, wbuffer,
1941 				    sizeof (wbuffer) / sizeof (wbuffer[0])) ||
1942 		!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
1943 				      NULL, &used_default) ||
1944 		used_default)
1945 		return ZONEINFO_DIRECTORY;
1946 	}
1947 	/* Look for the zoneinfo directory somewhere in the path where
1948 	 * the app is installed. If the path to the app is
1949 	 *
1950 	 *	C:\opt\evo-2.6\bin\evolution-2.6.exe
1951 	 *
1952 	 * and the compile-time ZONEINFO_DIRECTORY is
1953 	 *
1954 	 *	C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
1955 	 *
1956 	 * we check the pathnames:
1957 	 *
1958 	 *	C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
1959 	 *	C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
1960 	 *	C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
1961 	 *	C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo		<===
1962 	 *	C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
1963 	 *	C:\opt\evo-2.6/zoneinfo
1964 	 *	C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
1965 	 *	C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
1966 	 *	C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
1967 	 *	C:\opt/share/evolution-data-server-1.6/zoneinfo
1968 	 *	C:\opt/evolution-data-server-1.6/zoneinfo
1969 	 *	C:\opt/zoneinfo
1970 	 *	C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
1971 	 *	C:/target/evo/share/evolution-data-server-1.6/zoneinfo
1972 	 *	C:/evo/share/evolution-data-server-1.6/zoneinfo
1973 	 *	C:/share/evolution-data-server-1.6/zoneinfo
1974 	 *	C:/evolution-data-server-1.6/zoneinfo
1975 	 *	C:/zoneinfo
1976 	 *
1977 	 * In Evolution's case, we would get a match already at the
1978 	 * fourth pathname check.
1979 	 */
1980 
1981 	/* Strip away basename of app .exe first */
1982 	dirslash = _mbsrchr (buffer, '\\');
1983 	if (dirslash)
1984 	    *dirslash = '\0';
1985 
1986 	while ((dirslash = _mbsrchr (buffer, '\\'))) {
1987 	    /* Strip one more directory from app .exe location */
1988 	    *dirslash = '\0';
1989 
1990 	    strcpy (zoneinfodir, ZONEINFO_DIRECTORY);
1991 	    while ((zislash = _mbschr (zoneinfodir, '/'))) {
1992 		*zislash = '.';
1993 		strcpy (dirname, buffer);
1994 		strcat (dirname, "/");
1995 		strcat (dirname, zislash + 1);
1996 		if (stat (dirname, &st) == 0 &&
1997 		    S_ISDIR (st.st_mode)) {
1998 		    cache = strdup (dirname);
1999 		    return cache;
2000 		}
2001 	    }
2002 	}
2003 	return ZONEINFO_DIRECTORY;
2004 #endif
2005 }
2006 
set_zone_directory(char * path)2007 void set_zone_directory(char *path)
2008 {
2009 	if (zone_files_directory)
2010 		free_zone_directory();
2011 	zone_files_directory = malloc(strlen(path)+1);
2012 	if ( zone_files_directory != NULL )
2013 	{
2014 		strcpy(zone_files_directory,path);
2015 	}
2016 }
2017 
free_zone_directory(void)2018 void free_zone_directory(void)
2019 {
2020 	if ( zone_files_directory != NULL )
2021 	{
2022 		free(zone_files_directory);
2023 		zone_files_directory = NULL;
2024 	}
2025 }
2026 
icaltimezone_set_tzid_prefix(const char * new_prefix)2027 void icaltimezone_set_tzid_prefix(const char *new_prefix)
2028 {
2029 	if (new_prefix) {
2030 		ical_tzid_prefix = new_prefix;
2031 	}
2032 }
2033