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