summaryrefslogtreecommitdiffabout
path: root/libical/src/libical/icaltimezone.c
Unidiff
Diffstat (limited to 'libical/src/libical/icaltimezone.c') (more/less context) (ignore whitespace changes)
-rw-r--r--libical/src/libical/icaltimezone.c1672
1 files changed, 1672 insertions, 0 deletions
diff --git a/libical/src/libical/icaltimezone.c b/libical/src/libical/icaltimezone.c
new file mode 100644
index 0000000..fdb6b91
--- a/dev/null
+++ b/libical/src/libical/icaltimezone.c
@@ -0,0 +1,1672 @@
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$
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 <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include "icalproperty.h"
37#include "icalarray.h"
38#include "icalerror.h"
39#include "icalparser.h"
40#include "icaltimezone.h"
41
42#ifdef WIN32
43#define snprintf _snprintf
44#define PACKAGE_DATA_DIR "/Projects/libical"
45#endif
46
47/** This is the toplevel directory where the timezone data is installed in. */
48#define ZONEINFO_DIRECTORY "/zoneinfo"
49
50/** The prefix we use to uniquely identify TZIDs. */
51 #define TZID_PREFIX "/softwarestudio.org/"
52 #define TZID_PREFIX_LEN 20
53
54/** This is the filename of the file containing the city names and
55 coordinates of all the builtin timezones. */
56 #define ZONES_TAB_FILENAME"zones.tab"
57
58/** This is the number of years of extra coverage we do when expanding
59 the timezone changes. */
60 #define ICALTIMEZONE_EXTRA_COVERAGE5
61
62/** This is the maximum year we will expand to. time_t values only go up to
63 somewhere around 2037. */
64 #define ICALTIMEZONE_MAX_YEAR 2035
65
66struct _icaltimezone {
67 char *tzid;
68 /**< The unique ID of this timezone,
69 e.g. "/softwarestudio.org/Olson_20010601_1/Africa/Banjul".
70 This should only be used to identify a VTIMEZONE. It is not
71 meant to be displayed to the user in any form. */
72
73 char *location;
74 /**< The location for the timezone, e.g. "Africa/Accra" for the
75 Olson database. We look for this in the "LOCATION" or
76 "X-LIC-LOCATION" properties of the VTIMEZONE component. It
77 isn't a standard property yet. This will be NULL if no location
78 is found in the VTIMEZONE. */
79
80 char *tznames;
81 /**< This will be set to a combination of the TZNAME properties
82 from the last STANDARD and DAYLIGHT components in the
83 VTIMEZONE, e.g. "EST/EDT". If they both use the same TZNAME,
84 or only one type of component is found, then only one TZNAME
85 will appear, e.g. "AZOT". If no TZNAME is found this will be
86 NULL. */
87
88 double latitude;
89 double longitude;
90 /**< The coordinates of the city, in degrees. */
91
92 icalcomponent*component;
93 /**< The toplevel VTIMEZONE component loaded from the .ics file for this
94 timezone. If we need to regenerate the changes data we need this. */
95
96 icaltimezone*builtin_timezone;
97 /**< If this is not NULL it points to the builtin icaltimezone
98 that the above TZID refers to. This icaltimezone should be used
99 instead when accessing the timezone changes data, so that the
100 expanded timezone changes data is shared between calendar
101 components. */
102
103 int end_year;
104 /**< This is the last year for which we have expanded the data to.
105 If we need to calculate a date past this we need to expand the
106 timezone component data from scratch. */
107
108 icalarray *changes;
109 /**< A dynamically-allocated array of time zone changes, sorted by the
110 time of the change in local time. So we can do fast binary-searches
111 to convert from local time to UTC. */
112};
113
114 typedef struct _icaltimezonechangeicaltimezonechange;
115
116struct _icaltimezonechange {
117 int utc_offset;
118 /**< The offset to add to UTC to get local time, in seconds. */
119
120 int prev_utc_offset;
121 /**< The offset to add to UTC, before this change, in seconds. */
122
123 int year; /**< Actual year, e.g. 2001. */
124 int month; /**< 1 (Jan) to 12 (Dec). */
125 int day;
126 int hour;
127 int minute;
128 int second;
129 /**< The time that the change came into effect, in UTC.
130 Note that the prev_utc_offset applies to this local time,
131 since we haven't changed to the new offset yet. */
132
133 int is_daylight;
134 /**< Whether this is STANDARD or DAYLIGHT time. */
135};
136
137
138/** An array of icaltimezones for the builtin timezones. */
139static icalarray *builtin_timezones = NULL;
140
141/** This is the special UTC timezone, which isn't in builtin_timezones. */
142static icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
143
144static char* zone_files_directory = NULL;
145
146 static void icaltimezone_reset (icaltimezone *zone);
147static char* icaltimezone_get_location_from_vtimezone (icalcomponent *component);
148static char* icaltimezone_get_tznames_from_vtimezone (icalcomponent *component);
149 static void icaltimezone_expand_changes (icaltimezone*zone,
150 int end_year);
151 static void icaltimezone_expand_vtimezone (icalcomponent*comp,
152 int end_year,
153 icalarray*changes);
154 static int icaltimezone_compare_change_fn (const void*elem1,
155 const void*elem2);
156
157 static int icaltimezone_find_nearby_change (icaltimezone*zone,
158 icaltimezonechange *change);
159
160 static void icaltimezone_adjust_change (icaltimezonechange *tt,
161 int days,
162 int hours,
163 int minutes,
164 int seconds);
165
166 static void icaltimezone_init (icaltimezone*zone);
167
168/** Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from the
169 VTIMEZONE component and places them in the icaltimezone. It returns 1 on
170 success, or 0 if the TZID can't be found. */
171static int icaltimezone_get_vtimezone_properties (icaltimezone *zone,
172 icalcomponent *component);
173
174
175 static void icaltimezone_load_builtin_timezone (icaltimezone*zone);
176
177 static void icaltimezone_ensure_coverage (icaltimezone*zone,
178 int end_year);
179
180
181static void icaltimezone_init_builtin_timezones(void);
182
183 static void icaltimezone_parse_zone_tab(void);
184
185 static char* icaltimezone_load_get_line_fn (char *s,
186 size_t size,
187 void *data);
188
189 static void format_utc_offset (int utc_offset,
190 char *buffer);
191
192static char* get_zone_directory(void);
193
194
195/** Creates a new icaltimezone. */
196icaltimezone*
197 icaltimezone_new (void)
198{
199 icaltimezone *zone;
200
201 zone = (icaltimezone*) malloc (sizeof (icaltimezone));
202 if (!zone) {
203 icalerror_set_errno (ICAL_NEWFAILED_ERROR);
204 return NULL;
205 }
206
207 icaltimezone_init (zone);
208
209 return zone;
210}
211
212
213/** Frees all memory used for the icaltimezone. */
214void
215 icaltimezone_free (icaltimezone *zone,
216 int free_struct)
217{
218 icaltimezone_reset (zone);
219 if (free_struct)
220 free (zone);
221}
222
223
224/** Resets the icaltimezone to the initial state, freeing most of the fields. */
225static void
226 icaltimezone_reset (icaltimezone *zone)
227{
228 if (zone->tzid)
229 free (zone->tzid);
230 if (zone->location)
231 free (zone->location);
232 if (zone->tznames)
233 free (zone->tznames);
234 if (zone->component)
235 icalcomponent_free (zone->component);
236 if (zone->changes)
237 icalarray_free (zone->changes);
238
239 icaltimezone_init (zone);
240}
241
242
243/** Initializes an icaltimezone. */
244static void
245 icaltimezone_init (icaltimezone*zone)
246{
247 zone->tzid = NULL;
248 zone->location = NULL;
249 zone->tznames = NULL;
250 zone->latitude = 0.0;
251 zone->longitude = 0.0;
252 zone->component = NULL;
253 zone->builtin_timezone = NULL;
254 zone->end_year = 0;
255 zone->changes = NULL;
256}
257
258
259/** Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of
260 the VTIMEZONE component and stores them in the icaltimezone. It
261 returns 1 on success, or 0 if the TZID can't be found. Note that
262 it expects the zone to be initialized or reset - it doesn't free
263 any old values. */
264static int
265 icaltimezone_get_vtimezone_properties (icaltimezone*zone,
266 icalcomponent*component)
267{
268 icalproperty *prop;
269 const char *tzid;
270
271 prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
272 if (!prop)
273 return 0;
274
275 /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
276 tzid = icalproperty_get_tzid (prop);
277 if (!tzid)
278 return 0;
279
280 zone->tzid = strdup (tzid);
281 zone->component = component;
282 if ( zone->location != 0 ) free ( zone->location );
283 zone->location = icaltimezone_get_location_from_vtimezone (component);
284 zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
285
286 return 1;
287}
288
289/** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
290static char*
291icaltimezone_get_location_from_vtimezone (icalcomponent *component)
292{
293 icalproperty *prop;
294 const char *location;
295 const char *name;
296
297 prop = icalcomponent_get_first_property (component,
298 ICAL_LOCATION_PROPERTY);
299 if (prop) {
300 location = icalproperty_get_location (prop);
301 if (location)
302 return strdup (location);
303 }
304
305 prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
306 while (prop) {
307 name = icalproperty_get_x_name (prop);
308 if (name && !strcmp (name, "X-LIC-LOCATION")) {
309 location = icalproperty_get_x (prop);
310 if (location)
311 return strdup (location);
312 }
313 prop = icalcomponent_get_next_property (component,
314 ICAL_X_PROPERTY);
315 }
316
317 return NULL;
318}
319
320
321/** Gets the TZNAMEs used for the last STANDARD & DAYLIGHT components
322 in a VTIMEZONE. If both STANDARD and DAYLIGHT components use the
323 same TZNAME, it returns that. If they use different TZNAMEs, it
324 formats them like "EST/EDT". The returned string should be freed by
325 the caller. */
326static char*
327icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
328{
329 icalcomponent *comp;
330 icalcomponent_kind type;
331 icalproperty *prop;
332 struct icaltimetype dtstart;
333 struct icaldatetimeperiodtype rdate;
334 const char *current_tzname;
335 const char *standard_tzname = NULL, *daylight_tzname = NULL;
336 struct icaltimetype standard_max_date, daylight_max_date;
337 struct icaltimetype current_max_date;
338
339 standard_max_date = icaltime_null_time();
340 daylight_max_date = icaltime_null_time();
341
342 /* Step through the STANDARD & DAYLIGHT subcomponents. */
343 comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
344 while (comp) {
345 type = icalcomponent_isa (comp);
346 if (type == ICAL_XSTANDARD_COMPONENT
347 || type == ICAL_XDAYLIGHT_COMPONENT) {
348 current_max_date = icaltime_null_time ();
349 current_tzname = NULL;
350
351 /* Step through the properties. We want to find the TZNAME, and
352 the largest DTSTART or RDATE. */
353 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
354 while (prop) {
355 switch (icalproperty_isa (prop)) {
356 case ICAL_TZNAME_PROPERTY:
357 current_tzname = icalproperty_get_tzname (prop);
358 break;
359
360 case ICAL_DTSTART_PROPERTY:
361 dtstart = icalproperty_get_dtstart (prop);
362 if (icaltime_compare (dtstart, current_max_date) > 0)
363 current_max_date = dtstart;
364
365 break;
366
367 case ICAL_RDATE_PROPERTY:
368 rdate = icalproperty_get_rdate (prop);
369 if (icaltime_compare (rdate.time, current_max_date) > 0)
370 current_max_date = rdate.time;
371
372 break;
373
374 default:
375 break;
376 }
377
378 prop = icalcomponent_get_next_property (comp,
379 ICAL_ANY_PROPERTY);
380 }
381
382 if (current_tzname) {
383 if (type == ICAL_XSTANDARD_COMPONENT) {
384 if (!standard_tzname
385 || icaltime_compare (current_max_date,
386 standard_max_date) > 0) {
387 standard_max_date = current_max_date;
388 standard_tzname = current_tzname;
389 }
390 } else {
391 if (!daylight_tzname
392 || icaltime_compare (current_max_date,
393 daylight_max_date) > 0) {
394 daylight_max_date = current_max_date;
395 daylight_tzname = current_tzname;
396 }
397 }
398 }
399 }
400
401 comp = icalcomponent_get_next_component (component,
402 ICAL_ANY_COMPONENT);
403 }
404
405 /* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
406 strings, which is totally useless. So we return NULL in that case. */
407 if (standard_tzname && !strcmp (standard_tzname, "Standard Time"))
408 return NULL;
409
410 /* If both standard and daylight TZNAMEs were found, if they are the same
411 we return just one, else we format them like "EST/EDT". */
412 if (standard_tzname && daylight_tzname) {
413 unsigned int standard_len, daylight_len;
414 char *tznames;
415
416 if (!strcmp (standard_tzname, daylight_tzname))
417 return strdup (standard_tzname);
418
419 standard_len = strlen (standard_tzname);
420 daylight_len = strlen (daylight_tzname);
421 tznames = malloc (standard_len + daylight_len + 2);
422 strcpy (tznames, standard_tzname);
423 tznames[standard_len] = '/';
424 strcpy (tznames + standard_len + 1, daylight_tzname);
425 return tznames;
426 } else {
427 const char *tznames;
428
429 /* If either of the TZNAMEs was found just return that, else NULL. */
430 tznames = standard_tzname ? standard_tzname : daylight_tzname;
431 return tznames ? strdup (tznames) : NULL;
432 }
433}
434
435
436static void
437 icaltimezone_ensure_coverage (icaltimezone*zone,
438 int end_year)
439{
440 /* When we expand timezone changes we always expand at least up to this
441 year, plus ICALTIMEZONE_EXTRA_COVERAGE. */
442 static int icaltimezone_minimum_expansion_year = -1;
443
444 int changes_end_year;
445
446 if (!zone->component)
447 icaltimezone_load_builtin_timezone (zone);
448
449 if (icaltimezone_minimum_expansion_year == -1) {
450 struct icaltimetype today = icaltime_today();
451 icaltimezone_minimum_expansion_year = today.year;
452 }
453
454 changes_end_year = end_year;
455 if (changes_end_year < icaltimezone_minimum_expansion_year)
456 changes_end_year = icaltimezone_minimum_expansion_year;
457
458 changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
459
460 if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
461 changes_end_year = ICALTIMEZONE_MAX_YEAR;
462
463 if (!zone->changes || zone->end_year < end_year)
464 icaltimezone_expand_changes (zone, changes_end_year);
465}
466
467
468static void
469 icaltimezone_expand_changes (icaltimezone*zone,
470 int end_year)
471{
472 icalarray *changes;
473 icalcomponent *comp;
474
475#if 0
476 printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
477#endif
478
479 changes = icalarray_new (sizeof (icaltimezonechange), 32);
480 if (!changes)
481 return;
482
483 /* Scan the STANDARD and DAYLIGHT subcomponents. */
484 comp = icalcomponent_get_first_component (zone->component,
485 ICAL_ANY_COMPONENT);
486 while (comp) {
487 icaltimezone_expand_vtimezone (comp, end_year, changes);
488 comp = icalcomponent_get_next_component (zone->component,
489 ICAL_ANY_COMPONENT);
490 }
491
492 /* Sort the changes. We may have duplicates but I don't think it will
493 matter. */
494 icalarray_sort (changes, icaltimezone_compare_change_fn);
495
496 if (zone->changes)
497 icalarray_free (zone->changes);
498
499 zone->changes = changes;
500 zone->end_year = end_year;
501}
502
503
504static void
505 icaltimezone_expand_vtimezone (icalcomponent*comp,
506 int end_year,
507 icalarray*changes)
508{
509 icaltimezonechange change;
510 icalproperty *prop;
511 struct icaltimetype dtstart, occ;
512 struct icalrecurrencetype rrule;
513 icalrecur_iterator* rrule_iterator;
514 struct icaldatetimeperiodtype rdate;
515 int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
516 int has_recurrence = 0;
517
518 /* First we check if it is a STANDARD or DAYLIGHT component, and
519 just return if it isn't. */
520 if (icalcomponent_isa (comp) == ICAL_XSTANDARD_COMPONENT)
521 change.is_daylight = 0;
522 else if (icalcomponent_isa (comp) == ICAL_XDAYLIGHT_COMPONENT)
523 change.is_daylight = 1;
524 else
525 return;
526
527 /* Step through each of the properties to find the DTSTART,
528 TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
529 since we need these properties before we can do that. */
530 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
531 while (prop) {
532 switch (icalproperty_isa (prop)) {
533 case ICAL_DTSTART_PROPERTY:
534 dtstart = icalproperty_get_dtstart (prop);
535 found_dtstart = 1;
536 break;
537 case ICAL_TZOFFSETTO_PROPERTY:
538 change.utc_offset = icalproperty_get_tzoffsetto (prop);
539 /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset);*/
540 found_tzoffsetto = 1;
541 break;
542 case ICAL_TZOFFSETFROM_PROPERTY:
543 change.prev_utc_offset = icalproperty_get_tzoffsetfrom (prop);
544 /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset);*/
545 found_tzoffsetfrom = 1;
546 break;
547 case ICAL_RDATE_PROPERTY:
548 case ICAL_RRULE_PROPERTY:
549 has_recurrence = 1;
550 break;
551 default:
552 /* Just ignore any other properties. */
553 break;
554 }
555
556 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
557 }
558
559 /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
560 ignore the component. FIXME: Add an error property? */
561 if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
562 return;
563
564#if 0
565 printf ("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
566 dtstart.year, dtstart.month, dtstart.day,
567 dtstart.hour, dtstart.minute, dtstart.second);
568#endif
569
570 /* If the STANDARD/DAYLIGHT component has no recurrence data, we just add
571 a single change for the DTSTART. */
572 if (!has_recurrence) {
573 change.year = dtstart.year;
574 change.month = dtstart.month;
575 change.day = dtstart.day;
576 change.hour = dtstart.hour;
577 change.minute = dtstart.minute;
578 change.second = dtstart.second;
579
580 /* Convert to UTC. */
581 icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
582
583#if 0
584 printf (" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
585 change.year, change.month, change.day,
586 change.hour, change.minute, change.second);
587#endif
588
589 /* Add the change to the array. */
590 icalarray_append (changes, &change);
591 return;
592 }
593
594 /* The component has recurrence data, so we expand that now. */
595 prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
596 while (prop) {
597#if 0
598 printf ("Expanding property...\n");
599#endif
600 switch (icalproperty_isa (prop)) {
601 case ICAL_RDATE_PROPERTY:
602 rdate = icalproperty_get_rdate (prop);
603 change.year = rdate.time.year;
604 change.month = rdate.time.month;
605 change.day = rdate.time.day;
606 /* RDATEs with a DATE value inherit the time from
607 the DTSTART. */
608 if (icaltime_is_date(rdate.time)) {
609 change.hour = dtstart.hour;
610 change.minute = dtstart.minute;
611 change.second = dtstart.second;
612 } else {
613 change.hour = rdate.time.hour;
614 change.minute = rdate.time.minute;
615 change.second = rdate.time.second;
616
617 /* The spec was a bit vague about whether RDATEs were in local
618 time or UTC so we support both to be safe. So if it is in
619 UTC we have to add the UTC offset to get a local time. */
620 if (!icaltime_is_utc(rdate.time))
621 icaltimezone_adjust_change (&change, 0, 0, 0,
622 -change.prev_utc_offset);
623 }
624
625#if 0
626 printf (" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
627 change.year, change.month, change.day,
628 change.hour, change.minute, change.second);
629#endif
630
631 icalarray_append (changes, &change);
632 break;
633 case ICAL_RRULE_PROPERTY:
634 rrule = icalproperty_get_rrule (prop);
635
636 /* If the rrule UNTIL value is set and is in UTC, we convert it to
637 a local time, since the recurrence code has no way to convert
638 it itself. */
639 if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
640#if 0
641 printf (" Found RRULE UNTIL in UTC.\n");
642#endif
643
644 /* To convert from UTC to a local time, we use the TZOFFSETFROM
645 since that is the offset from UTC that will be in effect
646 when each of the RRULE occurrences happens. */
647 icaltime_adjust (&rrule.until, 0, 0, 0,
648 change.prev_utc_offset);
649 rrule.until.is_utc = 0;
650 }
651
652 rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
653 for (;;) {
654 occ = icalrecur_iterator_next (rrule_iterator);
655 if (occ.year > end_year || icaltime_is_null_time (occ))
656 break;
657
658 change.year = occ.year;
659 change.month = occ.month;
660 change.day = occ.day;
661 change.hour = occ.hour;
662 change.minute = occ.minute;
663 change.second = occ.second;
664
665#if 0
666 printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
667 change.year, change.month, change.day,
668 change.hour, change.minute, change.second);
669#endif
670
671 icaltimezone_adjust_change (&change, 0, 0, 0,
672 -change.prev_utc_offset);
673
674 icalarray_append (changes, &change);
675 }
676
677 icalrecur_iterator_free (rrule_iterator);
678 break;
679 default:
680 break;
681 }
682
683 prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
684 }
685}
686
687
688/** A function to compare 2 icaltimezonechange elements, used for qsort(). */
689static int
690 icaltimezone_compare_change_fn (const void*elem1,
691 const void*elem2)
692{
693 const icaltimezonechange *change1, *change2;
694 int retval;
695
696 change1 = elem1;
697 change2 = elem2;
698
699 if (change1->year < change2->year)
700 retval = -1;
701 else if (change1->year > change2->year)
702 retval = 1;
703
704 else if (change1->month < change2->month)
705 retval = -1;
706 else if (change1->month > change2->month)
707 retval = 1;
708
709 else if (change1->day < change2->day)
710 retval = -1;
711 else if (change1->day > change2->day)
712 retval = 1;
713
714 else if (change1->hour < change2->hour)
715 retval = -1;
716 else if (change1->hour > change2->hour)
717 retval = 1;
718
719 else if (change1->minute < change2->minute)
720 retval = -1;
721 else if (change1->minute > change2->minute)
722 retval = 1;
723
724 else if (change1->second < change2->second)
725 retval = -1;
726 else if (change1->second > change2->second)
727 retval = 1;
728
729 else
730 retval = 0;
731
732 return retval;
733}
734
735
736
737void
738 icaltimezone_convert_time (struct icaltimetype *tt,
739 icaltimezone*from_zone,
740 icaltimezone*to_zone)
741{
742 int utc_offset, is_daylight;
743
744 /* If the time is a DATE value or both timezones are the same, or we are
745 converting a floating time, we don't need to do anything. */
746 if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
747 return;
748
749 /* Convert the time to UTC by getting the UTC offset and subtracting it. */
750 utc_offset = icaltimezone_get_utc_offset (from_zone, tt, NULL);
751 icaltime_adjust (tt, 0, 0, 0, -utc_offset);
752
753 /* Now we convert the time to the new timezone by getting the UTC offset
754 of our UTC time and adding it. */
755 utc_offset = icaltimezone_get_utc_offset_of_utc_time (to_zone, tt,
756 &is_daylight);
757 tt->is_daylight = is_daylight;
758 icaltime_adjust (tt, 0, 0, 0, utc_offset);
759}
760
761
762
763
764/** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
765 timezone. Also, this takes a pointer instead of the struct. */
766/* Calculates the UTC offset of a given local time in the given
767 timezone. It is the number of seconds to add to UTC to get local
768 time. The is_daylight flag is set to 1 if the time is in
769 daylight-savings time. */
770int
771 icaltimezone_get_utc_offset (icaltimezone*zone,
772 struct icaltimetype*tt,
773 int *is_daylight)
774{
775 icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
776 int change_num, step, utc_offset_change, cmp;
777 int change_num_to_use;
778 int want_daylight;
779
780 if (tt == NULL)
781 return 0;
782
783 if (is_daylight)
784 *is_daylight = 0;
785
786 /* For local times and UTC return 0. */
787 if (zone == NULL || zone == &utc_timezone)
788 return 0;
789
790 /* Use the builtin icaltimezone if possible. */
791 if (zone->builtin_timezone)
792 zone = zone->builtin_timezone;
793
794 /* Make sure the changes array is expanded up to the given time. */
795 icaltimezone_ensure_coverage (zone, tt->year);
796
797 if (!zone->changes || zone->changes->num_elements == 0)
798 return 0;
799
800 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
801 can use our comparison function on it. */
802 tt_change.year = tt->year;
803 tt_change.month = tt->month;
804 tt_change.day = tt->day;
805 tt_change.hour = tt->hour;
806 tt_change.minute = tt->minute;
807 tt_change.second = tt->second;
808
809 /* This should find a change close to the time, either the change before
810 it or the change after it. */
811 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
812
813 /* Sanity check. */
814 icalerror_assert (change_num >= 0,
815 "Negative timezone change index");
816 icalerror_assert (change_num < zone->changes->num_elements,
817 "Timezone change index out of bounds");
818
819 /* Now move backwards or forwards to find the timezone change that applies
820 to tt. It should only have to do 1 or 2 steps. */
821 zone_change = icalarray_element_at (zone->changes, change_num);
822 step = 1;
823 change_num_to_use = -1;
824 for (;;) {
825 /* Copy the change, so we can adjust it. */
826 tmp_change = *zone_change;
827
828 /* If the clock is going backward, check if it is in the region of time
829 that is used twice. If it is, use the change with the daylight
830 setting which matches tt, or use standard if we don't know. */
831 if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
832 /* If the time change is at 2:00AM local time and the clock is
833 going back to 1:00AM we adjust the change to 1:00AM. We may
834 have the wrong change but we'll figure that out later. */
835 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
836 tmp_change.utc_offset);
837 } else {
838 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
839 tmp_change.prev_utc_offset);
840 }
841
842 cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
843
844 /* If the given time is on or after this change, then this change may
845 apply, but we continue as a later change may be the right one.
846 If the given time is before this change, then if we have already
847 found a change which applies we can use that, else we need to step
848 backwards. */
849 if (cmp >= 0)
850 change_num_to_use = change_num;
851 else
852 step = -1;
853
854 /* If we are stepping backwards through the changes and we have found
855 a change that applies, then we know this is the change to use so
856 we exit the loop. */
857 if (step == -1 && change_num_to_use != -1)
858 break;
859
860 change_num += step;
861
862 /* If we go past the start of the changes array, then we have no data
863 for this time so we return a UTC offset of 0. */
864 if (change_num < 0)
865 return 0;
866
867 if (change_num >= zone->changes->num_elements)
868 break;
869
870 zone_change = icalarray_element_at (zone->changes, change_num);
871 }
872
873 /* If we didn't find a change to use, then we have a bug! */
874 icalerror_assert (change_num_to_use != -1,
875 "No applicable timezone change found");
876
877 /* Now we just need to check if the time is in the overlapped region of
878 time when clocks go back. */
879 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
880
881 utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
882 if (utc_offset_change < 0 && change_num_to_use > 0) {
883 tmp_change = *zone_change;
884 icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
885 tmp_change.prev_utc_offset);
886
887 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
888 /* The time is in the overlapped region, so we may need to use
889 either the current zone_change or the previous one. If the
890 time has the is_daylight field set we use the matching change,
891 else we use the change with standard time. */
892 prev_zone_change = icalarray_element_at (zone->changes,
893 change_num_to_use - 1);
894
895 /* I was going to add an is_daylight flag to struct icaltimetype,
896 but iCalendar doesn't let us distinguish between standard and
897 daylight time anyway, so there's no point. So we just use the
898 standard time instead. */
899 want_daylight = (tt->is_daylight == 1) ? 1 : 0;
900
901#if 0
902 if (zone_change->is_daylight == prev_zone_change->is_daylight)
903 printf (" **** Same is_daylight setting\n");
904#endif
905
906 if (zone_change->is_daylight != want_daylight
907 && prev_zone_change->is_daylight == want_daylight)
908 zone_change = prev_zone_change;
909 }
910 }
911
912 /* Now we know exactly which timezone change applies to the time, so
913 we can return the UTC offset and whether it is a daylight time. */
914 if (is_daylight)
915 *is_daylight = zone_change->is_daylight;
916 return zone_change->utc_offset;
917}
918
919
920/** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
921 timezone. Also, this takes a pointer instead of the struct. */
922/** Calculates the UTC offset of a given UTC time in the given
923 timezone. It is the number of seconds to add to UTC to get local
924 time. The is_daylight flag is set to 1 if the time is in
925 daylight-savings time. */
926int
927 icaltimezone_get_utc_offset_of_utc_time (icaltimezone*zone,
928 struct icaltimetype*tt,
929 int *is_daylight)
930{
931 icaltimezonechange *zone_change, tt_change, tmp_change;
932 int change_num, step, change_num_to_use;
933
934 if (is_daylight)
935 *is_daylight = 0;
936
937 /* For local times and UTC return 0. */
938 if (zone == NULL || zone == &utc_timezone)
939 return 0;
940
941 /* Use the builtin icaltimezone if possible. */
942 if (zone->builtin_timezone)
943 zone = zone->builtin_timezone;
944
945 /* Make sure the changes array is expanded up to the given time. */
946 icaltimezone_ensure_coverage (zone, tt->year);
947
948 if (!zone->changes || zone->changes->num_elements == 0)
949 return 0;
950
951 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
952 can use our comparison function on it. */
953 tt_change.year = tt->year;
954 tt_change.month = tt->month;
955 tt_change.day = tt->day;
956 tt_change.hour = tt->hour;
957 tt_change.minute = tt->minute;
958 tt_change.second = tt->second;
959
960 /* This should find a change close to the time, either the change before
961 it or the change after it. */
962 change_num = icaltimezone_find_nearby_change (zone, &tt_change);
963
964 /* Sanity check. */
965 icalerror_assert (change_num >= 0,
966 "Negative timezone change index");
967 icalerror_assert (change_num < zone->changes->num_elements,
968 "Timezone change index out of bounds");
969
970 /* Now move backwards or forwards to find the timezone change that applies
971 to tt. It should only have to do 1 or 2 steps. */
972 zone_change = icalarray_element_at (zone->changes, change_num);
973 step = 1;
974 change_num_to_use = -1;
975 for (;;) {
976 /* Copy the change and adjust it to UTC. */
977 tmp_change = *zone_change;
978
979 /* If the given time is on or after this change, then this change may
980 apply, but we continue as a later change may be the right one.
981 If the given time is before this change, then if we have already
982 found a change which applies we can use that, else we need to step
983 backwards. */
984 if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
985 change_num_to_use = change_num;
986 else
987 step = -1;
988
989 /* If we are stepping backwards through the changes and we have found
990 a change that applies, then we know this is the change to use so
991 we exit the loop. */
992 if (step == -1 && change_num_to_use != -1)
993 break;
994
995 change_num += step;
996
997 /* If we go past the start of the changes array, then we have no data
998 for this time so we return a UTC offset of 0. */
999 if (change_num < 0)
1000 return 0;
1001
1002 if (change_num >= zone->changes->num_elements)
1003 break;
1004
1005 zone_change = icalarray_element_at (zone->changes, change_num);
1006 }
1007
1008 /* If we didn't find a change to use, then we have a bug! */
1009 icalerror_assert (change_num_to_use != -1,
1010 "No applicable timezone change found");
1011
1012 /* Now we know exactly which timezone change applies to the time, so
1013 we can return the UTC offset and whether it is a daylight time. */
1014 zone_change = icalarray_element_at (zone->changes, change_num_to_use);
1015 if (is_daylight)
1016 *is_daylight = zone_change->is_daylight;
1017
1018 return zone_change->utc_offset;
1019}
1020
1021
1022/** Returns the index of a timezone change which is close to the time
1023 given in change. */
1024static int
1025 icaltimezone_find_nearby_change (icaltimezone *zone,
1026 icaltimezonechange*change)
1027{
1028 icaltimezonechange *zone_change;
1029 int lower, upper, middle, cmp;
1030
1031 /* Do a simple binary search. */
1032 lower = middle = 0;
1033 upper = zone->changes->num_elements;
1034
1035 while (lower < upper) {
1036 middle = (lower + upper) / 2;
1037 zone_change = icalarray_element_at (zone->changes, middle);
1038 cmp = icaltimezone_compare_change_fn (change, zone_change);
1039 if (cmp == 0)
1040 break;
1041 else if (cmp < 0)
1042 upper = middle;
1043 else
1044 lower = middle + 1;
1045 }
1046
1047 return middle;
1048}
1049
1050
1051
1052
1053/** Adds (or subtracts) a time from a icaltimezonechange. NOTE: This
1054 function is exactly the same as icaltime_adjust() except for the
1055 type of the first parameter. */
1056static void
1057 icaltimezone_adjust_change (icaltimezonechange *tt,
1058 int days,
1059 int hours,
1060 int minutes,
1061 int seconds)
1062{
1063 int second, minute, hour, day;
1064 int minutes_overflow, hours_overflow, days_overflow;
1065 int days_in_month;
1066
1067 /* Add on the seconds. */
1068 second = tt->second + seconds;
1069 tt->second = second % 60;
1070 minutes_overflow = second / 60;
1071 if (tt->second < 0) {
1072 tt->second += 60;
1073 minutes_overflow--;
1074 }
1075
1076 /* Add on the minutes. */
1077 minute = tt->minute + minutes + minutes_overflow;
1078 tt->minute = minute % 60;
1079 hours_overflow = minute / 60;
1080 if (tt->minute < 0) {
1081 tt->minute += 60;
1082 hours_overflow--;
1083 }
1084
1085 /* Add on the hours. */
1086 hour = tt->hour + hours + hours_overflow;
1087 tt->hour = hour % 24;
1088 days_overflow = hour / 24;
1089 if (tt->hour < 0) {
1090 tt->hour += 24;
1091 days_overflow--;
1092 }
1093
1094 /* Add on the days. */
1095 day = tt->day + days + days_overflow;
1096 if (day > 0) {
1097 for (;;) {
1098 days_in_month = icaltime_days_in_month (tt->month, tt->year);
1099 if (day <= days_in_month)
1100 break;
1101
1102 tt->month++;
1103 if (tt->month >= 13) {
1104 tt->year++;
1105 tt->month = 1;
1106 }
1107
1108 day -= days_in_month;
1109 }
1110 } else {
1111 while (day <= 0) {
1112 if (tt->month == 1) {
1113 tt->year--;
1114 tt->month = 12;
1115 } else {
1116 tt->month--;
1117 }
1118
1119 day += icaltime_days_in_month (tt->month, tt->year);
1120 }
1121 }
1122 tt->day = day;
1123}
1124
1125
1126char*
1127 icaltimezone_get_tzid (icaltimezone*zone)
1128{
1129 /* If this is a floating time, without a timezone, return NULL. */
1130 if (!zone)
1131 return NULL;
1132
1133 if (!zone->tzid)
1134 icaltimezone_load_builtin_timezone (zone);
1135
1136 return zone->tzid;
1137}
1138
1139
1140char*
1141 icaltimezone_get_location (icaltimezone*zone)
1142{
1143 /* If this is a floating time, without a timezone, return NULL. */
1144 if (!zone)
1145 return NULL;
1146
1147 /* Note that for builtin timezones this comes from zones.tab so we don't
1148 need to check the timezone is loaded here. */
1149 return zone->location;
1150}
1151
1152
1153char*
1154 icaltimezone_get_tznames (icaltimezone*zone)
1155{
1156 /* If this is a floating time, without a timezone, return NULL. */
1157 if (!zone)
1158 return NULL;
1159
1160 if (!zone->component)
1161 icaltimezone_load_builtin_timezone (zone);
1162
1163 return zone->tznames;
1164}
1165
1166
1167/** Returns the latitude of a builtin timezone. */
1168double
1169 icaltimezone_get_latitude (icaltimezone*zone)
1170{
1171 /* If this is a floating time, without a timezone, return 0. */
1172 if (!zone)
1173 return 0.0;
1174
1175 /* Note that for builtin timezones this comes from zones.tab so we don't
1176 need to check the timezone is loaded here. */
1177 return zone->latitude;
1178}
1179
1180
1181/** Returns the longitude of a builtin timezone. */
1182double
1183 icaltimezone_get_longitude (icaltimezone*zone)
1184{
1185 /* If this is a floating time, without a timezone, return 0. */
1186 if (!zone)
1187 return 0.0;
1188
1189 /* Note that for builtin timezones this comes from zones.tab so we don't
1190 need to check the timezone is loaded here. */
1191 return zone->longitude;
1192}
1193
1194
1195/** Returns the VTIMEZONE component of a timezone. */
1196icalcomponent*
1197 icaltimezone_get_component (icaltimezone*zone)
1198{
1199 /* If this is a floating time, without a timezone, return NULL. */
1200 if (!zone)
1201 return NULL;
1202
1203 if (!zone->component)
1204 icaltimezone_load_builtin_timezone (zone);
1205
1206 return zone->component;
1207}
1208
1209
1210/** Sets the VTIMEZONE component of an icaltimezone, initializing the
1211 tzid, location & tzname fields. It returns 1 on success or 0 on
1212 failure, i.e. no TZID was found. */
1213int
1214 icaltimezone_set_component (icaltimezone*zone,
1215 icalcomponent*comp)
1216{
1217 icaltimezone_reset (zone);
1218 return icaltimezone_get_vtimezone_properties (zone, comp);
1219}
1220
1221
1222icalarray*
1223 icaltimezone_array_new (void)
1224{
1225 return icalarray_new (sizeof (icaltimezone), 16);
1226}
1227
1228
1229void
1230 icaltimezone_array_append_from_vtimezone (icalarray*timezones,
1231 icalcomponent*child)
1232{
1233 icaltimezone zone;
1234
1235 icaltimezone_init (&zone);
1236 if (icaltimezone_get_vtimezone_properties (&zone, child))
1237 icalarray_append (timezones, &zone);
1238}
1239
1240
1241void
1242 icaltimezone_array_free (icalarray*timezones)
1243{
1244 icaltimezone *zone;
1245 int i;
1246
1247 if ( timezones )
1248 {
1249 for (i = 0; i < timezones->num_elements; i++) {
1250 zone = icalarray_element_at (timezones, i);
1251 icaltimezone_free (zone, 0);
1252 }
1253
1254 icalarray_free (timezones);
1255 }
1256}
1257
1258
1259/*
1260 * BUILTIN TIMEZONE HANDLING
1261 */
1262
1263
1264/** Returns an icalarray of icaltimezone structs, one for each builtin
1265 timezone. This will load and parse the zones.tab file to get the
1266 timezone names and their coordinates. It will not load the
1267 VTIMEZONE data for any timezones. */
1268icalarray*
1269 icaltimezone_get_builtin_timezones(void)
1270{
1271 if (!builtin_timezones)
1272 icaltimezone_init_builtin_timezones ();
1273
1274 return builtin_timezones;
1275}
1276
1277/** Release builtin timezone memory */
1278void
1279icaltimezone_free_builtin_timezones(void)
1280{
1281 icaltimezone_array_free(builtin_timezones);
1282}
1283
1284
1285/** Returns a single builtin timezone, given its Olson city name. */
1286icaltimezone*
1287 icaltimezone_get_builtin_timezone(const char *location)
1288{
1289 icaltimezone *zone;
1290 int lower, upper, middle, cmp;
1291 char *zone_location;
1292
1293 if (!location || !location[0])
1294 return NULL;
1295
1296 if (!strcmp (location, "UTC"))
1297 return &utc_timezone;
1298
1299 if (!builtin_timezones)
1300 icaltimezone_init_builtin_timezones ();
1301
1302 /* Do a simple binary search. */
1303 lower = middle = 0;
1304 upper = builtin_timezones->num_elements;
1305
1306 while (lower < upper) {
1307 middle = (lower + upper) / 2;
1308 zone = icalarray_element_at (builtin_timezones, middle);
1309 zone_location = icaltimezone_get_location (zone);
1310 cmp = strcmp (location, zone_location);
1311 if (cmp == 0)
1312 return zone;
1313 else if (cmp < 0)
1314 upper = middle;
1315 else
1316 lower = middle + 1;
1317 }
1318
1319 return NULL;
1320}
1321
1322
1323/** Returns a single builtin timezone, given its TZID. */
1324icaltimezone*
1325icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1326{
1327 int num_slashes = 0;
1328 const char *p, *zone_tzid;
1329 icaltimezone *zone;
1330
1331 if (!tzid || !tzid[0])
1332 return NULL;
1333
1334 /* Check that the TZID starts with our unique prefix. */
1335 if (strncmp (tzid, TZID_PREFIX, TZID_PREFIX_LEN))
1336 return NULL;
1337
1338 /* Get the location, which is after the 3rd '/' character. */
1339 p = tzid;
1340 for (p = tzid; *p; p++) {
1341 if (*p == '/') {
1342 num_slashes++;
1343 if (num_slashes == 3)
1344 break;
1345 }
1346 }
1347
1348 if (num_slashes != 3)
1349 return NULL;
1350
1351 p++;
1352
1353 /* Now we can use the function to get the builtin timezone from the
1354 location string. */
1355 zone = icaltimezone_get_builtin_timezone (p);
1356 if (!zone)
1357 return NULL;
1358
1359 /* Check that the builtin TZID matches exactly. We don't want to return
1360 a different version of the VTIMEZONE. */
1361 zone_tzid = icaltimezone_get_tzid (zone);
1362 if (!strcmp (zone_tzid, tzid))
1363 return zone;
1364 else
1365 return NULL;
1366}
1367
1368
1369/** Returns the special UTC timezone. */
1370icaltimezone*
1371 icaltimezone_get_utc_timezone (void)
1372{
1373 if (!builtin_timezones)
1374 icaltimezone_init_builtin_timezones ();
1375
1376 return &utc_timezone;
1377}
1378
1379
1380
1381/** This initializes the builtin timezone data, i.e. the
1382 builtin_timezones array and the special UTC timezone. It should be
1383 called before any code that uses the timezone functions. */
1384static void
1385 icaltimezone_init_builtin_timezones(void)
1386{
1387 /* Initialize the special UTC timezone. */
1388 utc_timezone.tzid = "UTC";
1389
1390 icaltimezone_parse_zone_tab ();
1391}
1392
1393
1394/** This parses the zones.tab file containing the names and locations
1395 of the builtin timezones. It creates the builtin_timezones array
1396 which is an icalarray of icaltimezone structs. It only fills in the
1397 location, latitude and longtude fields; the rest are left
1398 blank. The VTIMEZONE component is loaded later if it is needed. The
1399 timezones in the zones.tab file are sorted by their name, which is
1400 useful for binary searches. */
1401static void
1402 icaltimezone_parse_zone_tab (void)
1403{
1404 char *filename;
1405 FILE *fp;
1406 char buf[1024]; /* Used to store each line of zones.tab as it is read. */
1407 char location[1024]; /* Stores the city name when parsing buf. */
1408 unsigned int filename_len;
1409 int latitude_degrees, latitude_minutes, latitude_seconds;
1410 int longitude_degrees, longitude_minutes, longitude_seconds;
1411 icaltimezone zone;
1412
1413 icalerror_assert (builtin_timezones == NULL,
1414 "Parsing zones.tab file multiple times");
1415
1416 builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1417
1418 filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1419 + 2;
1420
1421 filename = (char*) malloc (filename_len);
1422 if (!filename) {
1423 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1424 return;
1425 }
1426
1427 snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1428 ZONES_TAB_FILENAME);
1429
1430 fp = fopen (filename, "r");
1431 free (filename);
1432 if (!fp) {
1433 icalerror_set_errno(ICAL_FILE_ERROR);
1434 return;
1435 }
1436
1437 while (fgets (buf, sizeof(buf), fp)) {
1438 if (*buf == '#') continue;
1439
1440 /* The format of each line is: "latitude longitude location". */
1441 if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
1442 &latitude_degrees, &latitude_minutes,
1443 &latitude_seconds,
1444 &longitude_degrees, &longitude_minutes,
1445 &longitude_seconds,
1446 location) != 7) {
1447 fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1448 continue;
1449 }
1450
1451 icaltimezone_init (&zone);
1452 zone.location = strdup (location);
1453
1454 if (latitude_degrees >= 0)
1455 zone.latitude = (double) latitude_degrees
1456 + (double) latitude_minutes / 60
1457 + (double) latitude_seconds / 3600;
1458 else
1459 zone.latitude = (double) latitude_degrees
1460 - (double) latitude_minutes / 60
1461 - (double) latitude_seconds / 3600;
1462
1463 if (longitude_degrees >= 0)
1464 zone.longitude = (double) longitude_degrees
1465 + (double) longitude_minutes / 60
1466 + (double) longitude_seconds / 3600;
1467 else
1468 zone.longitude = (double) longitude_degrees
1469 - (double) longitude_minutes / 60
1470 - (double) longitude_seconds / 3600;
1471
1472 icalarray_append (builtin_timezones, &zone);
1473
1474#if 0
1475 printf ("Found zone: %s %f %f\n",
1476 location, zone.latitude, zone.longitude);
1477#endif
1478 }
1479
1480 fclose (fp);
1481}
1482
1483
1484/** Loads the builtin VTIMEZONE data for the given timezone. */
1485static void
1486 icaltimezone_load_builtin_timezone (icaltimezone*zone)
1487{
1488 char *filename;
1489 unsigned int filename_len;
1490 FILE *fp;
1491 icalparser *parser;
1492 icalcomponent *comp, *subcomp;
1493
1494 /* If the location isn't set, it isn't a builtin timezone. */
1495 if (!zone->location || !zone->location[0])
1496 return;
1497
1498 filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1499
1500 filename = (char*) malloc (filename_len);
1501 if (!filename) {
1502 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1503 return;
1504 }
1505
1506 snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1507 zone->location);
1508
1509 fp = fopen (filename, "r");
1510 free (filename);
1511 if (!fp) {
1512 icalerror_set_errno(ICAL_FILE_ERROR);
1513 return;
1514 }
1515
1516
1517 /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1518 this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1519 fprintf(stderr, "** WARNING ** %s: %d %s\n", __FILE__, __LINE__, icalerror_strerror(icalerrno));
1520 */
1521
1522 parser = icalparser_new ();
1523 icalparser_set_gen_data (parser, fp);
1524 comp = icalparser_parse (parser, icaltimezone_load_get_line_fn);
1525 icalparser_free (parser);
1526 fclose (fp);
1527
1528
1529
1530 /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1531 subcomp = icalcomponent_get_first_component (comp,
1532 ICAL_VTIMEZONE_COMPONENT);
1533 if (!subcomp) {
1534 icalerror_set_errno(ICAL_PARSE_ERROR);
1535 return;
1536 }
1537
1538 icaltimezone_get_vtimezone_properties (zone, subcomp);
1539
1540 icalcomponent_remove_component(comp,subcomp);
1541
1542 icalcomponent_free(comp);
1543
1544}
1545
1546
1547/** Callback used from icalparser_parse() */
1548static char *
1549 icaltimezone_load_get_line_fn (char *s,
1550 size_t size,
1551 void *data)
1552{
1553 return fgets (s, size, (FILE*) data);
1554}
1555
1556
1557
1558
1559/*
1560 * DEBUGGING
1561 */
1562
1563/**
1564 * This outputs a list of timezone changes for the given timezone to the
1565 * given file, up to the maximum year given. We compare this output with the
1566 * output from 'vzic --dump-changes' to make sure that we are consistent.
1567 * (vzic is the Olson timezone database to VTIMEZONE converter.)
1568 *
1569 * The output format is:
1570 *
1571 *Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1572 *
1573 * The Date and Time fields specify the time change in UTC.
1574 *
1575 * The UTC Offset is for local (wall-clock) time. It is the amount of time
1576 * to add to UTC to get local time.
1577 */
1578int
1579 icaltimezone_dump_changes (icaltimezone*zone,
1580 int max_year,
1581 FILE *fp)
1582{
1583 static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1584 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1585 icaltimezonechange *zone_change;
1586 int change_num;
1587 char buffer[8];
1588
1589 /* Make sure the changes array is expanded up to the given time. */
1590 icaltimezone_ensure_coverage (zone, max_year);
1591
1592#if 0
1593 printf ("Num changes: %i\n", zone->changes->num_elements);
1594#endif
1595
1596 change_num = 0;
1597 for (change_num = 0; change_num < zone->changes->num_elements; change_num++) {
1598 zone_change = icalarray_element_at (zone->changes, change_num);
1599
1600 if (zone_change->year > max_year)
1601 break;
1602
1603 fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1604 zone->location,
1605 zone_change->day, months[zone_change->month - 1],
1606 zone_change->year,
1607 zone_change->hour, zone_change->minute, zone_change->second);
1608
1609 /* Wall Clock Time offset from UTC. */
1610 format_utc_offset (zone_change->utc_offset, buffer);
1611 fprintf (fp, "\t%s", buffer);
1612
1613 fprintf (fp, "\n");
1614 }
1615 return 1;
1616}
1617
1618
1619/** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1620 buffer should have space for 8 characters. */
1621static void
1622 format_utc_offset (int utc_offset,
1623 char *buffer)
1624{
1625 char *sign = "+";
1626 int hours, minutes, seconds;
1627
1628 if (utc_offset < 0) {
1629 utc_offset = -utc_offset;
1630 sign = "-";
1631 }
1632
1633 hours = utc_offset / 3600;
1634 minutes = (utc_offset % 3600) / 60;
1635 seconds = utc_offset % 60;
1636
1637 /* Sanity check. Standard timezone offsets shouldn't be much more than 12
1638 hours, and daylight saving shouldn't change it by more than a few hours.
1639 (The maximum offset is 15 hours 56 minutes at present.) */
1640 if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
1641 || seconds < 0 || seconds >= 60) {
1642 fprintf (stderr, "Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1643 hours, minutes, seconds);
1644 }
1645
1646 if (seconds == 0)
1647 sprintf (buffer, "%s%02i%02i", sign, hours, minutes);
1648 else
1649 sprintf (buffer, "%s%02i%02i%02i", sign, hours, minutes, seconds);
1650}
1651
1652static char* get_zone_directory(void)
1653{
1654 return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1655}
1656
1657void set_zone_directory(char *path)
1658{
1659 zone_files_directory = malloc(strlen(path)+1);
1660 if ( zone_files_directory != NULL )
1661 {
1662 strcpy(zone_files_directory,path);
1663 }
1664}
1665
1666void free_zone_directory(void)
1667{
1668 if ( zone_files_directory != NULL )
1669 {
1670 free(zone_files_directory);
1671 }
1672}