author | zautrix <zautrix> | 2004-06-29 11:59:46 (UTC) |
---|---|---|
committer | zautrix <zautrix> | 2004-06-29 11:59:46 (UTC) |
commit | da43dbdc6c82453228f34766fc74585615cba938 (patch) (unidiff) | |
tree | 16576932cea08bf117b2d0320b0d5f66ee8ad093 /libical/src/libical/icaltimezone.c | |
parent | 627489ea2669d3997676bc3cee0f5d0d0c16c4d4 (diff) | |
download | kdepimpi-da43dbdc6c82453228f34766fc74585615cba938.zip kdepimpi-da43dbdc6c82453228f34766fc74585615cba938.tar.gz kdepimpi-da43dbdc6c82453228f34766fc74585615cba938.tar.bz2 |
New lib ical.Some minor changes as well.
Diffstat (limited to 'libical/src/libical/icaltimezone.c') (more/less context) (ignore whitespace changes)
-rw-r--r-- | libical/src/libical/icaltimezone.c | 1672 |
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 | |||
66 | struct _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 | |||
116 | struct _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. */ | ||
139 | static icalarray *builtin_timezones = NULL; | ||
140 | |||
141 | /** This is the special UTC timezone, which isn't in builtin_timezones. */ | ||
142 | static icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | ||
143 | |||
144 | static char* zone_files_directory = NULL; | ||
145 | |||
146 | static void icaltimezone_reset (icaltimezone *zone); | ||
147 | static char* icaltimezone_get_location_from_vtimezone (icalcomponent *component); | ||
148 | static 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. */ | ||
171 | static 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 | |||
181 | static 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 | |||
192 | static char* get_zone_directory(void); | ||
193 | |||
194 | |||
195 | /** Creates a new icaltimezone. */ | ||
196 | icaltimezone* | ||
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. */ | ||
214 | void | ||
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. */ | ||
225 | static 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. */ | ||
244 | static 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. */ | ||
264 | static 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. */ | ||
290 | static char* | ||
291 | icaltimezone_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. */ | ||
326 | static char* | ||
327 | icaltimezone_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 | |||
436 | static 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 | |||
468 | static 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 | |||
504 | static 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(). */ | ||
689 | static 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 | |||
737 | void | ||
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. */ | ||
770 | int | ||
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. */ | ||
926 | int | ||
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. */ | ||
1024 | static 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. */ | ||
1056 | static 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 | |||
1126 | char* | ||
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 | |||
1140 | char* | ||
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 | |||
1153 | char* | ||
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. */ | ||
1168 | double | ||
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. */ | ||
1182 | double | ||
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. */ | ||
1196 | icalcomponent* | ||
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. */ | ||
1213 | int | ||
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 | |||
1222 | icalarray* | ||
1223 | icaltimezone_array_new (void) | ||
1224 | { | ||
1225 | return icalarray_new (sizeof (icaltimezone), 16); | ||
1226 | } | ||
1227 | |||
1228 | |||
1229 | void | ||
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 | |||
1241 | void | ||
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. */ | ||
1268 | icalarray* | ||
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 */ | ||
1278 | void | ||
1279 | icaltimezone_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. */ | ||
1286 | icaltimezone* | ||
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. */ | ||
1324 | icaltimezone* | ||
1325 | icaltimezone_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. */ | ||
1370 | icaltimezone* | ||
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. */ | ||
1384 | static 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. */ | ||
1401 | static 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. */ | ||
1485 | static 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() */ | ||
1548 | static 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 | */ | ||
1578 | int | ||
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. */ | ||
1621 | static 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 | |||
1652 | static char* get_zone_directory(void) | ||
1653 | { | ||
1654 | return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory; | ||
1655 | } | ||
1656 | |||
1657 | void 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 | |||
1666 | void free_zone_directory(void) | ||
1667 | { | ||
1668 | if ( zone_files_directory != NULL ) | ||
1669 | { | ||
1670 | free(zone_files_directory); | ||
1671 | } | ||
1672 | } | ||