/src/libical/src/libical/icaltimezone.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*====================================================================== |
2 | | FILE: icaltimezone.c |
3 | | CREATOR: Damon Chaplin 15 March 2001 |
4 | | |
5 | | SPDX-FileCopyrightText: 2001, Damon Chaplin <damon@ximian.com> |
6 | | |
7 | | SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0 |
8 | | |
9 | | ======================================================================*/ |
10 | | //krazy:excludeall=cpp |
11 | | |
12 | | #ifdef HAVE_CONFIG_H |
13 | | #include <config.h> |
14 | | #endif |
15 | | |
16 | | #include "icaltimezone.h" |
17 | | #include "icaltimezoneimpl.h" |
18 | | #include "icalarray.h" |
19 | | #include "icalerror.h" |
20 | | #include "icalparser.h" |
21 | | #include "icalmemory.h" |
22 | | #include "icaltz-util.h" |
23 | | |
24 | | #include <ctype.h> |
25 | | #include <stddef.h> /* for ptrdiff_t */ |
26 | | #include <stdlib.h> |
27 | | #include <limits.h> |
28 | | |
29 | | #if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD |
30 | | #include <pthread.h> |
31 | | #if defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP) |
32 | | // It seems the same thread can attempt to lock builtin_mutex multiple times |
33 | | // (at least when using builtin tzdata), so make it builtin_mutex recursive: |
34 | | static pthread_mutex_t builtin_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; |
35 | | #else |
36 | | static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER; |
37 | | #endif |
38 | | // To avoid use-after-free in multithreaded applications when accessing icaltimezone::changes |
39 | | static pthread_mutex_t changes_mutex = PTHREAD_MUTEX_INITIALIZER; |
40 | | #endif |
41 | | |
42 | | #if defined(_WIN32) |
43 | | #if !defined(_WIN32_WCE) |
44 | | #include <mbstring.h> |
45 | | #endif |
46 | | #include <windows.h> |
47 | | #endif |
48 | | |
49 | | /** This is the toplevel directory where the timezone data is installed in. */ |
50 | 0 | #define ZONEINFO_DIRECTORY PACKAGE_DATA_DIR "/zoneinfo" |
51 | | |
52 | | /** The prefix we use to uniquely identify TZIDs. |
53 | | It must begin and end with forward slashes. |
54 | | */ |
55 | 0 | #define BUILTIN_TZID_PREFIX_LEN 256 |
56 | 0 | #define BUILTIN_TZID_PREFIX "/freeassociation.sourceforge.net/" |
57 | | |
58 | | /* Known prefixes from the old versions of libical */ |
59 | | static const struct _compat_tzids { |
60 | | const char *tzid; |
61 | | int slashes; |
62 | | } glob_compat_tzids[] = { |
63 | | { "/freeassociation.sourceforge.net/Tzfile/", 3 }, |
64 | | { "/freeassociation.sourceforge.net/", 2 }, |
65 | | { "/citadel.org/", 3 }, /* Full TZID for this can be: "/citadel.org/20190914_1/" */ |
66 | | { NULL, -1 } |
67 | | }; |
68 | | |
69 | | /* The prefix to be used for tzid's generated from system tzdata */ |
70 | | static ICAL_GLOBAL_VAR char s_ical_tzid_prefix[BUILTIN_TZID_PREFIX_LEN] = {0}; |
71 | | |
72 | | /** This is the filename of the file containing the city names and |
73 | | coordinates of all the builtin timezones. */ |
74 | 0 | #define ZONES_TAB_FILENAME "zones.tab" |
75 | | |
76 | | /** This is the number of years of extra coverage we do when expanding |
77 | | the timezone changes. */ |
78 | 0 | #define ICALTIMEZONE_EXTRA_COVERAGE 5 |
79 | | |
80 | | #if (SIZEOF_ICALTIME_T > 4) |
81 | | /** Arbitrarily go up to 1000th anniversary of Gregorian calendar, since |
82 | | 64-bit icaltime_t values get us up to the tm_year limit of 2+ billion years. */ |
83 | 0 | #define ICALTIMEZONE_MAX_YEAR 2582 |
84 | | #else |
85 | | /** This is the maximum year we will expand to, since 32-bit icaltime_t values |
86 | | only go up to the start of 2038. */ |
87 | | #define ICALTIMEZONE_MAX_YEAR 2037 |
88 | | #endif |
89 | | |
90 | | typedef struct _icaltimezonechange icaltimezonechange; |
91 | | |
92 | | struct _icaltimezonechange |
93 | | { |
94 | | int utc_offset; |
95 | | /**< The offset to add to UTC to get local time, in seconds. */ |
96 | | |
97 | | int prev_utc_offset; |
98 | | /**< The offset to add to UTC, before this change, in seconds. */ |
99 | | |
100 | | int year; /**< Actual year, e.g. 2001. */ |
101 | | int month; /**< 1 (Jan) to 12 (Dec). */ |
102 | | int day; |
103 | | int hour; |
104 | | int minute; |
105 | | int second; |
106 | | /**< The time that the change came into effect, in UTC. |
107 | | Note that the prev_utc_offset applies to this local time, |
108 | | since we haven't changed to the new offset yet. */ |
109 | | |
110 | | int is_daylight; |
111 | | /**< Whether this is STANDARD or DAYLIGHT time. */ |
112 | | }; |
113 | | |
114 | | /** An array of icaltimezones for the builtin timezones. */ |
115 | | static ICAL_GLOBAL_VAR icalarray *builtin_timezones = NULL; |
116 | | |
117 | | /** This is the special UTC timezone, which isn't in builtin_timezones. */ |
118 | | static ICAL_GLOBAL_VAR icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
119 | | |
120 | | static ICAL_GLOBAL_VAR char *zone_files_directory = NULL; |
121 | | |
122 | | #if defined(USE_BUILTIN_TZDATA) |
123 | | static ICAL_GLOBAL_VAR int use_builtin_tzdata = 1; |
124 | | #else |
125 | | static ICAL_GLOBAL_VAR int use_builtin_tzdata = 0; |
126 | | #endif |
127 | | |
128 | | static void icaltimezone_reset(icaltimezone *zone); |
129 | | static void icaltimezone_expand_changes(icaltimezone *zone, int end_year); |
130 | | static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2); |
131 | | |
132 | | static size_t icaltimezone_find_nearby_change(icaltimezone *zone, icaltimezonechange *change); |
133 | | |
134 | | static void icaltimezone_adjust_change(icaltimezonechange *tt, |
135 | | int days, int hours, int minutes, int seconds); |
136 | | |
137 | | static void icaltimezone_init(icaltimezone *zone); |
138 | | |
139 | | /** @brief Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from |
140 | | * the VTIMEZONE component and places them in the icaltimezone. |
141 | | * |
142 | | * @returns 1 on success, or 0 if the TZID can't be found. |
143 | | */ |
144 | | static int icaltimezone_get_vtimezone_properties(icaltimezone *zone, icalcomponent *component) |
145 | | #if defined(THREAD_SANITIZER) |
146 | | __attribute__((no_sanitize("thread"))) |
147 | | #endif |
148 | | ; |
149 | | |
150 | | static void icaltimezone_load_builtin_timezone(icaltimezone *zone) |
151 | | #if defined(THREAD_SANITIZER) |
152 | | __attribute__((no_sanitize("thread"))) |
153 | | #endif |
154 | | ; |
155 | | |
156 | | static void icaltimezone_ensure_coverage(icaltimezone *zone, int end_year); |
157 | | |
158 | | static void icaltimezone_init_builtin_timezones(void); |
159 | | |
160 | | static void icaltimezone_parse_zone_tab(void); |
161 | | |
162 | | static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data); |
163 | | |
164 | | static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size); |
165 | | static const char *get_zone_directory_builtin(void); |
166 | | |
167 | | static void icaltimezone_builtin_lock(void) |
168 | 1 | { |
169 | 1 | #if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD |
170 | 1 | pthread_mutex_lock(&builtin_mutex); |
171 | 1 | #endif |
172 | 1 | } |
173 | | |
174 | | static void icaltimezone_builtin_unlock(void) |
175 | 1 | { |
176 | 1 | #if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD |
177 | 1 | pthread_mutex_unlock(&builtin_mutex); |
178 | 1 | #endif |
179 | 1 | } |
180 | | |
181 | | static void icaltimezone_changes_lock(void) |
182 | 0 | { |
183 | 0 | #if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD |
184 | 0 | pthread_mutex_lock(&changes_mutex); |
185 | 0 | #endif |
186 | 0 | } |
187 | | |
188 | | static void icaltimezone_changes_unlock(void) |
189 | 0 | { |
190 | 0 | #if ICAL_SYNC_MODE == ICAL_SYNC_MODE_PTHREAD |
191 | 0 | pthread_mutex_unlock(&changes_mutex); |
192 | 0 | #endif |
193 | 0 | } |
194 | | |
195 | | const char *icaltimezone_tzid_prefix(void) |
196 | 0 | { |
197 | 0 | if (s_ical_tzid_prefix[0] == '\0') { |
198 | 0 | strncpy(s_ical_tzid_prefix, BUILTIN_TZID_PREFIX, BUILTIN_TZID_PREFIX_LEN-1); |
199 | 0 | } |
200 | 0 | return s_ical_tzid_prefix; |
201 | 0 | } |
202 | | |
203 | | icaltimezone *icaltimezone_new(void) |
204 | 0 | { |
205 | 0 | icaltimezone *zone; |
206 | |
|
207 | 0 | zone = (icaltimezone *) icalmemory_new_buffer(sizeof(icaltimezone)); |
208 | 0 | if (!zone) { |
209 | 0 | icalerror_set_errno(ICAL_NEWFAILED_ERROR); |
210 | 0 | return NULL; |
211 | 0 | } |
212 | | |
213 | 0 | icaltimezone_init(zone); |
214 | |
|
215 | 0 | return zone; |
216 | 0 | } |
217 | | |
218 | | icaltimezone *icaltimezone_copy(icaltimezone *originalzone) |
219 | 0 | { |
220 | 0 | icaltimezone *zone; |
221 | |
|
222 | 0 | zone = (icaltimezone *) icalmemory_new_buffer(sizeof(icaltimezone)); |
223 | 0 | if (!zone) { |
224 | 0 | icalerror_set_errno(ICAL_NEWFAILED_ERROR); |
225 | 0 | return NULL; |
226 | 0 | } |
227 | | |
228 | 0 | memcpy(zone, originalzone, sizeof(icaltimezone)); |
229 | 0 | if (zone->tzid != NULL) { |
230 | 0 | zone->tzid = icalmemory_strdup(zone->tzid); |
231 | 0 | } |
232 | 0 | if (zone->location != NULL) { |
233 | 0 | zone->location = icalmemory_strdup(zone->location); |
234 | 0 | } |
235 | 0 | if (zone->tznames != NULL) { |
236 | 0 | zone->tznames = icalmemory_strdup(zone->tznames); |
237 | 0 | } |
238 | |
|
239 | 0 | icaltimezone_changes_lock(); |
240 | 0 | if (zone->changes != NULL) { |
241 | 0 | zone->changes = icalarray_copy(zone->changes); |
242 | 0 | } |
243 | 0 | icaltimezone_changes_unlock(); |
244 | | |
245 | | /* Let the caller set the component because then they will |
246 | | know to be careful not to free this reference twice. */ |
247 | 0 | zone->component = NULL; |
248 | |
|
249 | 0 | return zone; |
250 | 0 | } |
251 | | |
252 | | void icaltimezone_free(icaltimezone *zone, int free_struct) |
253 | 113k | { |
254 | 113k | icaltimezone_reset(zone); |
255 | 113k | if (free_struct) |
256 | 0 | icalmemory_free_buffer(zone); |
257 | 113k | } |
258 | | |
259 | | /** @brief Resets the icaltimezone to the initial state, freeing most of the |
260 | | * fields. |
261 | | */ |
262 | | static void icaltimezone_reset(icaltimezone *zone) |
263 | 113k | { |
264 | 113k | if (zone->tzid) |
265 | 113k | icalmemory_free_buffer(zone->tzid); |
266 | | |
267 | 113k | if (zone->location) |
268 | 389 | icalmemory_free_buffer(zone->location); |
269 | | |
270 | 113k | if (zone->tznames) |
271 | 1.16k | icalmemory_free_buffer(zone->tznames); |
272 | | |
273 | 113k | if (zone->component) |
274 | 113k | icalcomponent_free(zone->component); |
275 | | |
276 | | // icaltimezone_changes_lock(); |
277 | 113k | if (zone->changes) { |
278 | 0 | icalarray_free(zone->changes); |
279 | 0 | zone->changes = NULL; |
280 | 0 | } |
281 | | // icaltimezone_changes_unlock(); |
282 | | |
283 | 113k | icaltimezone_init(zone); |
284 | 113k | } |
285 | | |
286 | | /** @brief Initializes an icaltimezone. */ |
287 | | static void icaltimezone_init(icaltimezone *zone) |
288 | 228k | { |
289 | 228k | zone->tzid = NULL; |
290 | 228k | zone->location = NULL; |
291 | 228k | zone->tznames = NULL; |
292 | 228k | zone->latitude = 0.0; |
293 | 228k | zone->longitude = 0.0; |
294 | 228k | zone->component = NULL; |
295 | 228k | zone->builtin_timezone = NULL; |
296 | 228k | zone->end_year = 0; |
297 | 228k | zone->changes = NULL; |
298 | 228k | } |
299 | | |
300 | | /** @brief Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of |
301 | | * the VTIMEZONE component and stores them in the icaltimezone. |
302 | | * |
303 | | * @returns 1 on success, or 0 if the TZID can't be found. |
304 | | * |
305 | | * Note that it expects the zone to be initialized or reset - it doesn't free |
306 | | * any old values. |
307 | | */ |
308 | | static int icaltimezone_get_vtimezone_properties(icaltimezone *zone, icalcomponent *component) |
309 | 114k | { |
310 | 114k | icalproperty *prop; |
311 | 114k | const char *tzid; |
312 | | |
313 | 114k | prop = icalcomponent_get_first_property(component, ICAL_TZID_PROPERTY); |
314 | 114k | if (!prop) |
315 | 1.04k | return 0; |
316 | | |
317 | | /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */ |
318 | 113k | tzid = icalproperty_get_tzid(prop); |
319 | 113k | if (!tzid) { |
320 | 0 | return 0; |
321 | 0 | } |
322 | | |
323 | 113k | if (zone->tzid) { |
324 | 0 | icalmemory_free_buffer(zone->tzid); |
325 | 0 | } |
326 | 113k | zone->tzid = icalmemory_strdup(tzid); |
327 | | |
328 | 113k | if (zone->component) { |
329 | 0 | icalcomponent_free(zone->component); |
330 | 0 | } |
331 | 113k | zone->component = component; |
332 | | |
333 | 113k | if (zone->location) { |
334 | 0 | icalmemory_free_buffer(zone->location); |
335 | 0 | } |
336 | 113k | zone->location = icaltimezone_get_location_from_vtimezone(component); |
337 | | |
338 | 113k | if (zone->tznames) { |
339 | 0 | icalmemory_free_buffer(zone->tznames); |
340 | 0 | } |
341 | 113k | zone->tznames = icaltimezone_get_tznames_from_vtimezone(component); |
342 | | |
343 | 113k | return 1; |
344 | 113k | } |
345 | | |
346 | | char *icaltimezone_get_location_from_vtimezone(icalcomponent *component) |
347 | 113k | { |
348 | 113k | icalproperty *prop; |
349 | 113k | const char *location; |
350 | 113k | const char *name; |
351 | | |
352 | 113k | prop = icalcomponent_get_first_property(component, ICAL_LOCATION_PROPERTY); |
353 | 113k | if (prop) { |
354 | 195 | location = icalproperty_get_location(prop); |
355 | 195 | if (location) |
356 | 195 | return icalmemory_strdup(location); |
357 | 195 | } |
358 | | |
359 | 113k | prop = icalcomponent_get_first_property(component, ICAL_X_PROPERTY); |
360 | 114k | while (prop) { |
361 | 1.36k | name = icalproperty_get_x_name(prop); |
362 | 1.36k | if (name && !strcasecmp(name, "X-LIC-LOCATION")) { |
363 | 568 | location = icalproperty_get_x(prop); |
364 | 568 | if (location) |
365 | 194 | return icalmemory_strdup(location); |
366 | 568 | } |
367 | 1.17k | prop = icalcomponent_get_next_property(component, ICAL_X_PROPERTY); |
368 | 1.17k | } |
369 | | |
370 | 113k | return NULL; |
371 | 113k | } |
372 | | |
373 | | char *icaltimezone_get_tznames_from_vtimezone(icalcomponent *component) |
374 | 113k | { |
375 | 113k | icalcomponent *comp; |
376 | 113k | icalcomponent_kind type; |
377 | 113k | icalproperty *prop; |
378 | 113k | struct icaltimetype dtstart; |
379 | 113k | struct icaldatetimeperiodtype rdate; |
380 | 113k | const char *current_tzname; |
381 | 113k | const char *standard_tzname = NULL, *daylight_tzname = NULL; |
382 | 113k | struct icaltimetype standard_max_date, daylight_max_date; |
383 | 113k | struct icaltimetype current_max_date; |
384 | | |
385 | 113k | standard_max_date = icaltime_null_time(); |
386 | 113k | daylight_max_date = icaltime_null_time(); |
387 | | |
388 | | /* Step through the STANDARD & DAYLIGHT subcomponents. */ |
389 | 113k | comp = icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT); |
390 | 119k | while (comp) { |
391 | 5.89k | type = icalcomponent_isa(comp); |
392 | 5.89k | if (type == ICAL_XSTANDARD_COMPONENT || type == ICAL_XDAYLIGHT_COMPONENT) { |
393 | 4.04k | current_max_date = icaltime_null_time(); |
394 | 4.04k | current_tzname = NULL; |
395 | | |
396 | | /* Step through the properties. We want to find the TZNAME, and |
397 | | the largest DTSTART or RDATE. */ |
398 | 4.04k | prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); |
399 | 14.0k | while (prop) { |
400 | 10.0k | switch (icalproperty_isa(prop)) { |
401 | 3.14k | case ICAL_TZNAME_PROPERTY: |
402 | 3.14k | current_tzname = icalproperty_get_tzname(prop); |
403 | 3.14k | break; |
404 | | |
405 | 914 | case ICAL_DTSTART_PROPERTY: |
406 | 914 | dtstart = icalproperty_get_dtstart(prop); |
407 | 914 | if (icaltime_compare(dtstart, current_max_date) > 0) |
408 | 506 | current_max_date = dtstart; |
409 | | |
410 | 914 | break; |
411 | | |
412 | 4.92k | case ICAL_RDATE_PROPERTY: |
413 | 4.92k | rdate = icalproperty_get_rdate(prop); |
414 | 4.92k | if (icaltime_compare(rdate.time, current_max_date) > 0) |
415 | 2.24k | current_max_date = rdate.time; |
416 | | |
417 | 4.92k | break; |
418 | | |
419 | 1.04k | default: |
420 | 1.04k | break; |
421 | 10.0k | } |
422 | | |
423 | 10.0k | prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); |
424 | 10.0k | } |
425 | | |
426 | 4.04k | if (current_tzname) { |
427 | 2.77k | if (type == ICAL_XSTANDARD_COMPONENT) { |
428 | 1.49k | if (!standard_tzname || |
429 | 1.49k | icaltime_compare(current_max_date, standard_max_date) > 0) { |
430 | 1.29k | standard_max_date = current_max_date; |
431 | 1.29k | standard_tzname = current_tzname; |
432 | 1.29k | } |
433 | 1.49k | } else { |
434 | 1.28k | if (!daylight_tzname || |
435 | 1.28k | icaltime_compare(current_max_date, daylight_max_date) > 0) { |
436 | 985 | daylight_max_date = current_max_date; |
437 | 985 | daylight_tzname = current_tzname; |
438 | 985 | } |
439 | 1.28k | } |
440 | 2.77k | } |
441 | 4.04k | } |
442 | | |
443 | 5.89k | comp = icalcomponent_get_next_component(component, ICAL_ANY_COMPONENT); |
444 | 5.89k | } |
445 | | |
446 | | /* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME |
447 | | strings, which is totally useless. So we return NULL in that case. */ |
448 | 113k | if (standard_tzname && !strcmp(standard_tzname, "Standard Time")) |
449 | 194 | return NULL; |
450 | | |
451 | | /* If both standard and daylight TZNAMEs were found, if they are the same |
452 | | we return just one, else we format them like "EST/EDT". */ |
453 | 113k | if (standard_tzname && daylight_tzname) { |
454 | 536 | size_t standard_len, daylight_len; |
455 | 536 | char *tznames; |
456 | | |
457 | 536 | if (!strcmp(standard_tzname, daylight_tzname)) |
458 | 242 | return icalmemory_strdup(standard_tzname); |
459 | | |
460 | 294 | standard_len = strlen(standard_tzname); |
461 | 294 | daylight_len = strlen(daylight_tzname); |
462 | 294 | tznames = icalmemory_new_buffer(standard_len + daylight_len + 2); |
463 | 294 | strcpy(tznames, standard_tzname); |
464 | 294 | tznames[standard_len] = '/'; |
465 | 294 | strcpy(tznames + standard_len + 1, daylight_tzname); |
466 | 294 | return tznames; |
467 | 112k | } else { |
468 | 112k | const char *tznames; |
469 | | |
470 | | /* If either of the TZNAMEs was found just return that, else NULL. */ |
471 | 112k | tznames = standard_tzname ? standard_tzname : daylight_tzname; |
472 | 112k | return tznames ? icalmemory_strdup(tznames) : NULL; |
473 | 112k | } |
474 | 113k | } |
475 | | |
476 | | static void icaltimezone_ensure_coverage(icaltimezone *zone, int end_year) |
477 | 0 | { |
478 | | /* When we expand timezone changes we always expand at least up to this |
479 | | year, plus ICALTIMEZONE_EXTRA_COVERAGE. */ |
480 | 0 | static ICAL_GLOBAL_VAR int icaltimezone_minimum_expansion_year = -1; |
481 | |
|
482 | 0 | int changes_end_year; |
483 | |
|
484 | 0 | icaltimezone_load_builtin_timezone(zone); |
485 | |
|
486 | 0 | if (icaltimezone_minimum_expansion_year == -1) { |
487 | 0 | struct icaltimetype today = icaltime_today(); |
488 | |
|
489 | 0 | icaltimezone_minimum_expansion_year = today.year; |
490 | 0 | } |
491 | |
|
492 | 0 | changes_end_year = end_year; |
493 | 0 | if (changes_end_year < icaltimezone_minimum_expansion_year) |
494 | 0 | changes_end_year = icaltimezone_minimum_expansion_year; |
495 | |
|
496 | 0 | changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE; |
497 | |
|
498 | 0 | if (changes_end_year > ICALTIMEZONE_MAX_YEAR) |
499 | 0 | changes_end_year = ICALTIMEZONE_MAX_YEAR; |
500 | |
|
501 | 0 | if (!zone->changes || zone->end_year < end_year) |
502 | 0 | icaltimezone_expand_changes(zone, changes_end_year); |
503 | 0 | } |
504 | | |
505 | | /* Hold the icaltimezone_changes_lock(); before calling this function */ |
506 | | static void icaltimezone_expand_changes(icaltimezone *zone, int end_year) |
507 | 0 | { |
508 | 0 | icalarray *changes; |
509 | 0 | icalcomponent *comp; |
510 | |
|
511 | | #if 0 |
512 | | printf("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year); |
513 | | #endif |
514 | |
|
515 | 0 | changes = icalarray_new(sizeof(icaltimezonechange), 32); |
516 | 0 | if (!changes) |
517 | 0 | return; |
518 | | |
519 | | /* Scan the STANDARD and DAYLIGHT subcomponents. */ |
520 | 0 | comp = icalcomponent_get_first_component(zone->component, ICAL_ANY_COMPONENT); |
521 | 0 | while (comp) { |
522 | 0 | icaltimezone_expand_vtimezone(comp, end_year, changes); |
523 | 0 | comp = icalcomponent_get_next_component(zone->component, ICAL_ANY_COMPONENT); |
524 | 0 | } |
525 | | |
526 | | /* Sort the changes. We may have duplicates but I don't think it will |
527 | | matter. */ |
528 | 0 | icalarray_sort(changes, icaltimezone_compare_change_fn); |
529 | |
|
530 | 0 | if (zone->changes) { |
531 | 0 | icalarray_free(zone->changes); |
532 | 0 | zone->changes = 0; |
533 | 0 | } |
534 | |
|
535 | 0 | zone->changes = changes; |
536 | 0 | zone->end_year = end_year; |
537 | 0 | } |
538 | | |
539 | | void icaltimezone_expand_vtimezone(icalcomponent *comp, int end_year, icalarray *changes) |
540 | 0 | { |
541 | 0 | icaltimezonechange change; |
542 | 0 | icalproperty *prop; |
543 | 0 | struct icaltimetype dtstart, occ; |
544 | 0 | struct icalrecurrencetype rrule; |
545 | 0 | icalrecur_iterator *rrule_iterator; |
546 | 0 | struct icaldatetimeperiodtype rdate; |
547 | 0 | int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0; |
548 | 0 | int has_rdate = 0, has_rrule = 0; |
549 | | |
550 | | /* First we check if it is a STANDARD or DAYLIGHT component, and |
551 | | just return if it isn't. */ |
552 | 0 | if (icalcomponent_isa(comp) == ICAL_XSTANDARD_COMPONENT) { |
553 | 0 | change.is_daylight = 0; |
554 | 0 | } else if (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT) { |
555 | 0 | change.is_daylight = 1; |
556 | 0 | } else { |
557 | 0 | return; |
558 | 0 | } |
559 | | |
560 | | /* Step through each of the properties to find the DTSTART, |
561 | | TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here |
562 | | since we need these properties before we can do that. */ |
563 | 0 | prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); |
564 | 0 | while (prop) { |
565 | 0 | switch (icalproperty_isa(prop)) { |
566 | 0 | case ICAL_DTSTART_PROPERTY: |
567 | 0 | dtstart = icalproperty_get_dtstart(prop); |
568 | 0 | found_dtstart = 1; |
569 | 0 | break; |
570 | 0 | case ICAL_TZOFFSETTO_PROPERTY: |
571 | 0 | change.utc_offset = icalproperty_get_tzoffsetto(prop); |
572 | | /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset); */ |
573 | 0 | found_tzoffsetto = 1; |
574 | 0 | break; |
575 | 0 | case ICAL_TZOFFSETFROM_PROPERTY: |
576 | 0 | change.prev_utc_offset = icalproperty_get_tzoffsetfrom(prop); |
577 | | /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset); */ |
578 | 0 | found_tzoffsetfrom = 1; |
579 | 0 | break; |
580 | 0 | case ICAL_RDATE_PROPERTY: |
581 | 0 | has_rdate = 1; |
582 | 0 | break; |
583 | 0 | case ICAL_RRULE_PROPERTY: |
584 | 0 | has_rrule = 1; |
585 | 0 | break; |
586 | 0 | default: |
587 | | /* Just ignore any other properties. */ |
588 | 0 | break; |
589 | 0 | } |
590 | | |
591 | 0 | prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); |
592 | 0 | } |
593 | | |
594 | | /* Microsoft Outlook for Mac (and possibly other versions) will create |
595 | | timezones without a tzoffsetfrom property if it's a timezone that |
596 | | doesn't change for DST. */ |
597 | 0 | if (found_tzoffsetto && !found_tzoffsetfrom) { |
598 | 0 | change.prev_utc_offset = change.utc_offset; |
599 | 0 | found_tzoffsetfrom = 1; |
600 | 0 | } |
601 | | |
602 | | /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to |
603 | | ignore the component. FIXME: Add an error property? */ |
604 | 0 | if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom) |
605 | 0 | return; |
606 | | |
607 | | #if 0 |
608 | | printf("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n", |
609 | | dtstart.year, dtstart.month, dtstart.day, dtstart.hour, dtstart.minute, dtstart.second); |
610 | | #endif |
611 | | |
612 | | /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add |
613 | | a single change for the DTSTART. */ |
614 | 0 | if (!has_rrule) { |
615 | 0 | change.year = dtstart.year; |
616 | 0 | change.month = dtstart.month; |
617 | 0 | change.day = dtstart.day; |
618 | 0 | change.hour = dtstart.hour; |
619 | 0 | change.minute = dtstart.minute; |
620 | 0 | change.second = dtstart.second; |
621 | | |
622 | | /* Convert to UTC. */ |
623 | 0 | icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset); |
624 | |
|
625 | | #if 0 |
626 | | printf(" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n", |
627 | | change.year, change.month, change.day, change.hour, change.minute, change.second); |
628 | | #endif |
629 | | |
630 | | /* Add the change to the array. */ |
631 | 0 | icalarray_append(changes, &change); |
632 | 0 | } |
633 | | |
634 | | /* The component has recurrence data, so we expand that now. */ |
635 | 0 | prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); |
636 | 0 | while (prop && (has_rdate || has_rrule)) { |
637 | | #if 0 |
638 | | printf("Expanding property...\n"); |
639 | | #endif |
640 | 0 | switch (icalproperty_isa(prop)) { |
641 | 0 | case ICAL_RDATE_PROPERTY: |
642 | 0 | rdate = icalproperty_get_rdate(prop); |
643 | 0 | change.year = rdate.time.year; |
644 | 0 | change.month = rdate.time.month; |
645 | 0 | change.day = rdate.time.day; |
646 | | /* RDATEs with a DATE value inherit the time from |
647 | | the DTSTART. */ |
648 | 0 | if (icaltime_is_date(rdate.time)) { |
649 | 0 | change.hour = dtstart.hour; |
650 | 0 | change.minute = dtstart.minute; |
651 | 0 | change.second = dtstart.second; |
652 | 0 | } else { |
653 | 0 | change.hour = rdate.time.hour; |
654 | 0 | change.minute = rdate.time.minute; |
655 | 0 | change.second = rdate.time.second; |
656 | | |
657 | | /* The spec was a bit vague about whether RDATEs were in local |
658 | | time or UTC so we support both to be safe. So if it is in |
659 | | UTC we have to add the UTC offset to get a local time. */ |
660 | 0 | if (!icaltime_is_utc(rdate.time)) |
661 | 0 | icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset); |
662 | 0 | } |
663 | |
|
664 | | #if 0 |
665 | | printf(" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n", |
666 | | change.year, change.month, change.day, |
667 | | change.hour, change.minute, change.second); |
668 | | #endif |
669 | |
|
670 | 0 | icalarray_append(changes, &change); |
671 | 0 | break; |
672 | 0 | case ICAL_RRULE_PROPERTY: |
673 | 0 | rrule = icalproperty_get_rrule(prop); |
674 | | |
675 | | /* If the rrule UNTIL value is set and is in UTC, we convert it to |
676 | | a local time, since the recurrence code has no way to convert |
677 | | it itself. */ |
678 | 0 | if (!icaltime_is_null_time(rrule.until) && icaltime_is_utc(rrule.until)) { |
679 | | #if 0 |
680 | | printf(" Found RRULE UNTIL in UTC.\n"); |
681 | | #endif |
682 | | |
683 | | /* To convert from UTC to a local time, we use the TZOFFSETFROM |
684 | | since that is the offset from UTC that will be in effect |
685 | | when each of the RRULE occurrences happens. */ |
686 | 0 | icaltime_adjust(&rrule.until, 0, 0, 0, change.prev_utc_offset); |
687 | 0 | rrule.until.zone = NULL; |
688 | 0 | } |
689 | | |
690 | | /* Add the dtstart to changes, otherwise some oddly-defined VTIMEZONE |
691 | | components can cause the first year to get skipped. */ |
692 | 0 | change.year = dtstart.year; |
693 | 0 | change.month = dtstart.month; |
694 | 0 | change.day = dtstart.day; |
695 | 0 | change.hour = dtstart.hour; |
696 | 0 | change.minute = dtstart.minute; |
697 | 0 | change.second = dtstart.second; |
698 | |
|
699 | | #if 0 |
700 | | printf(" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n", |
701 | | change.year, change.month, change.day, |
702 | | change.hour, change.minute, change.second); |
703 | | #endif |
704 | |
|
705 | 0 | icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset); |
706 | |
|
707 | 0 | icalarray_append(changes, &change); |
708 | |
|
709 | 0 | rrule_iterator = icalrecur_iterator_new(rrule, dtstart); |
710 | 0 | for (; rrule_iterator;) { |
711 | 0 | occ = icalrecur_iterator_next(rrule_iterator); |
712 | | /* Skip dtstart since we just added it */ |
713 | 0 | if (icaltime_compare(dtstart, occ) == 0) { |
714 | 0 | continue; |
715 | 0 | } |
716 | 0 | if (occ.year > end_year || icaltime_is_null_time(occ)) { |
717 | 0 | break; |
718 | 0 | } |
719 | 0 | change.year = occ.year; |
720 | 0 | change.month = occ.month; |
721 | 0 | change.day = occ.day; |
722 | 0 | change.hour = occ.hour; |
723 | 0 | change.minute = occ.minute; |
724 | 0 | change.second = occ.second; |
725 | |
|
726 | | #if 0 |
727 | | printf(" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n", |
728 | | change.year, change.month, change.day, |
729 | | change.hour, change.minute, change.second); |
730 | | #endif |
731 | |
|
732 | 0 | icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset); |
733 | |
|
734 | 0 | icalarray_append(changes, &change); |
735 | 0 | } |
736 | |
|
737 | 0 | icalrecur_iterator_free(rrule_iterator); |
738 | 0 | break; |
739 | 0 | default: |
740 | 0 | break; |
741 | 0 | } |
742 | | |
743 | 0 | prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); |
744 | 0 | } |
745 | 0 | } |
746 | | |
747 | | /** @brief A function to compare 2 icaltimezonechange elements, used for |
748 | | * qsort(). |
749 | | */ |
750 | | static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2) |
751 | 0 | { |
752 | 0 | const icaltimezonechange *change1, *change2; |
753 | 0 | int retval; |
754 | |
|
755 | 0 | change1 = (const icaltimezonechange *)elem1; |
756 | 0 | change2 = (const icaltimezonechange *)elem2; |
757 | |
|
758 | 0 | if (change1->year < change2->year) { |
759 | 0 | retval = -1; |
760 | 0 | } else if (change1->year > change2->year) { |
761 | 0 | retval = 1; |
762 | 0 | } else if (change1->month < change2->month) { |
763 | 0 | retval = -1; |
764 | 0 | } else if (change1->month > change2->month) { |
765 | 0 | retval = 1; |
766 | 0 | } else if (change1->day < change2->day) { |
767 | 0 | retval = -1; |
768 | 0 | } else if (change1->day > change2->day) { |
769 | 0 | retval = 1; |
770 | 0 | } else if (change1->hour < change2->hour) { |
771 | 0 | retval = -1; |
772 | 0 | } else if (change1->hour > change2->hour) { |
773 | 0 | retval = 1; |
774 | 0 | } else if (change1->minute < change2->minute) { |
775 | 0 | retval = -1; |
776 | 0 | } else if (change1->minute > change2->minute) { |
777 | 0 | retval = 1; |
778 | 0 | } else if (change1->second < change2->second) { |
779 | 0 | retval = -1; |
780 | 0 | } else if (change1->second > change2->second) { |
781 | 0 | retval = 1; |
782 | 0 | } else { |
783 | 0 | retval = 0; |
784 | 0 | } |
785 | |
|
786 | 0 | return retval; |
787 | 0 | } |
788 | | |
789 | | void icaltimezone_convert_time(struct icaltimetype *tt, |
790 | | icaltimezone *from_zone, icaltimezone *to_zone) |
791 | 0 | { |
792 | 0 | int utc_offset, is_daylight; |
793 | | |
794 | | /* If the time is a DATE value or both timezones are the same, or we are |
795 | | converting a floating time, we don't need to do anything. */ |
796 | 0 | if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL) |
797 | 0 | return; |
798 | | |
799 | | /* Convert the time to UTC by getting the UTC offset and subtracting it. */ |
800 | 0 | utc_offset = icaltimezone_get_utc_offset(from_zone, tt, NULL); |
801 | 0 | icaltime_adjust(tt, 0, 0, 0, -utc_offset); |
802 | | |
803 | | /* Now we convert the time to the new timezone by getting the UTC offset |
804 | | of our UTC time and adding it. */ |
805 | 0 | utc_offset = icaltimezone_get_utc_offset_of_utc_time(to_zone, tt, &is_daylight); |
806 | 0 | tt->is_daylight = is_daylight; |
807 | 0 | icaltime_adjust(tt, 0, 0, 0, utc_offset); |
808 | 0 | } |
809 | | |
810 | | int icaltimezone_get_utc_offset(icaltimezone *zone, struct icaltimetype *tt, int *is_daylight) |
811 | 0 | { |
812 | 0 | icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change; |
813 | 0 | size_t change_num, change_num_to_use; |
814 | 0 | int found_change; |
815 | 0 | int step, utc_offset_change, cmp; |
816 | 0 | int want_daylight; |
817 | |
|
818 | 0 | if (tt == NULL) |
819 | 0 | return 0; |
820 | | |
821 | 0 | if (is_daylight) |
822 | 0 | *is_daylight = 0; |
823 | | |
824 | | /* For local times and UTC return 0. */ |
825 | 0 | if (zone == NULL || zone == &utc_timezone) |
826 | 0 | return 0; |
827 | | |
828 | | /* Use the builtin icaltimezone if possible. */ |
829 | 0 | if (zone->builtin_timezone) |
830 | 0 | zone = zone->builtin_timezone; |
831 | |
|
832 | 0 | icaltimezone_changes_lock(); |
833 | | |
834 | | /* Make sure the changes array is expanded up to the given time. */ |
835 | 0 | icaltimezone_ensure_coverage(zone, tt->year); |
836 | |
|
837 | 0 | if (!zone->changes || zone->changes->num_elements == 0) { |
838 | 0 | icaltimezone_changes_unlock(); |
839 | 0 | return 0; |
840 | 0 | } |
841 | | |
842 | | /* Copy the time parts of the icaltimetype to an icaltimezonechange so we |
843 | | can use our comparison function on it. */ |
844 | 0 | tt_change.year = tt->year; |
845 | 0 | tt_change.month = tt->month; |
846 | 0 | tt_change.day = tt->day; |
847 | 0 | tt_change.hour = tt->hour; |
848 | 0 | tt_change.minute = tt->minute; |
849 | 0 | tt_change.second = tt->second; |
850 | | |
851 | | /* This should find a change close to the time, either the change before |
852 | | it or the change after it. */ |
853 | 0 | change_num = icaltimezone_find_nearby_change(zone, &tt_change); |
854 | | |
855 | | /* Now move backwards or forwards to find the timezone change that applies |
856 | | to tt. It should only have to do 1 or 2 steps. */ |
857 | 0 | zone_change = icalarray_element_at(zone->changes, change_num); |
858 | 0 | step = 1; |
859 | 0 | found_change = 0; |
860 | 0 | change_num_to_use = -1; |
861 | 0 | for (;;) { |
862 | | /* Copy the change, so we can adjust it. */ |
863 | 0 | tmp_change = *zone_change; |
864 | | |
865 | | /* If the clock is going backward, check if it is in the region of time |
866 | | that is used twice. If it is, use the change with the daylight |
867 | | setting which matches tt, or use standard if we don't know. */ |
868 | 0 | if (tmp_change.utc_offset < tmp_change.prev_utc_offset) { |
869 | | /* If the time change is at 2:00AM local time and the clock is |
870 | | going back to 1:00AM we adjust the change to 1:00AM. We may |
871 | | have the wrong change but we'll figure that out later. */ |
872 | 0 | icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.utc_offset); |
873 | 0 | } else { |
874 | 0 | icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset); |
875 | 0 | } |
876 | |
|
877 | 0 | cmp = icaltimezone_compare_change_fn(&tt_change, &tmp_change); |
878 | | |
879 | | /* If the given time is on or after this change, then this change may |
880 | | apply, but we continue as a later change may be the right one. |
881 | | If the given time is before this change, then if we have already |
882 | | found a change which applies we can use that, else we need to step |
883 | | backwards. */ |
884 | 0 | if (cmp >= 0) { |
885 | 0 | found_change = 1; |
886 | 0 | change_num_to_use = change_num; |
887 | 0 | } else { |
888 | 0 | step = -1; |
889 | 0 | } |
890 | | |
891 | | /* If we are stepping backwards through the changes and we have found |
892 | | a change that applies, then we know this is the change to use so |
893 | | we exit the loop. */ |
894 | 0 | if (step == -1 && found_change == 1) |
895 | 0 | break; |
896 | | |
897 | | /* If we go past the start of the changes array, then we have no data |
898 | | for this time so we return the prev UTC offset. */ |
899 | 0 | if (change_num == 0 && step < 0) { |
900 | 0 | if (is_daylight) { |
901 | 0 | *is_daylight = ! tmp_change.is_daylight; |
902 | 0 | } |
903 | |
|
904 | 0 | icaltimezone_changes_unlock(); |
905 | |
|
906 | 0 | return tmp_change.prev_utc_offset; |
907 | 0 | } |
908 | | |
909 | 0 | change_num += step; |
910 | |
|
911 | 0 | if (change_num >= zone->changes->num_elements) |
912 | 0 | break; |
913 | | |
914 | 0 | zone_change = icalarray_element_at(zone->changes, change_num); |
915 | 0 | } |
916 | | |
917 | | /* If we didn't find a change to use, then we have a bug! */ |
918 | 0 | icalerror_assert(found_change == 1, "No applicable timezone change found"); |
919 | | |
920 | | /* Now we just need to check if the time is in the overlapped region of |
921 | | time when clocks go back. */ |
922 | 0 | zone_change = icalarray_element_at(zone->changes, change_num_to_use); |
923 | |
|
924 | 0 | utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset; |
925 | 0 | if (utc_offset_change < 0 && change_num_to_use > 0) { |
926 | 0 | tmp_change = *zone_change; |
927 | 0 | icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset); |
928 | |
|
929 | 0 | if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) < 0) { |
930 | | /* The time is in the overlapped region, so we may need to use |
931 | | either the current zone_change or the previous one. If the |
932 | | time has the is_daylight field set we use the matching change, |
933 | | else we use the change with standard time. */ |
934 | 0 | prev_zone_change = icalarray_element_at(zone->changes, change_num_to_use - 1); |
935 | | |
936 | | /* I was going to add an is_daylight flag to struct icaltimetype, |
937 | | but iCalendar doesn't let us distinguish between standard and |
938 | | daylight time anyway, so there's no point. So we just use the |
939 | | standard time instead. */ |
940 | 0 | want_daylight = (tt->is_daylight == 1) ? 1 : 0; |
941 | |
|
942 | | #if 0 |
943 | | if (zone_change->is_daylight == prev_zone_change->is_daylight) { |
944 | | printf(" **** Same is_daylight setting\n"); |
945 | | } |
946 | | #endif |
947 | |
|
948 | 0 | if (zone_change->is_daylight != want_daylight && |
949 | 0 | prev_zone_change->is_daylight == want_daylight) { |
950 | 0 | zone_change = prev_zone_change; |
951 | 0 | } |
952 | 0 | } |
953 | 0 | } |
954 | | |
955 | | /* Now we know exactly which timezone change applies to the time, so |
956 | | we can return the UTC offset and whether it is a daylight time. */ |
957 | 0 | if (is_daylight) { |
958 | 0 | *is_daylight = zone_change->is_daylight; |
959 | 0 | } |
960 | 0 | utc_offset_change = zone_change->utc_offset; |
961 | |
|
962 | 0 | icaltimezone_changes_unlock(); |
963 | |
|
964 | 0 | return utc_offset_change; |
965 | 0 | } |
966 | | |
967 | | int icaltimezone_get_utc_offset_of_utc_time(icaltimezone *zone, |
968 | | struct icaltimetype *tt, int *is_daylight) |
969 | 0 | { |
970 | 0 | icaltimezonechange *zone_change, tt_change, tmp_change; |
971 | 0 | size_t change_num, change_num_to_use; |
972 | 0 | int found_change = 1; |
973 | 0 | int step, utc_offset; |
974 | |
|
975 | 0 | if (is_daylight) |
976 | 0 | *is_daylight = 0; |
977 | | |
978 | | /* For local times and UTC return 0. */ |
979 | 0 | if (zone == NULL || zone == &utc_timezone) |
980 | 0 | return 0; |
981 | | |
982 | | /* Use the builtin icaltimezone if possible. */ |
983 | 0 | if (zone->builtin_timezone) |
984 | 0 | zone = zone->builtin_timezone; |
985 | |
|
986 | 0 | icaltimezone_changes_lock(); |
987 | | |
988 | | /* Make sure the changes array is expanded up to the given time. */ |
989 | 0 | icaltimezone_ensure_coverage(zone, tt->year); |
990 | |
|
991 | 0 | if (!zone->changes || zone->changes->num_elements == 0) { |
992 | 0 | icaltimezone_changes_unlock(); |
993 | 0 | return 0; |
994 | 0 | } |
995 | | |
996 | | /* Copy the time parts of the icaltimetype to an icaltimezonechange so we |
997 | | can use our comparison function on it. */ |
998 | 0 | tt_change.year = tt->year; |
999 | 0 | tt_change.month = tt->month; |
1000 | 0 | tt_change.day = tt->day; |
1001 | 0 | tt_change.hour = tt->hour; |
1002 | 0 | tt_change.minute = tt->minute; |
1003 | 0 | tt_change.second = tt->second; |
1004 | | |
1005 | | /* This should find a change close to the time, either the change before |
1006 | | it or the change after it. */ |
1007 | 0 | change_num = icaltimezone_find_nearby_change(zone, &tt_change); |
1008 | | |
1009 | | /* Now move backwards or forwards to find the timezone change that applies |
1010 | | to tt. It should only have to do 1 or 2 steps. */ |
1011 | 0 | zone_change = icalarray_element_at(zone->changes, change_num); |
1012 | 0 | step = 1; |
1013 | 0 | found_change = 0; |
1014 | 0 | change_num_to_use = -1; |
1015 | 0 | for (;;) { |
1016 | | /* Copy the change and adjust it to UTC. */ |
1017 | 0 | tmp_change = *zone_change; |
1018 | | |
1019 | | /* If the given time is on or after this change, then this change may |
1020 | | apply, but we continue as a later change may be the right one. |
1021 | | If the given time is before this change, then if we have already |
1022 | | found a change which applies we can use that, else we need to step |
1023 | | backwards. */ |
1024 | 0 | if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) >= 0) { |
1025 | 0 | found_change = 1; |
1026 | 0 | change_num_to_use = change_num; |
1027 | 0 | } else { |
1028 | 0 | step = -1; |
1029 | 0 | } |
1030 | | |
1031 | | /* If we are stepping backwards through the changes and we have found |
1032 | | a change that applies, then we know this is the change to use so |
1033 | | we exit the loop. */ |
1034 | 0 | if (step == -1 && found_change == 1) |
1035 | 0 | break; |
1036 | | |
1037 | | /* If we go past the start of the changes array, then we have no data |
1038 | | for this time so we return the prev UTC offset. */ |
1039 | 0 | if (change_num == 0 && step < 0) { |
1040 | 0 | if (is_daylight) { |
1041 | 0 | *is_daylight = ! tmp_change.is_daylight; |
1042 | 0 | } |
1043 | |
|
1044 | 0 | icaltimezone_changes_unlock(); |
1045 | |
|
1046 | 0 | return tmp_change.prev_utc_offset; |
1047 | 0 | } |
1048 | | |
1049 | 0 | change_num += step; |
1050 | |
|
1051 | 0 | if (change_num >= zone->changes->num_elements) |
1052 | 0 | break; |
1053 | | |
1054 | 0 | zone_change = icalarray_element_at(zone->changes, change_num); |
1055 | 0 | } |
1056 | | |
1057 | | /* If we didn't find a change to use, then we have a bug! */ |
1058 | 0 | icalerror_assert(found_change == 1, "No applicable timezone change found"); |
1059 | | |
1060 | | /* Now we know exactly which timezone change applies to the time, so |
1061 | | we can return the UTC offset and whether it is a daylight time. */ |
1062 | 0 | zone_change = icalarray_element_at(zone->changes, change_num_to_use); |
1063 | 0 | if (is_daylight) { |
1064 | 0 | *is_daylight = zone_change->is_daylight; |
1065 | 0 | } |
1066 | 0 | utc_offset = zone_change->utc_offset; |
1067 | |
|
1068 | 0 | icaltimezone_changes_unlock(); |
1069 | |
|
1070 | 0 | return utc_offset; |
1071 | 0 | } |
1072 | | |
1073 | | /** @brief Returns the index of a timezone change which is close to the time |
1074 | | * given in change. |
1075 | | * |
1076 | | * Hold icaltimezone_changes_lock(); before calling this function. |
1077 | | */ |
1078 | | static size_t icaltimezone_find_nearby_change(icaltimezone *zone, icaltimezonechange * change) |
1079 | 0 | { |
1080 | 0 | icaltimezonechange *zone_change; |
1081 | 0 | size_t lower, middle, upper; |
1082 | 0 | int cmp; |
1083 | | |
1084 | | /* Do a simple binary search. */ |
1085 | 0 | lower = middle = 0; |
1086 | 0 | upper = zone->changes->num_elements; |
1087 | |
|
1088 | 0 | while (lower < upper) { |
1089 | 0 | middle = (lower + upper) / 2; |
1090 | 0 | zone_change = icalarray_element_at(zone->changes, middle); |
1091 | 0 | cmp = icaltimezone_compare_change_fn(change, zone_change); |
1092 | 0 | if (cmp == 0) { |
1093 | 0 | break; |
1094 | 0 | } else if (cmp < 0) { |
1095 | 0 | upper = middle; |
1096 | 0 | } else { |
1097 | 0 | lower = middle + 1; |
1098 | 0 | } |
1099 | 0 | } |
1100 | |
|
1101 | 0 | return middle; |
1102 | 0 | } |
1103 | | |
1104 | | /** @brief Adds (or subtracts) a time from an icaltimezonechange. |
1105 | | * |
1106 | | * NOTE: This function is exactly the same as icaltime_adjust() except |
1107 | | * for the type of the first parameter. |
1108 | | */ |
1109 | | static void icaltimezone_adjust_change(icaltimezonechange *tt, |
1110 | | int days, int hours, int minutes, int seconds) |
1111 | 0 | { |
1112 | 0 | int second, minute, hour, day; |
1113 | 0 | int minutes_overflow, hours_overflow, days_overflow; |
1114 | 0 | int days_in_month; |
1115 | | |
1116 | | /* Add on the seconds. */ |
1117 | 0 | second = tt->second + seconds; |
1118 | 0 | tt->second = second % 60; |
1119 | 0 | minutes_overflow = second / 60; |
1120 | 0 | if (tt->second < 0) { |
1121 | 0 | tt->second += 60; |
1122 | 0 | minutes_overflow--; |
1123 | 0 | } |
1124 | | |
1125 | | /* Add on the minutes. */ |
1126 | 0 | minute = tt->minute + minutes + minutes_overflow; |
1127 | 0 | tt->minute = minute % 60; |
1128 | 0 | hours_overflow = minute / 60; |
1129 | 0 | if (tt->minute < 0) { |
1130 | 0 | tt->minute += 60; |
1131 | 0 | hours_overflow--; |
1132 | 0 | } |
1133 | | |
1134 | | /* Add on the hours. */ |
1135 | 0 | hour = tt->hour + hours + hours_overflow; |
1136 | 0 | tt->hour = hour % 24; |
1137 | 0 | days_overflow = hour / 24; |
1138 | 0 | if (tt->hour < 0) { |
1139 | 0 | tt->hour += 24; |
1140 | 0 | days_overflow--; |
1141 | 0 | } |
1142 | | |
1143 | | /* Add on the days. */ |
1144 | 0 | day = tt->day + days + days_overflow; |
1145 | 0 | if (day > 0) { |
1146 | 0 | for (;;) { |
1147 | 0 | days_in_month = icaltime_days_in_month(tt->month, tt->year); |
1148 | 0 | if (day <= days_in_month) |
1149 | 0 | break; |
1150 | | |
1151 | 0 | tt->month++; |
1152 | 0 | if (tt->month >= 13) { |
1153 | 0 | tt->year++; |
1154 | 0 | tt->month = 1; |
1155 | 0 | } |
1156 | |
|
1157 | 0 | day -= days_in_month; |
1158 | 0 | } |
1159 | 0 | } else { |
1160 | 0 | while (day <= 0) { |
1161 | 0 | if (tt->month == 1) { |
1162 | 0 | tt->year--; |
1163 | 0 | tt->month = 12; |
1164 | 0 | } else { |
1165 | 0 | tt->month--; |
1166 | 0 | } |
1167 | |
|
1168 | 0 | day += icaltime_days_in_month(tt->month, tt->year); |
1169 | 0 | } |
1170 | 0 | } |
1171 | 0 | tt->day = day; |
1172 | 0 | } |
1173 | | |
1174 | | const char *icaltimezone_get_tzid(icaltimezone *zone) |
1175 | 0 | { |
1176 | | /* If this is a floating time, without a timezone, return NULL. */ |
1177 | 0 | if (!zone) |
1178 | 0 | return NULL; |
1179 | | |
1180 | 0 | icaltimezone_load_builtin_timezone(zone); |
1181 | |
|
1182 | 0 | return zone->tzid; |
1183 | 0 | } |
1184 | | |
1185 | | const char *icaltimezone_get_location(icaltimezone *zone) |
1186 | 0 | { |
1187 | | /* If this is a floating time, without a timezone, return NULL. */ |
1188 | 0 | if (!zone) |
1189 | 0 | return NULL; |
1190 | | |
1191 | | /* Note that for builtin timezones this comes from zones.tab so we don't |
1192 | | need to check the timezone is loaded here. */ |
1193 | 0 | return zone->location; |
1194 | 0 | } |
1195 | | |
1196 | | const char *icaltimezone_get_tznames(icaltimezone *zone) |
1197 | 0 | { |
1198 | | /* If this is a floating time, without a timezone, return NULL. */ |
1199 | 0 | if (!zone) |
1200 | 0 | return NULL; |
1201 | | |
1202 | 0 | icaltimezone_load_builtin_timezone(zone); |
1203 | |
|
1204 | 0 | return zone->tznames; |
1205 | 0 | } |
1206 | | |
1207 | | double icaltimezone_get_latitude(icaltimezone *zone) |
1208 | 0 | { |
1209 | | /* If this is a floating time, without a timezone, return 0. */ |
1210 | 0 | if (!zone) |
1211 | 0 | return 0.0; |
1212 | | |
1213 | | /* Note that for builtin timezones this comes from zones.tab so we don't |
1214 | | need to check the timezone is loaded here. */ |
1215 | 0 | return zone->latitude; |
1216 | 0 | } |
1217 | | |
1218 | | double icaltimezone_get_longitude(icaltimezone *zone) |
1219 | 0 | { |
1220 | | /* If this is a floating time, without a timezone, return 0. */ |
1221 | 0 | if (!zone) |
1222 | 0 | return 0.0; |
1223 | | |
1224 | | /* Note that for builtin timezones this comes from zones.tab so we don't |
1225 | | need to check the timezone is loaded here. */ |
1226 | 0 | return zone->longitude; |
1227 | 0 | } |
1228 | | |
1229 | | icalcomponent *icaltimezone_get_component(icaltimezone *zone) |
1230 | 572M | { |
1231 | | /* If this is a floating time, without a timezone, return NULL. */ |
1232 | 572M | if (!zone) |
1233 | 0 | return NULL; |
1234 | | |
1235 | 572M | icaltimezone_load_builtin_timezone(zone); |
1236 | | |
1237 | 572M | return zone->component; |
1238 | 572M | } |
1239 | | |
1240 | | int icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp) |
1241 | 0 | { |
1242 | 0 | icaltimezone_reset(zone); |
1243 | 0 | return icaltimezone_get_vtimezone_properties(zone, comp); |
1244 | 0 | } |
1245 | | |
1246 | | static const char *skip_slashes(const char *text, int n_slashes) |
1247 | 0 | { |
1248 | 0 | const char *pp; |
1249 | 0 | int num_slashes = 0; |
1250 | |
|
1251 | 0 | if(!text) |
1252 | 0 | return NULL; |
1253 | | |
1254 | 0 | for (pp = text; *pp; pp++) { |
1255 | 0 | if(*pp == '/') { |
1256 | 0 | num_slashes++; |
1257 | 0 | if(num_slashes == n_slashes) |
1258 | 0 | return pp + 1; |
1259 | 0 | } |
1260 | 0 | } |
1261 | | |
1262 | 0 | return NULL; |
1263 | 0 | } |
1264 | | |
1265 | | const char *icaltimezone_get_display_name(icaltimezone *zone) |
1266 | 0 | { |
1267 | 0 | const char *display_name; |
1268 | 0 | const char *tzid_prefix; |
1269 | |
|
1270 | 0 | display_name = icaltimezone_get_location(zone); |
1271 | 0 | if (!display_name) { |
1272 | 0 | display_name = icaltimezone_get_tznames(zone); |
1273 | 0 | } |
1274 | 0 | if (!display_name) { |
1275 | 0 | display_name = icaltimezone_get_tzid(zone); |
1276 | 0 | tzid_prefix = icaltimezone_tzid_prefix(); |
1277 | | /* Outlook will strip out X-LIC-LOCATION property and so all |
1278 | | we get back in the iTIP replies is the TZID. So we see if |
1279 | | this is one of our TZIDs and if so we jump to the city name |
1280 | | at the end of it. */ |
1281 | 0 | if (display_name && |
1282 | 0 | !strncmp(display_name, tzid_prefix, strlen(tzid_prefix))) { |
1283 | | /* Skip past our prefix */ |
1284 | 0 | display_name += strlen(tzid_prefix); |
1285 | 0 | } |
1286 | 0 | } |
1287 | |
|
1288 | 0 | return display_name; |
1289 | 0 | } |
1290 | | |
1291 | | icalarray *icaltimezone_array_new(void) |
1292 | 1.80k | { |
1293 | 1.80k | return icalarray_new(sizeof(icaltimezone), 16); |
1294 | 1.80k | } |
1295 | | |
1296 | | void icaltimezone_array_append_from_vtimezone(icalarray *timezones, icalcomponent *child) |
1297 | 114k | { |
1298 | 114k | icaltimezone zone; |
1299 | | |
1300 | 114k | icaltimezone_init(&zone); |
1301 | 114k | if (icaltimezone_get_vtimezone_properties(&zone, child)) |
1302 | 113k | icalarray_append(timezones, &zone); |
1303 | 114k | } |
1304 | | |
1305 | | void icaltimezone_array_free(icalarray *timezones) |
1306 | 1.80k | { |
1307 | 1.80k | icaltimezone *zone; |
1308 | 1.80k | size_t i; |
1309 | | |
1310 | 1.80k | if (timezones) { |
1311 | 1.80k | for (i = 0; i < timezones->num_elements; i++) { |
1312 | 0 | zone = icalarray_element_at(timezones, i); |
1313 | 0 | icaltimezone_free(zone, 0); |
1314 | 0 | } |
1315 | | |
1316 | 1.80k | icalarray_free(timezones); |
1317 | 1.80k | } |
1318 | 1.80k | } |
1319 | | |
1320 | | /* |
1321 | | * BUILTIN TIMEZONE HANDLING |
1322 | | */ |
1323 | | |
1324 | | icalarray *icaltimezone_get_builtin_timezones(void) |
1325 | 0 | { |
1326 | 0 | if (!builtin_timezones) |
1327 | 0 | icaltimezone_init_builtin_timezones(); |
1328 | |
|
1329 | 0 | return builtin_timezones; |
1330 | 0 | } |
1331 | | |
1332 | | void icaltimezone_free_builtin_timezones(void) |
1333 | 0 | { |
1334 | 0 | icaltimezone_array_free(builtin_timezones); |
1335 | 0 | builtin_timezones = 0; |
1336 | 0 | } |
1337 | | |
1338 | | icaltimezone *icaltimezone_get_builtin_timezone(const char *location) |
1339 | 0 | { |
1340 | 0 | icalcomponent *comp; |
1341 | 0 | icaltimezone *zone; |
1342 | 0 | size_t lower; |
1343 | 0 | const char *zone_location; |
1344 | |
|
1345 | 0 | if (!location || !location[0]) |
1346 | 0 | return NULL; |
1347 | | |
1348 | 0 | if (!builtin_timezones) |
1349 | 0 | icaltimezone_init_builtin_timezones(); |
1350 | |
|
1351 | 0 | if (strcmp(location, "UTC") == 0 || strcmp(location, "GMT") == 0) |
1352 | 0 | return &utc_timezone; |
1353 | | |
1354 | | #if 0 |
1355 | | /* Do a simple binary search. */ |
1356 | | lower = middle = 0; |
1357 | | upper = builtin_timezones->num_elements; |
1358 | | |
1359 | | while (lower < upper) { |
1360 | | middle = (lower + upper) / 2; |
1361 | | zone = icalarray_element_at(builtin_timezones, middle); |
1362 | | zone_location = icaltimezone_get_location(zone); |
1363 | | cmp = strcmp(location, zone_location); |
1364 | | if (cmp == 0) { |
1365 | | return zone; |
1366 | | } else if (cmp < 0) { |
1367 | | upper = middle; |
1368 | | } else { |
1369 | | lower = middle + 1; |
1370 | | } |
1371 | | } |
1372 | | #endif |
1373 | | |
1374 | | /* The zones from the system are not stored in alphabetical order, |
1375 | | so we just do a sequential search */ |
1376 | 0 | for (lower = 0; lower < builtin_timezones->num_elements; lower++) { |
1377 | 0 | zone = icalarray_element_at(builtin_timezones, lower); |
1378 | 0 | zone_location = icaltimezone_get_location(zone); |
1379 | 0 | if (strcmp(location, zone_location) == 0) |
1380 | 0 | return zone; |
1381 | 0 | } |
1382 | | |
1383 | | /* Check whether file exists, but is not mentioned in zone.tab. |
1384 | | It means it's a deprecated timezone, but still available. */ |
1385 | 0 | comp = icaltzutil_fetch_timezone(location); |
1386 | 0 | if (comp) { |
1387 | 0 | icaltimezone tz; |
1388 | |
|
1389 | 0 | icaltimezone_init(&tz); |
1390 | 0 | if (icaltimezone_set_component(&tz, comp)) { |
1391 | 0 | icalarray_append(builtin_timezones, &tz); |
1392 | 0 | return icalarray_element_at(builtin_timezones, builtin_timezones->num_elements - 1); |
1393 | 0 | } else { |
1394 | 0 | icalcomponent_free(comp); |
1395 | 0 | } |
1396 | 0 | } |
1397 | | |
1398 | 0 | return NULL; |
1399 | 0 | } |
1400 | | |
1401 | | static struct icaltimetype tm_to_icaltimetype(struct tm *tm) |
1402 | 0 | { |
1403 | 0 | struct icaltimetype itt; |
1404 | |
|
1405 | 0 | memset(&itt, 0, sizeof(struct icaltimetype)); |
1406 | | |
1407 | | /* cppcheck-suppress ctuuninitvar */ |
1408 | 0 | itt.second = tm->tm_sec; |
1409 | 0 | itt.minute = tm->tm_min; |
1410 | 0 | itt.hour = tm->tm_hour; |
1411 | |
|
1412 | 0 | itt.day = tm->tm_mday; |
1413 | 0 | itt.month = tm->tm_mon + 1; |
1414 | 0 | itt.year = tm->tm_year + 1900; |
1415 | |
|
1416 | 0 | itt.is_date = 0; |
1417 | |
|
1418 | 0 | return itt; |
1419 | 0 | } |
1420 | | |
1421 | | static int get_offset(icaltimezone *zone) |
1422 | 0 | { |
1423 | 0 | struct tm local; |
1424 | 0 | struct icaltimetype tt; |
1425 | 0 | int offset; |
1426 | 0 | const icaltime_t now = icaltime(NULL); |
1427 | |
|
1428 | 0 | if (!icalgmtime_r(&now, &local)) |
1429 | 0 | return 0; |
1430 | | |
1431 | 0 | tt = tm_to_icaltimetype(&local); |
1432 | 0 | offset = icaltimezone_get_utc_offset(zone, &tt, NULL); |
1433 | |
|
1434 | 0 | return offset; |
1435 | 0 | } |
1436 | | |
1437 | | icaltimezone *icaltimezone_get_builtin_timezone_from_offset(int offset, const char *tzname) |
1438 | 0 | { |
1439 | 0 | icaltimezone *zone = NULL; |
1440 | 0 | size_t i, count; |
1441 | |
|
1442 | 0 | if (!builtin_timezones) |
1443 | 0 | icaltimezone_init_builtin_timezones(); |
1444 | |
|
1445 | 0 | if (offset == 0) |
1446 | 0 | return &utc_timezone; |
1447 | | |
1448 | 0 | if (!tzname) |
1449 | 0 | return NULL; |
1450 | | |
1451 | 0 | count = builtin_timezones->num_elements; |
1452 | |
|
1453 | 0 | for (i = 0; i < count; i++) { |
1454 | 0 | int z_offset; |
1455 | |
|
1456 | 0 | zone = icalarray_element_at(builtin_timezones, i); |
1457 | 0 | icaltimezone_load_builtin_timezone(zone); |
1458 | |
|
1459 | 0 | z_offset = get_offset(zone); |
1460 | |
|
1461 | 0 | if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames)) |
1462 | 0 | return zone; |
1463 | 0 | } |
1464 | | |
1465 | 0 | return NULL; |
1466 | 0 | } |
1467 | | |
1468 | | icaltimezone *icaltimezone_get_builtin_timezone_from_tzid(const char *tzid) |
1469 | 0 | { |
1470 | 0 | const char *p, *zone_tzid, *tzid_prefix; |
1471 | 0 | icaltimezone *zone; |
1472 | 0 | int compat = 0; |
1473 | |
|
1474 | 0 | if (!tzid || !tzid[0]) |
1475 | 0 | return NULL; |
1476 | | |
1477 | 0 | if (strcmp(tzid, "UTC") == 0 || strcmp(tzid, "GMT") == 0) { |
1478 | 0 | return icaltimezone_get_builtin_timezone(tzid); |
1479 | 0 | } |
1480 | | |
1481 | 0 | tzid_prefix = icaltimezone_tzid_prefix(); |
1482 | | /* Check that the TZID starts with our unique prefix. */ |
1483 | 0 | if (strncmp(tzid, tzid_prefix, strlen(tzid_prefix))) { |
1484 | 0 | int ii; |
1485 | |
|
1486 | 0 | for (ii = 0; glob_compat_tzids[ii].tzid; ii++) { |
1487 | 0 | if(strncmp(tzid, glob_compat_tzids[ii].tzid, strlen(glob_compat_tzids[ii].tzid)) == 0) { |
1488 | 0 | p = skip_slashes(tzid, glob_compat_tzids[ii].slashes); |
1489 | 0 | if(p) { |
1490 | 0 | zone = icaltimezone_get_builtin_timezone(p); |
1491 | | /* Do not recheck the TZID matches exactly, it does not, because |
1492 | | fallbacking with the compatibility timezone prefix here. */ |
1493 | 0 | return zone; |
1494 | 0 | } |
1495 | 0 | break; |
1496 | 0 | } |
1497 | 0 | } |
1498 | | |
1499 | 0 | return NULL; |
1500 | 0 | } |
1501 | | |
1502 | | /* Skip past our prefix */ |
1503 | 0 | p = tzid + strlen(tzid_prefix); |
1504 | | |
1505 | | /* Special-case "/freeassociation.sourceforge.net/Tzfile/" |
1506 | | because it shares prefix with BUILTIN_TZID_PREFIX */ |
1507 | 0 | if (strcmp(tzid_prefix, BUILTIN_TZID_PREFIX) == 0 && |
1508 | 0 | strncmp(p, "Tzfile/", 7) == 0) { |
1509 | 0 | p += 7; |
1510 | 0 | compat = 1; |
1511 | 0 | } |
1512 | | |
1513 | | /* Now we can use the function to get the builtin timezone from the |
1514 | | location string. */ |
1515 | 0 | zone = icaltimezone_get_builtin_timezone(p); |
1516 | 0 | if (!zone || compat) |
1517 | 0 | return zone; |
1518 | | |
1519 | | #if defined(USE_BUILTIN_TZDATA) |
1520 | | if (use_builtin_tzdata) |
1521 | | return zone; |
1522 | | #endif |
1523 | | |
1524 | | /* Check that the builtin TZID matches exactly. We don't want to return |
1525 | | a different version of the VTIMEZONE. */ |
1526 | 0 | zone_tzid = icaltimezone_get_tzid(zone); |
1527 | 0 | if (!strcmp(zone_tzid, tzid)) { |
1528 | 0 | return zone; |
1529 | 0 | } else { |
1530 | 0 | return NULL; |
1531 | 0 | } |
1532 | 0 | } |
1533 | | |
1534 | | icaltimezone *icaltimezone_get_utc_timezone(void) |
1535 | 454 | { |
1536 | 454 | if (!builtin_timezones) |
1537 | 1 | icaltimezone_init_builtin_timezones(); |
1538 | | |
1539 | 454 | return &utc_timezone; |
1540 | 454 | } |
1541 | | |
1542 | | /** @brief Initializes the builtin timezone data, i.e. the |
1543 | | * builtin_timezones array and the special UTC timezone. |
1544 | | * |
1545 | | * It should be called before any code that uses the timezone functions. |
1546 | | */ |
1547 | | static void icaltimezone_init_builtin_timezones(void) |
1548 | 1 | { |
1549 | | /* Initialize the special UTC timezone. */ |
1550 | 1 | utc_timezone.tzid = (char *)"UTC"; |
1551 | | |
1552 | 1 | icaltimezone_builtin_lock(); |
1553 | 1 | if (!builtin_timezones) { |
1554 | 1 | icaltimezone_parse_zone_tab(); |
1555 | 1 | } |
1556 | 1 | icaltimezone_builtin_unlock(); |
1557 | 1 | } |
1558 | | |
1559 | | static int parse_coord(char *coord, int len, int *degrees, int *minutes, int *seconds) |
1560 | 838 | { |
1561 | 838 | if (len == 5) { |
1562 | 365 | sscanf(coord + 1, "%2d%2d", degrees, minutes); |
1563 | 473 | } else if (len == 6) { |
1564 | 365 | sscanf(coord + 1, "%3d%2d", degrees, minutes); |
1565 | 365 | } else if (len == 7) { |
1566 | 54 | sscanf(coord + 1, "%2d%2d%2d", degrees, minutes, seconds); |
1567 | 54 | } else if (len == 8) { |
1568 | 54 | sscanf(coord + 1, "%3d%2d%2d", degrees, minutes, seconds); |
1569 | 54 | } else { |
1570 | 0 | icalerrprintf("Invalid coordinate: %s\n", coord); |
1571 | 0 | return 1; |
1572 | 0 | } |
1573 | | |
1574 | 838 | if (coord[0] == '-') |
1575 | 312 | *degrees = -*degrees; |
1576 | | |
1577 | 838 | return 0; |
1578 | 838 | } |
1579 | | |
1580 | | static int fetch_lat_long_from_string(const char *str, |
1581 | | int *latitude_degrees, int *latitude_minutes, |
1582 | | int *latitude_seconds, |
1583 | | int *longitude_degrees, int *longitude_minutes, |
1584 | | int *longitude_seconds, |
1585 | | char *location) |
1586 | 419 | { |
1587 | 419 | size_t len; |
1588 | 419 | char *sptr, *lat, *lon, *loc, *temp; |
1589 | | |
1590 | | /* We need to parse the latitude/longitude coordinates and location fields */ |
1591 | 419 | sptr = (char *)str; |
1592 | 1.25k | while ((*sptr != '\t') && (*sptr != '\0')) { |
1593 | 838 | sptr++; |
1594 | 838 | } |
1595 | 419 | temp = ++sptr; |
1596 | 5.24k | while (*sptr != '\t' && *sptr != '\0') { |
1597 | 4.82k | sptr++; |
1598 | 4.82k | } |
1599 | 419 | len = (ptrdiff_t) (sptr - temp); |
1600 | 419 | lat = (char *)icalmemory_new_buffer(len + 1); |
1601 | 419 | memset(lat, '\0', len + 1); |
1602 | 419 | strncpy(lat, temp, len); |
1603 | 419 | lat[len] = '\0'; |
1604 | 419 | while ((*sptr != '\t') && (*sptr != '\0')) { |
1605 | 0 | sptr++; |
1606 | 0 | } |
1607 | 419 | loc = ++sptr; |
1608 | 6.90k | while (!isspace((int)(*sptr)) && (*sptr != '\0')) { |
1609 | 6.48k | sptr++; |
1610 | 6.48k | } |
1611 | 419 | len = (ptrdiff_t)(sptr - loc); |
1612 | 419 | strncpy(location, loc, len); |
1613 | 419 | location[len] = '\0'; |
1614 | | |
1615 | | #if defined(sun) && defined(__SVR4) |
1616 | | /* Handle EET, MET and WET in zone_sun.tab. */ |
1617 | | if (!strcmp(location, "Europe/")) { |
1618 | | while ((*sptr != '\t') && (*sptr != '\0')) { |
1619 | | sptr++; |
1620 | | } |
1621 | | loc = ++sptr; |
1622 | | while (!isspace(*sptr) && (*sptr != '\0')) { |
1623 | | sptr++; |
1624 | | } |
1625 | | len = (ptrdiff_t)(sptr - loc); |
1626 | | strncpy(location, loc, len); |
1627 | | location[len] = '\0'; |
1628 | | } |
1629 | | #endif |
1630 | | |
1631 | 419 | lon = lat + 1; |
1632 | 2.20k | while (*lon != '\0' && *lon != '+' && *lon != '-') { |
1633 | 1.78k | lon++; |
1634 | 1.78k | } |
1635 | | |
1636 | 419 | if (parse_coord(lat, (int)(lon - lat), |
1637 | 419 | latitude_degrees, |
1638 | 419 | latitude_minutes, |
1639 | 419 | latitude_seconds) == 1 || |
1640 | 419 | parse_coord(lon, (int)strlen(lon), |
1641 | 419 | longitude_degrees, longitude_minutes, longitude_seconds) == 1) { |
1642 | 0 | icalmemory_free_buffer(lat); |
1643 | 0 | return 1; |
1644 | 0 | } |
1645 | | |
1646 | 419 | icalmemory_free_buffer(lat); |
1647 | | |
1648 | 419 | return 0; |
1649 | 419 | } |
1650 | | |
1651 | | /** @brief Parses the zones.tab file containing the names and locations |
1652 | | * of the builtin timezones. |
1653 | | * |
1654 | | * It creates the builtin_timezones array |
1655 | | * which is an icalarray of icaltimezone structs. It only fills in the |
1656 | | * location, latitude and longtude fields; the rest are left |
1657 | | * blank. The VTIMEZONE component is loaded later if it is needed. The |
1658 | | * timezones in the zones.tab file are sorted by their name, which is |
1659 | | * useful for binary searches. |
1660 | | */ |
1661 | | static void icaltimezone_parse_zone_tab(void) |
1662 | 1 | { |
1663 | 1 | const char *zonedir, *zonetab; |
1664 | 1 | char *filename; |
1665 | 1 | FILE *fp; |
1666 | 1 | char buf[1024]; /* Used to store each line of zones.tab as it is read. */ |
1667 | 1 | char location[1024]; /* Stores the city name when parsing buf. */ |
1668 | 1 | size_t filename_len; |
1669 | 1 | int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0; |
1670 | 1 | int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0; |
1671 | 1 | icaltimezone zone; |
1672 | | |
1673 | 1 | icalerror_assert(builtin_timezones == NULL, "Parsing zones.tab file multiple times"); |
1674 | | |
1675 | 1 | builtin_timezones = icalarray_new(sizeof(icaltimezone), 1024); |
1676 | | |
1677 | 1 | if (!use_builtin_tzdata) { |
1678 | 1 | zonedir = icaltzutil_get_zone_directory(); |
1679 | 1 | zonetab = ZONES_TAB_SYSTEM_FILENAME; |
1680 | 1 | } else { |
1681 | 0 | zonedir = get_zone_directory_builtin(); |
1682 | 0 | zonetab = ZONES_TAB_FILENAME; |
1683 | 0 | } |
1684 | | |
1685 | 1 | filename_len = 0; |
1686 | 1 | if (zonedir) { |
1687 | 1 | filename_len = strlen(zonedir); |
1688 | 1 | } |
1689 | | |
1690 | 1 | icalerror_assert(filename_len > 0, "Unable to locate a zoneinfo dir"); |
1691 | 1 | if (filename_len == 0) { |
1692 | 0 | icalerror_set_errno(ICAL_INTERNAL_ERROR);\ |
1693 | 0 | return; |
1694 | 0 | } |
1695 | | |
1696 | 1 | filename_len += strlen(zonetab); |
1697 | 1 | filename_len += 2; /* for dir separator and final '\0' */ |
1698 | | |
1699 | 1 | filename = (char *)icalmemory_new_buffer(filename_len); |
1700 | 1 | if (!filename) { |
1701 | 0 | icalerror_set_errno(ICAL_NEWFAILED_ERROR); |
1702 | 0 | return; |
1703 | 0 | } |
1704 | 1 | snprintf(filename, filename_len, "%s/%s", zonedir, zonetab); |
1705 | | |
1706 | 1 | fp = fopen(filename, "r"); |
1707 | 1 | icalmemory_free_buffer(filename); |
1708 | 1 | icalerror_assert(fp, "Cannot open the zonetab file for reading"); |
1709 | 1 | if (!fp) { |
1710 | 0 | icalerror_set_errno(ICAL_INTERNAL_ERROR); |
1711 | 0 | return; |
1712 | 0 | } |
1713 | | |
1714 | 450 | while (fgets(buf, (int)sizeof(buf), fp)) { |
1715 | 449 | if (*buf == '#') |
1716 | 30 | continue; |
1717 | | |
1718 | 419 | if (use_builtin_tzdata) { |
1719 | | /* The format of each line is: "[ latitude longitude ] location". */ |
1720 | 0 | if (buf[0] != '+' && buf[0] != '-') { |
1721 | 0 | latitude_degrees = longitude_degrees = 360; |
1722 | 0 | latitude_minutes = longitude_minutes = 0; |
1723 | 0 | latitude_seconds = longitude_seconds = 0; |
1724 | 0 | if (sscanf(buf, "%1000s", location) != 1) { /*limit location to 1000chars */ |
1725 | | /*increase as needed */ |
1726 | | /*see location and buf declarations */ |
1727 | 0 | icalerrprintf("Invalid timezone description line: %s\n", buf); |
1728 | 0 | continue; |
1729 | 0 | } |
1730 | 0 | } else if (sscanf(buf, "%4d%2d%2d %4d%2d%2d %1000s", /*limit location to 1000chars */ |
1731 | | /*increase as needed */ |
1732 | | /*see location and buf declarations */ |
1733 | 0 | &latitude_degrees, &latitude_minutes, |
1734 | 0 | &latitude_seconds, |
1735 | 0 | &longitude_degrees, &longitude_minutes, |
1736 | 0 | &longitude_seconds, location) != 7) { |
1737 | 0 | icalerrprintf("Invalid timezone description line: %s\n", buf); |
1738 | 0 | continue; |
1739 | 0 | } |
1740 | 419 | } else { |
1741 | | /* coverity[tainted_data] */ |
1742 | 419 | if (fetch_lat_long_from_string(buf, &latitude_degrees, &latitude_minutes, |
1743 | 419 | &latitude_seconds, |
1744 | 419 | &longitude_degrees, &longitude_minutes, |
1745 | 419 | &longitude_seconds, location)) { |
1746 | 0 | icalerrprintf("Invalid timezone description line: %s\n", buf); |
1747 | 0 | continue; |
1748 | 0 | } |
1749 | 419 | } |
1750 | | |
1751 | 419 | icaltimezone_init(&zone); |
1752 | 419 | zone.location = icalmemory_strdup(location); |
1753 | | |
1754 | 419 | if (latitude_degrees >= 0) { |
1755 | 306 | zone.latitude = |
1756 | 306 | (double)latitude_degrees + |
1757 | 306 | (double)latitude_minutes / 60 + |
1758 | 306 | (double)latitude_seconds / 3600; |
1759 | 306 | } else { |
1760 | 113 | zone.latitude = |
1761 | 113 | (double)latitude_degrees - |
1762 | 113 | (double)latitude_minutes / 60 - |
1763 | 113 | (double)latitude_seconds / 3600; |
1764 | 113 | } |
1765 | | |
1766 | 419 | if (longitude_degrees >= 0) { |
1767 | 225 | zone.longitude = |
1768 | 225 | (double)longitude_degrees + |
1769 | 225 | (double)longitude_minutes / 60 + |
1770 | 225 | (double)longitude_seconds / 3600; |
1771 | 225 | } else { |
1772 | 194 | zone.longitude = |
1773 | 194 | (double)longitude_degrees - |
1774 | 194 | (double)longitude_minutes / 60 - |
1775 | 194 | (double)longitude_seconds / 3600; |
1776 | 194 | } |
1777 | | |
1778 | 419 | icalarray_append(builtin_timezones, &zone); |
1779 | | |
1780 | | #if 0 |
1781 | | printf("Found zone: %s %f %f\n", location, zone.latitude, zone.longitude); |
1782 | | #endif |
1783 | 419 | } |
1784 | | |
1785 | 1 | fclose(fp); |
1786 | 1 | } |
1787 | | |
1788 | | void icaltimezone_release_zone_tab(void) |
1789 | 0 | { |
1790 | 0 | size_t i; |
1791 | 0 | icalarray *mybuiltin_timezones = builtin_timezones; |
1792 | |
|
1793 | 0 | if (builtin_timezones == NULL) |
1794 | 0 | return; |
1795 | | |
1796 | 0 | builtin_timezones = NULL; |
1797 | 0 | for (i = 0; i < mybuiltin_timezones->num_elements; i++) { |
1798 | 0 | icalmemory_free_buffer(((icaltimezone *) icalarray_element_at(mybuiltin_timezones, i))->location); |
1799 | 0 | } |
1800 | 0 | icalarray_free(mybuiltin_timezones); |
1801 | 0 | } |
1802 | | |
1803 | | /** @brief Loads the builtin VTIMEZONE data for the given timezone. */ |
1804 | | static void icaltimezone_load_builtin_timezone(icaltimezone *zone) |
1805 | 572M | { |
1806 | 572M | icalcomponent *comp = 0, *subcomp; |
1807 | | |
1808 | | /* Prevent blocking on mutex lock caused by recursive calls */ |
1809 | 572M | if (zone->component) |
1810 | 572M | return; |
1811 | | |
1812 | 0 | icaltimezone_builtin_lock(); |
1813 | | |
1814 | | /* Try again, maybe it had been set by other thread while waiting for the lock */ |
1815 | 0 | if (zone->component) { |
1816 | 0 | icaltimezone_builtin_unlock(); |
1817 | 0 | return; |
1818 | 0 | } |
1819 | | |
1820 | | /* If the location isn't set, it isn't a builtin timezone. */ |
1821 | 0 | if (!zone->location || !zone->location[0]) { |
1822 | 0 | icaltimezone_builtin_unlock(); |
1823 | 0 | return; |
1824 | 0 | } |
1825 | | |
1826 | 0 | if (use_builtin_tzdata) { |
1827 | 0 | char *filename; |
1828 | 0 | size_t filename_len; |
1829 | 0 | FILE *fp; |
1830 | 0 | icalparser *parser; |
1831 | |
|
1832 | 0 | filename_len = strlen(get_zone_directory_builtin()) + strlen(zone->location) + 6; |
1833 | |
|
1834 | 0 | filename = (char *)icalmemory_new_buffer(filename_len); |
1835 | 0 | if (!filename) { |
1836 | 0 | icalerror_set_errno(ICAL_NEWFAILED_ERROR); |
1837 | 0 | goto out; |
1838 | 0 | } |
1839 | | |
1840 | 0 | snprintf(filename, filename_len, "%s/%s.ics", get_zone_directory_builtin(), zone->location); |
1841 | |
|
1842 | 0 | fp = fopen(filename, "r"); |
1843 | 0 | icalmemory_free_buffer(filename); |
1844 | 0 | if (!fp) { |
1845 | 0 | icalerror_set_errno(ICAL_FILE_ERROR); |
1846 | 0 | goto out; |
1847 | 0 | } |
1848 | | |
1849 | | /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100 |
1850 | | this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse' |
1851 | | icalerrprintf("** WARNING ** %s: %d %s\n", |
1852 | | __FILE__, __LINE__, icalerror_strerror(icalerrno)); |
1853 | | */ |
1854 | | |
1855 | 0 | parser = icalparser_new(); |
1856 | 0 | icalparser_set_gen_data(parser, fp); |
1857 | 0 | comp = icalparser_parse(parser, icaltimezone_load_get_line_fn); |
1858 | 0 | icalparser_free(parser); |
1859 | 0 | fclose(fp); |
1860 | | |
1861 | | /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */ |
1862 | 0 | subcomp = icalcomponent_get_first_component(comp, ICAL_VTIMEZONE_COMPONENT); |
1863 | |
|
1864 | 0 | if (subcomp) { |
1865 | 0 | icalproperty *prop; |
1866 | | |
1867 | | /* Ensure expected TZID */ |
1868 | 0 | prop = icalcomponent_get_first_property(subcomp, ICAL_TZID_PROPERTY); |
1869 | 0 | if(prop) { |
1870 | 0 | char *new_tzid; |
1871 | 0 | size_t new_tzid_len; |
1872 | 0 | const char *tzid_prefix = icaltimezone_tzid_prefix(); |
1873 | |
|
1874 | 0 | new_tzid_len = strlen(tzid_prefix) + strlen(zone->location) + 1; |
1875 | 0 | new_tzid = (char *)icalmemory_new_buffer(sizeof(char) * (new_tzid_len + 1)); |
1876 | 0 | if(new_tzid) { |
1877 | 0 | snprintf(new_tzid, new_tzid_len, "%s%s", tzid_prefix, zone->location); |
1878 | 0 | icalproperty_set_tzid(prop, new_tzid); |
1879 | 0 | icalmemory_free_buffer(new_tzid); |
1880 | 0 | } else { |
1881 | 0 | icalerror_set_errno(ICAL_NEWFAILED_ERROR); |
1882 | 0 | } |
1883 | 0 | } |
1884 | | |
1885 | | /* Ensure expected Location - it's for cases where one VTIMEZONE is shared |
1886 | | between different locations (like Pacific/Midway is Pacific/Pago_Pago). |
1887 | | This updates the properties, thus when the component is converted to |
1888 | | the string and back to the component the Location will still match. */ |
1889 | 0 | prop = icalcomponent_get_first_property(subcomp, ICAL_LOCATION_PROPERTY); |
1890 | 0 | if (prop) |
1891 | 0 | icalproperty_set_location(prop, zone->location); |
1892 | |
|
1893 | 0 | for (prop = icalcomponent_get_first_property(subcomp, ICAL_X_PROPERTY); |
1894 | 0 | prop; |
1895 | 0 | prop = icalcomponent_get_next_property(subcomp, ICAL_X_PROPERTY)) { |
1896 | 0 | const char *name; |
1897 | |
|
1898 | 0 | name = icalproperty_get_x_name(prop); |
1899 | 0 | if (name && !strcasecmp(name, "X-LIC-LOCATION")) { |
1900 | 0 | icalproperty_set_x(prop, zone->location); |
1901 | 0 | break; |
1902 | 0 | } |
1903 | 0 | } |
1904 | 0 | } |
1905 | 0 | } else { |
1906 | 0 | subcomp = icaltzutil_fetch_timezone(zone->location); |
1907 | 0 | } |
1908 | | |
1909 | 0 | if (!subcomp) { |
1910 | 0 | icalerror_set_errno(ICAL_PARSE_ERROR); |
1911 | 0 | goto out; |
1912 | 0 | } |
1913 | | |
1914 | 0 | icaltimezone_get_vtimezone_properties(zone, subcomp); |
1915 | |
|
1916 | 0 | if (use_builtin_tzdata) { |
1917 | 0 | icalcomponent_remove_component(comp, subcomp); |
1918 | 0 | icalcomponent_free(comp); |
1919 | 0 | } |
1920 | |
|
1921 | 0 | out: |
1922 | 0 | icaltimezone_builtin_unlock(); |
1923 | 0 | return; |
1924 | 0 | } |
1925 | | |
1926 | | /** @brief Callback used from icalparser_parse() */ |
1927 | | static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data) |
1928 | 0 | { |
1929 | 0 | return fgets(s, (int)size, (FILE *) data); |
1930 | 0 | } |
1931 | | |
1932 | | /* |
1933 | | * DEBUGGING |
1934 | | */ |
1935 | | |
1936 | | int icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp) |
1937 | 0 | { |
1938 | 0 | static const char months[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
1939 | 0 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
1940 | 0 | }; |
1941 | 0 | icaltimezonechange *zone_change; |
1942 | 0 | size_t change_num; |
1943 | 0 | char buffer[8]; |
1944 | | |
1945 | | /* Make sure the changes array is expanded up to the given time. */ |
1946 | 0 | icaltimezone_ensure_coverage(zone, max_year); |
1947 | |
|
1948 | | #if 0 |
1949 | | printf("Num changes: %i\n", zone->changes->num_elements); |
1950 | | #endif |
1951 | |
|
1952 | 0 | icaltimezone_changes_lock(); |
1953 | |
|
1954 | 0 | for (change_num = 0; change_num < zone->changes->num_elements; change_num++) { |
1955 | 0 | zone_change = icalarray_element_at(zone->changes, change_num); |
1956 | |
|
1957 | 0 | if (zone_change->year > max_year) |
1958 | 0 | break; |
1959 | | |
1960 | 0 | fprintf(fp, "%s\t%2i %s %04i\t%2i:%02i:%02i", |
1961 | 0 | zone->location, |
1962 | 0 | zone_change->day, months[zone_change->month - 1], |
1963 | 0 | zone_change->year, zone_change->hour, zone_change->minute, zone_change->second); |
1964 | | |
1965 | | /* Wall Clock Time offset from UTC. */ |
1966 | 0 | format_utc_offset(zone_change->utc_offset, buffer, sizeof(buffer)); |
1967 | 0 | fprintf(fp, "\t%s", buffer); |
1968 | |
|
1969 | 0 | fprintf(fp, "\n"); |
1970 | 0 | } |
1971 | |
|
1972 | 0 | icaltimezone_changes_unlock(); |
1973 | |
|
1974 | 0 | return 1; |
1975 | 0 | } |
1976 | | |
1977 | | /** @brief Formats a UTC offset as "+HHMM" or "+HHMMSS". |
1978 | | * |
1979 | | * @p buffer should have space for 8 characters. */ |
1980 | | static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size) |
1981 | 0 | { |
1982 | 0 | const char *sign = "+"; |
1983 | 0 | int hours, minutes, seconds; |
1984 | |
|
1985 | 0 | if (utc_offset < 0) { |
1986 | 0 | utc_offset = -utc_offset; |
1987 | 0 | sign = "-"; |
1988 | 0 | } |
1989 | |
|
1990 | 0 | hours = utc_offset / 3600; |
1991 | 0 | minutes = (utc_offset % 3600) / 60; |
1992 | 0 | seconds = utc_offset % 60; |
1993 | | |
1994 | | /* Sanity check. Standard timezone offsets shouldn't be much more than 12 |
1995 | | hours, and daylight saving shouldn't change it by more than a few hours. |
1996 | | (The maximum offset is 15 hours 56 minutes at present.) */ |
1997 | 0 | if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60) { |
1998 | 0 | icalerrprintf("Warning: Strange timezone offset: H:%i M:%i S:%i\n", |
1999 | 0 | hours, minutes, seconds); |
2000 | 0 | } |
2001 | |
|
2002 | 0 | if (seconds == 0) { |
2003 | 0 | snprintf(buffer, buffer_size, "%s%02i%02i", sign, hours, minutes); |
2004 | 0 | } else { |
2005 | 0 | snprintf(buffer, buffer_size, "%s%02i%02i%02i", sign, hours, minutes, seconds); |
2006 | 0 | } |
2007 | 0 | } |
2008 | | |
2009 | | static const char *get_zone_directory_builtin(void) |
2010 | 0 | { |
2011 | 0 | #if !defined(_WIN32) |
2012 | 0 | return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory; |
2013 | | #else |
2014 | | wchar_t wbuffer[1000]; |
2015 | | |
2016 | | #if !defined(_WIN32_WCE) |
2017 | | char buffer[1000], zoneinfodir[1000], dirname[1000]; |
2018 | | #else |
2019 | | wchar_t zoneinfodir[1000], dirname[1000]; |
2020 | | #endif |
2021 | | int used_default; |
2022 | | static ICAL_GLOBAL_VAR char *cache = NULL; |
2023 | | |
2024 | | #if !defined(_WIN32_WCE) |
2025 | | unsigned char *dirslash, *zislash, *zislashp1; |
2026 | | #else |
2027 | | wchar_t *dirslash, *zislash; |
2028 | | #endif |
2029 | | struct stat st; |
2030 | | |
2031 | | if (zone_files_directory) |
2032 | | return zone_files_directory; |
2033 | | |
2034 | | if (cache) |
2035 | | return cache; |
2036 | | |
2037 | | /* Get the filename of the application */ |
2038 | | if (!GetModuleFileNameW(NULL, wbuffer, sizeof(wbuffer) / sizeof(wbuffer[0]))) |
2039 | | return ZONEINFO_DIRECTORY; |
2040 | | |
2041 | | /*wince supports only unicode*/ |
2042 | | #if !defined(_WIN32_WCE) |
2043 | | /* Convert to system codepage */ |
2044 | | if (!WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer), |
2045 | | NULL, &used_default) || used_default) { |
2046 | | /* Failed, try 8.3 format */ |
2047 | | if (!GetShortPathNameW(wbuffer, wbuffer, |
2048 | | sizeof(wbuffer) / sizeof(wbuffer[0])) || |
2049 | | !WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer), |
2050 | | NULL, &used_default) || used_default) { |
2051 | | return ZONEINFO_DIRECTORY; |
2052 | | } |
2053 | | } |
2054 | | #endif |
2055 | | /* Look for the zoneinfo directory somewhere in the path where |
2056 | | * the app is installed. If the path to the app is |
2057 | | * |
2058 | | * C:\opt\evo-2.6\bin\evolution-2.6.exe |
2059 | | * |
2060 | | * and the compile-time ZONEINFO_DIRECTORY is |
2061 | | * |
2062 | | * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo, |
2063 | | * |
2064 | | * we check the pathnames: |
2065 | | * |
2066 | | * C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo |
2067 | | * C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo |
2068 | | * C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo |
2069 | | * C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo <=== |
2070 | | * C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo |
2071 | | * C:\opt\evo-2.6/zoneinfo |
2072 | | * C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo |
2073 | | * C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo |
2074 | | * C:\opt/evo/share/evolution-data-server-1.6/zoneinfo |
2075 | | * C:\opt/share/evolution-data-server-1.6/zoneinfo |
2076 | | * C:\opt/evolution-data-server-1.6/zoneinfo |
2077 | | * C:\opt/zoneinfo |
2078 | | * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo |
2079 | | * C:/target/evo/share/evolution-data-server-1.6/zoneinfo |
2080 | | * C:/evo/share/evolution-data-server-1.6/zoneinfo |
2081 | | * C:/share/evolution-data-server-1.6/zoneinfo |
2082 | | * C:/evolution-data-server-1.6/zoneinfo |
2083 | | * C:/zoneinfo |
2084 | | * |
2085 | | * In Evolution's case, we would get a match already at the |
2086 | | * fourth pathname check. |
2087 | | */ |
2088 | | |
2089 | | /* Strip away basename of app .exe first */ |
2090 | | #if !defined(_WIN32_WCE) |
2091 | | dirslash = _mbsrchr((unsigned char *)buffer, '\\'); |
2092 | | #else |
2093 | | dirslash = wcsrchr(wbuffer, L'\\'); |
2094 | | #endif |
2095 | | if (dirslash) { |
2096 | | #if !defined(_WIN32_WCE) |
2097 | | *dirslash = '\0'; |
2098 | | #else |
2099 | | *dirslash = L'\0'; |
2100 | | #endif |
2101 | | } |
2102 | | |
2103 | | #if defined(_WIN32_WCE) |
2104 | | while ((dirslash = wcsrchr(wbuffer, '\\'))) { |
2105 | | /* Strip one more directory from app .exe location */ |
2106 | | *dirslash = L'\0'; |
2107 | | |
2108 | | MultiByteToWideChar(CP_ACP, 0, ZONEINFO_DIRECTORY, -1, zoneinfodir, 1000); |
2109 | | |
2110 | | while ((zislash = wcschr(zoneinfodir, L'/'))) { |
2111 | | *zislash = L'.'; |
2112 | | wcscpy(dirname, wbuffer); |
2113 | | wcscat(dirname, "/"); |
2114 | | wcscat(dirname, zislash + 1); |
2115 | | if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) { |
2116 | | cache = wce_wctomb(dirname); |
2117 | | return cache; |
2118 | | } |
2119 | | } |
2120 | | } |
2121 | | #else |
2122 | | while ((dirslash = _mbsrchr((unsigned char *)buffer, '\\'))) { |
2123 | | /* Strip one more directory from app .exe location */ |
2124 | | *dirslash = '\0'; |
2125 | | |
2126 | | strcpy(zoneinfodir, ZONEINFO_DIRECTORY); |
2127 | | while ((zislash = _mbschr((unsigned char *)zoneinfodir, '/'))) { |
2128 | | *zislash = '.'; |
2129 | | strcpy(dirname, buffer); |
2130 | | strcat(dirname, "/"); |
2131 | | zislashp1 = zislash + 1; |
2132 | | strcat(dirname, (char *)zislashp1); |
2133 | | if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) { |
2134 | | cache = icalmemory_strdup(dirname); |
2135 | | return cache; |
2136 | | } |
2137 | | } |
2138 | | } |
2139 | | #endif |
2140 | | return ZONEINFO_DIRECTORY; |
2141 | | #endif |
2142 | 0 | } |
2143 | | |
2144 | | const char *get_zone_directory(void) |
2145 | 0 | { |
2146 | 0 | if (use_builtin_tzdata) { |
2147 | 0 | return get_zone_directory_builtin(); |
2148 | 0 | } else { |
2149 | 0 | return icaltzutil_get_zone_directory(); |
2150 | 0 | } |
2151 | 0 | } |
2152 | | |
2153 | | void set_zone_directory(const char *path) |
2154 | 0 | { |
2155 | 0 | if (zone_files_directory) |
2156 | 0 | free_zone_directory(); |
2157 | |
|
2158 | 0 | zone_files_directory = icalmemory_new_buffer(strlen(path) + 1); |
2159 | |
|
2160 | 0 | if (zone_files_directory != NULL) |
2161 | 0 | strcpy(zone_files_directory, path); |
2162 | 0 | } |
2163 | | |
2164 | | void free_zone_directory(void) |
2165 | 0 | { |
2166 | 0 | if (zone_files_directory != NULL) { |
2167 | 0 | icalmemory_free_buffer(zone_files_directory); |
2168 | 0 | zone_files_directory = NULL; |
2169 | 0 | } |
2170 | 0 | } |
2171 | | |
2172 | | void icaltimezone_set_tzid_prefix(const char *new_prefix) |
2173 | 0 | { |
2174 | 0 | if (new_prefix) { |
2175 | 0 | strncpy(s_ical_tzid_prefix, new_prefix, BUILTIN_TZID_PREFIX_LEN-1); |
2176 | 0 | } |
2177 | 0 | } |
2178 | | |
2179 | | void icaltimezone_set_builtin_tzdata(int set) |
2180 | 0 | { |
2181 | 0 | use_builtin_tzdata = set; |
2182 | 0 | } |
2183 | | |
2184 | | int icaltimezone_get_builtin_tzdata(void) |
2185 | 0 | { |
2186 | 0 | return use_builtin_tzdata; |
2187 | 0 | } |
2188 | | |
2189 | | struct observance { |
2190 | | const char *name; |
2191 | | icaltimetype onset; |
2192 | | int offset_from; |
2193 | | int offset_to; |
2194 | | }; |
2195 | | |
2196 | | static void check_tombstone(struct observance *tombstone, |
2197 | | struct observance *obs) |
2198 | 0 | { |
2199 | 0 | if (icaltime_compare(obs->onset, tombstone->onset) > 0) { |
2200 | | /* onset is closer to cutoff than existing tombstone */ |
2201 | 0 | tombstone->name = icalmemory_tmp_copy(obs->name); |
2202 | 0 | tombstone->offset_from = tombstone->offset_to = obs->offset_to; |
2203 | 0 | tombstone->onset = obs->onset; |
2204 | 0 | } |
2205 | 0 | } |
2206 | | |
2207 | | struct rdate { |
2208 | | icalproperty *prop; |
2209 | | struct icaldatetimeperiodtype date; |
2210 | | }; |
2211 | | |
2212 | | static int rdate_compare(const void *rdate1, const void *rdate2) |
2213 | 0 | { |
2214 | 0 | return icaltime_compare(((struct rdate *) rdate1)->date.time, |
2215 | 0 | ((struct rdate *) rdate2)->date.time); |
2216 | 0 | } |
2217 | | |
2218 | | void icaltimezone_truncate_vtimezone(icalcomponent *vtz, |
2219 | | icaltimetype start, icaltimetype end, |
2220 | | int ms_compatible) |
2221 | 0 | { |
2222 | 0 | icalcomponent *comp, *nextc, *tomb_std = NULL, *tomb_day = NULL; |
2223 | 0 | icalproperty *prop, *proleptic_prop = NULL; |
2224 | 0 | struct observance tombstone; |
2225 | 0 | unsigned need_tomb = (unsigned)!icaltime_is_null_time(start); |
2226 | 0 | unsigned need_tzuntil = (unsigned)!icaltime_is_null_time(end); |
2227 | |
|
2228 | 0 | if (!need_tomb && !need_tzuntil) { |
2229 | | /* Nothing to do */ |
2230 | 0 | return; |
2231 | 0 | } |
2232 | | |
2233 | | /* See if we have a proleptic tzname in VTIMEZONE */ |
2234 | 0 | for (prop = icalcomponent_get_first_property(vtz, ICAL_X_PROPERTY); |
2235 | 0 | prop; |
2236 | 0 | prop = icalcomponent_get_next_property(vtz, ICAL_X_PROPERTY)) { |
2237 | 0 | if (!strcmp("X-PROLEPTIC-TZNAME", icalproperty_get_x_name(prop))) { |
2238 | 0 | proleptic_prop = prop; |
2239 | 0 | break; |
2240 | 0 | } |
2241 | 0 | } |
2242 | |
|
2243 | 0 | memset(&tombstone, 0, sizeof(struct observance)); |
2244 | 0 | tombstone.name = icalmemory_tmp_copy(proleptic_prop ? |
2245 | 0 | icalproperty_get_x(proleptic_prop) : |
2246 | 0 | "LMT"); |
2247 | 0 | if (!proleptic_prop || |
2248 | 0 | !icalproperty_get_parameter_as_string(proleptic_prop, "X-NO-BIG-BANG")) { |
2249 | 0 | tombstone.onset.year = -1; |
2250 | 0 | } |
2251 | | |
2252 | | /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */ |
2253 | 0 | for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT); |
2254 | 0 | comp; comp = nextc) { |
2255 | 0 | icalproperty *dtstart_prop = NULL, *rrule_prop = NULL; |
2256 | 0 | icalarray *rdates = icalarray_new(sizeof(struct rdate), 10); |
2257 | 0 | icaltimetype dtstart; |
2258 | 0 | struct observance obs; |
2259 | 0 | size_t n; |
2260 | 0 | unsigned trunc_dtstart = 0; |
2261 | 0 | int r; |
2262 | |
|
2263 | 0 | nextc = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT); |
2264 | |
|
2265 | 0 | memset(&obs, 0, sizeof(struct observance)); |
2266 | 0 | obs.offset_from = obs.offset_to = INT_MAX; |
2267 | 0 | obs.onset.is_daylight = |
2268 | 0 | (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT); |
2269 | | |
2270 | | /* Grab the properties that we require to expand recurrences */ |
2271 | 0 | for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); |
2272 | 0 | prop; |
2273 | 0 | prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) { |
2274 | |
|
2275 | 0 | switch (icalproperty_isa(prop)) { |
2276 | 0 | case ICAL_TZNAME_PROPERTY: |
2277 | 0 | obs.name = icalproperty_get_tzname(prop); |
2278 | 0 | break; |
2279 | | |
2280 | 0 | case ICAL_DTSTART_PROPERTY: |
2281 | 0 | dtstart_prop = prop; |
2282 | 0 | obs.onset = dtstart = icalproperty_get_dtstart(prop); |
2283 | 0 | break; |
2284 | | |
2285 | 0 | case ICAL_TZOFFSETFROM_PROPERTY: |
2286 | 0 | obs.offset_from = icalproperty_get_tzoffsetfrom(prop); |
2287 | 0 | break; |
2288 | | |
2289 | 0 | case ICAL_TZOFFSETTO_PROPERTY: |
2290 | 0 | obs.offset_to = icalproperty_get_tzoffsetto(prop); |
2291 | 0 | break; |
2292 | | |
2293 | 0 | case ICAL_RRULE_PROPERTY: |
2294 | 0 | rrule_prop = prop; |
2295 | 0 | break; |
2296 | | |
2297 | 0 | case ICAL_RDATE_PROPERTY: { |
2298 | 0 | struct icaldatetimeperiodtype dtp = icalproperty_get_rdate(prop); |
2299 | 0 | struct rdate rdate; |
2300 | |
|
2301 | 0 | rdate.prop = prop; |
2302 | 0 | rdate.date.time = dtp.time; |
2303 | 0 | rdate.date.period = dtp.period; |
2304 | |
|
2305 | 0 | icalarray_append(rdates, &rdate); |
2306 | 0 | break; |
2307 | 0 | } |
2308 | | |
2309 | 0 | default: |
2310 | | /* ignore all other properties */ |
2311 | 0 | break; |
2312 | 0 | } |
2313 | 0 | } |
2314 | | |
2315 | | /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */ |
2316 | 0 | if (!dtstart_prop || !obs.name || |
2317 | 0 | obs.offset_from == INT_MAX || obs.offset_to == INT_MAX) { |
2318 | 0 | icalarray_free(rdates); |
2319 | 0 | continue; |
2320 | 0 | } |
2321 | | |
2322 | | /* Adjust DTSTART observance to UTC */ |
2323 | 0 | icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); |
2324 | 0 | (void)icaltime_set_timezone(&obs.onset, icaltimezone_get_utc_timezone()); |
2325 | | |
2326 | | /* Check DTSTART vs window close */ |
2327 | 0 | if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) { |
2328 | | /* All observances occur on/after window close - remove component */ |
2329 | 0 | icalcomponent_remove_component(vtz, comp); |
2330 | 0 | icalcomponent_free(comp); |
2331 | | |
2332 | | /* Nothing else to do */ |
2333 | 0 | icalarray_free(rdates); |
2334 | 0 | continue; |
2335 | 0 | } |
2336 | | |
2337 | | /* Check DTSTART vs window open */ |
2338 | 0 | r = icaltime_compare(obs.onset, start); |
2339 | 0 | if (r < 0) { |
2340 | | /* DTSTART is prior to our window open - check it vs tombstone */ |
2341 | 0 | if (need_tomb) { |
2342 | 0 | check_tombstone(&tombstone, &obs); |
2343 | 0 | } |
2344 | | |
2345 | | /* Adjust it */ |
2346 | 0 | trunc_dtstart = 1; |
2347 | 0 | } else if (r == 0) { |
2348 | | /* DTSTART is on/after our window open */ |
2349 | 0 | need_tomb = 0; |
2350 | 0 | } |
2351 | |
|
2352 | 0 | if (rrule_prop) { |
2353 | 0 | struct icalrecurrencetype rrule = icalproperty_get_rrule(rrule_prop); |
2354 | 0 | unsigned eternal = (unsigned)icaltime_is_null_time(rrule.until); |
2355 | 0 | icalrecur_iterator *ritr = NULL; |
2356 | 0 | unsigned trunc_until = 0; |
2357 | | |
2358 | | /* Check RRULE duration */ |
2359 | 0 | if (!eternal && icaltime_compare(rrule.until, start) < 0) { |
2360 | | /* RRULE ends prior to our window open - |
2361 | | check UNTIL vs tombstone */ |
2362 | 0 | obs.onset = rrule.until; |
2363 | 0 | if (need_tomb) { |
2364 | 0 | check_tombstone(&tombstone, &obs); |
2365 | 0 | } |
2366 | | |
2367 | | /* Remove RRULE */ |
2368 | 0 | icalcomponent_remove_property(comp, rrule_prop); |
2369 | 0 | icalproperty_free(rrule_prop); |
2370 | 0 | } else { |
2371 | | /* RRULE ends on/after our window open */ |
2372 | 0 | if (need_tzuntil && |
2373 | 0 | (eternal || icaltime_compare(rrule.until, end) >= 0)) { |
2374 | | /* RRULE ends after our window close - need to adjust it */ |
2375 | 0 | trunc_until = 1; |
2376 | 0 | } |
2377 | |
|
2378 | 0 | if (!eternal) { |
2379 | | /* Adjust UNTIL to local time (for iterator) */ |
2380 | 0 | icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from); |
2381 | 0 | (void)icaltime_set_timezone(&rrule.until, NULL); |
2382 | 0 | } |
2383 | |
|
2384 | 0 | ritr = icalrecur_iterator_new(rrule, dtstart); |
2385 | |
|
2386 | 0 | if (trunc_dtstart) { |
2387 | | /* Bump RRULE start to 1 year prior to our window open */ |
2388 | 0 | icaltimetype newstart = dtstart; |
2389 | 0 | newstart.year = start.year - 1; |
2390 | 0 | newstart.month = start.month; |
2391 | 0 | newstart.day = start.day; |
2392 | 0 | (void)icaltime_normalize(newstart); |
2393 | 0 | icalrecur_iterator_set_start(ritr, newstart); |
2394 | 0 | } |
2395 | 0 | } |
2396 | | |
2397 | | /* Process any RRULE observances within our window */ |
2398 | 0 | if (ritr) { |
2399 | 0 | icaltimetype recur, prev_onset; |
2400 | |
|
2401 | 0 | while (!icaltime_is_null_time(recur = icalrecur_iterator_next(ritr))) { |
2402 | 0 | unsigned ydiff; |
2403 | |
|
2404 | 0 | obs.onset = recur; |
2405 | | |
2406 | | /* Adjust observance to UTC */ |
2407 | 0 | icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); |
2408 | 0 | (void)icaltime_set_timezone(&obs.onset, |
2409 | 0 | icaltimezone_get_utc_timezone()); |
2410 | |
|
2411 | 0 | if (trunc_until && icaltime_compare(obs.onset, end) >= 0) { |
2412 | | /* Observance is on/after window close */ |
2413 | | |
2414 | | /* Check if DSTART is within 1yr of prev onset */ |
2415 | 0 | ydiff = (unsigned)(prev_onset.year - dtstart.year); |
2416 | 0 | if (ydiff <= 1) { |
2417 | | /* Remove RRULE */ |
2418 | 0 | icalcomponent_remove_property(comp, rrule_prop); |
2419 | 0 | icalproperty_free(rrule_prop); |
2420 | |
|
2421 | 0 | if (ydiff) { |
2422 | | /* Add previous onset as RDATE */ |
2423 | 0 | struct icaldatetimeperiodtype rdate; |
2424 | 0 | rdate.time = prev_onset; |
2425 | 0 | rdate.period = icalperiodtype_null_period(); |
2426 | |
|
2427 | 0 | prop = icalproperty_new_rdate(rdate); |
2428 | 0 | icalcomponent_add_property(comp, prop); |
2429 | 0 | } |
2430 | 0 | } else { |
2431 | | /* Set UNTIL to previous onset */ |
2432 | 0 | rrule.until = prev_onset; |
2433 | 0 | icalproperty_set_rrule(rrule_prop, rrule); |
2434 | 0 | } |
2435 | | |
2436 | | /* We're done */ |
2437 | 0 | break; |
2438 | 0 | } |
2439 | | |
2440 | | /* Check observance vs our window open */ |
2441 | 0 | r = icaltime_compare(obs.onset, start); |
2442 | 0 | if (r < 0) { |
2443 | | /* Observance is prior to our window open - |
2444 | | check it vs tombstone */ |
2445 | 0 | if (ms_compatible) { |
2446 | | /* XXX We don't want to move DTSTART of the RRULE |
2447 | | as Outlook/Exchange doesn't appear to like |
2448 | | truncating the frontend of RRULEs */ |
2449 | 0 | need_tomb = 0; |
2450 | 0 | trunc_dtstart = 0; |
2451 | 0 | if (proleptic_prop) { |
2452 | 0 | icalcomponent_remove_property(vtz, |
2453 | 0 | proleptic_prop); |
2454 | 0 | icalproperty_free(proleptic_prop); |
2455 | 0 | proleptic_prop = NULL; |
2456 | 0 | } |
2457 | 0 | } |
2458 | 0 | if (need_tomb) { |
2459 | 0 | check_tombstone(&tombstone, &obs); |
2460 | 0 | } |
2461 | 0 | } else { |
2462 | | /* Observance is on/after our window open */ |
2463 | 0 | if (r == 0) need_tomb = 0; |
2464 | |
|
2465 | 0 | if (trunc_dtstart) { |
2466 | | /* Make this observance the new DTSTART */ |
2467 | 0 | icalproperty_set_dtstart(dtstart_prop, recur); |
2468 | 0 | dtstart = obs.onset; |
2469 | 0 | trunc_dtstart = 0; |
2470 | | |
2471 | | /* Check if new DSTART is within 1yr of UNTIL */ |
2472 | 0 | ydiff = (unsigned)(rrule.until.year - recur.year); |
2473 | 0 | if (!trunc_until && ydiff <= 1) { |
2474 | | /* Remove RRULE */ |
2475 | 0 | icalcomponent_remove_property(comp, rrule_prop); |
2476 | 0 | icalproperty_free(rrule_prop); |
2477 | |
|
2478 | 0 | if (ydiff) { |
2479 | | /* Add UNTIL as RDATE */ |
2480 | 0 | struct icaldatetimeperiodtype rdate; |
2481 | 0 | rdate.time = rrule.until; |
2482 | 0 | rdate.period = icalperiodtype_null_period(); |
2483 | |
|
2484 | 0 | prop = icalproperty_new_rdate(rdate); |
2485 | 0 | icalcomponent_add_property(comp, prop); |
2486 | 0 | } |
2487 | 0 | } |
2488 | 0 | } |
2489 | |
|
2490 | 0 | if (!trunc_until) { |
2491 | | /* We're done */ |
2492 | 0 | break; |
2493 | 0 | } |
2494 | | |
2495 | | /* Check if observance is outside 1yr of window close */ |
2496 | 0 | ydiff = (unsigned)(end.year - recur.year); |
2497 | 0 | if (ydiff > 1) { |
2498 | | /* Bump RRULE to restart at 1 year prior to our window close */ |
2499 | 0 | icaltimetype newstart = recur; |
2500 | 0 | newstart.year = end.year - 1; |
2501 | 0 | newstart.month = end.month; |
2502 | 0 | newstart.day = end.day; |
2503 | 0 | (void)icaltime_normalize(newstart); |
2504 | 0 | icalrecur_iterator_set_start(ritr, newstart); |
2505 | 0 | } |
2506 | 0 | } |
2507 | 0 | prev_onset = obs.onset; |
2508 | 0 | } |
2509 | 0 | icalrecur_iterator_free(ritr); |
2510 | 0 | } |
2511 | 0 | } |
2512 | | |
2513 | | /* Sort the RDATEs by onset */ |
2514 | 0 | icalarray_sort(rdates, &rdate_compare); |
2515 | | |
2516 | | /* Check RDATEs */ |
2517 | 0 | for (n = 0; n < rdates->num_elements; n++) { |
2518 | 0 | struct rdate *rdate = icalarray_element_at(rdates, n); |
2519 | | |
2520 | | /* RDATEs with a DATE value inherit the time from the DTSTART. */ |
2521 | 0 | if (icaltime_is_date(rdate->date.time)) { |
2522 | 0 | rdate->date.time.hour = dtstart.hour; |
2523 | 0 | rdate->date.time.minute = dtstart.minute; |
2524 | 0 | rdate->date.time.second = dtstart.second; |
2525 | 0 | } |
2526 | |
|
2527 | 0 | if (n == 0 && icaltime_compare(rdate->date.time, dtstart) == 0) { |
2528 | | /* RDATE is same as DTSTART - remove it */ |
2529 | 0 | icalcomponent_remove_property(comp, rdate->prop); |
2530 | 0 | icalproperty_free(rdate->prop); |
2531 | 0 | continue; |
2532 | 0 | } |
2533 | | |
2534 | 0 | obs.onset = rdate->date.time; |
2535 | | |
2536 | | /* Adjust observance to UTC */ |
2537 | 0 | icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); |
2538 | 0 | (void)icaltime_set_timezone(&obs.onset, icaltimezone_get_utc_timezone()); |
2539 | |
|
2540 | 0 | if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) { |
2541 | | /* RDATE is after our window close - remove it */ |
2542 | 0 | icalcomponent_remove_property(comp, rdate->prop); |
2543 | 0 | icalproperty_free(rdate->prop); |
2544 | |
|
2545 | 0 | continue; |
2546 | 0 | } |
2547 | | |
2548 | 0 | r = icaltime_compare(obs.onset, start); |
2549 | 0 | if (r < 0) { |
2550 | | /* RDATE is prior to window open - check it vs tombstone */ |
2551 | 0 | if (need_tomb) { |
2552 | 0 | check_tombstone(&tombstone, &obs); |
2553 | 0 | } |
2554 | | |
2555 | | /* Remove it */ |
2556 | 0 | icalcomponent_remove_property(comp, rdate->prop); |
2557 | 0 | icalproperty_free(rdate->prop); |
2558 | 0 | } else { |
2559 | | /* RDATE is on/after our window open */ |
2560 | 0 | if (r == 0) need_tomb = 0; |
2561 | |
|
2562 | 0 | if (trunc_dtstart) { |
2563 | | /* Make this RDATE the new DTSTART */ |
2564 | 0 | icalproperty_set_dtstart(dtstart_prop, |
2565 | 0 | rdate->date.time); |
2566 | 0 | trunc_dtstart = 0; |
2567 | |
|
2568 | 0 | icalcomponent_remove_property(comp, rdate->prop); |
2569 | 0 | icalproperty_free(rdate->prop); |
2570 | 0 | } |
2571 | 0 | } |
2572 | 0 | } |
2573 | 0 | icalarray_free(rdates); |
2574 | | |
2575 | | /* Final check */ |
2576 | 0 | if (trunc_dtstart) { |
2577 | | /* All observances in comp occur prior to window open, remove it |
2578 | | unless we haven't saved a tombstone comp of this type yet */ |
2579 | 0 | if (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT) { |
2580 | 0 | if (!tomb_day) { |
2581 | 0 | tomb_day = comp; |
2582 | 0 | comp = NULL; |
2583 | 0 | } |
2584 | 0 | } |
2585 | 0 | else if (!tomb_std) { |
2586 | 0 | tomb_std = comp; |
2587 | 0 | comp = NULL; |
2588 | 0 | } |
2589 | |
|
2590 | 0 | if (comp) { |
2591 | 0 | icalcomponent_remove_component(vtz, comp); |
2592 | 0 | icalcomponent_free(comp); |
2593 | 0 | } |
2594 | 0 | } |
2595 | 0 | } |
2596 | | |
2597 | 0 | if (need_tomb && !icaltime_is_null_time(tombstone.onset)) { |
2598 | | /* Need to add tombstone component/observance starting at window open |
2599 | | as long as its not prior to start of TZ data */ |
2600 | 0 | icalcomponent *tomb; |
2601 | 0 | icalproperty *prop, *nextp; |
2602 | | |
2603 | | /* Determine which tombstone component we need */ |
2604 | 0 | if (tombstone.onset.is_daylight) { |
2605 | 0 | tomb = tomb_day; |
2606 | 0 | tomb_day = NULL; |
2607 | 0 | } else { |
2608 | 0 | tomb = tomb_std; |
2609 | 0 | tomb_std = NULL; |
2610 | 0 | } |
2611 | | |
2612 | | /* Set property values on our tombstone */ |
2613 | 0 | for (prop = icalcomponent_get_first_property(tomb, ICAL_ANY_PROPERTY); |
2614 | 0 | prop; prop = nextp) { |
2615 | |
|
2616 | 0 | nextp = icalcomponent_get_next_property(tomb, ICAL_ANY_PROPERTY); |
2617 | |
|
2618 | 0 | switch (icalproperty_isa(prop)) { |
2619 | 0 | case ICAL_TZNAME_PROPERTY: |
2620 | 0 | icalproperty_set_tzname(prop, tombstone.name); |
2621 | 0 | break; |
2622 | 0 | case ICAL_TZOFFSETFROM_PROPERTY: |
2623 | 0 | icalproperty_set_tzoffsetfrom(prop, tombstone.offset_from); |
2624 | 0 | break; |
2625 | 0 | case ICAL_TZOFFSETTO_PROPERTY: |
2626 | 0 | icalproperty_set_tzoffsetto(prop, tombstone.offset_to); |
2627 | 0 | break; |
2628 | 0 | case ICAL_DTSTART_PROPERTY: |
2629 | | /* Adjust window open to local time */ |
2630 | 0 | icaltime_adjust(&start, 0, 0, 0, tombstone.offset_from); |
2631 | 0 | (void)icaltime_set_timezone(&start, NULL); |
2632 | |
|
2633 | 0 | icalproperty_set_dtstart(prop, start); |
2634 | 0 | break; |
2635 | 0 | default: |
2636 | 0 | icalcomponent_remove_property(tomb, prop); |
2637 | 0 | icalproperty_free(prop); |
2638 | 0 | break; |
2639 | 0 | } |
2640 | 0 | } |
2641 | | |
2642 | | /* Remove X-PROLEPTIC-TZNAME as it no longer applies */ |
2643 | 0 | if (proleptic_prop) { |
2644 | 0 | icalcomponent_remove_property(vtz, proleptic_prop); |
2645 | 0 | icalproperty_free(proleptic_prop); |
2646 | 0 | } |
2647 | 0 | } |
2648 | | |
2649 | | /* Remove any unused tombstone components */ |
2650 | 0 | if (tomb_std) { |
2651 | 0 | icalcomponent_remove_component(vtz, tomb_std); |
2652 | 0 | icalcomponent_free(tomb_std); |
2653 | 0 | } |
2654 | 0 | if (tomb_day) { |
2655 | 0 | icalcomponent_remove_component(vtz, tomb_day); |
2656 | 0 | icalcomponent_free(tomb_day); |
2657 | 0 | } |
2658 | |
|
2659 | 0 | if (need_tzuntil) { |
2660 | | /* Add TZUNTIL to VTIMEZONE */ |
2661 | 0 | prop = icalcomponent_get_first_property(vtz, ICAL_TZUNTIL_PROPERTY); |
2662 | |
|
2663 | 0 | if (prop) { |
2664 | 0 | icalproperty_set_tzuntil(prop, end); |
2665 | 0 | } else { |
2666 | 0 | icalcomponent_add_property(vtz, icalproperty_new_tzuntil(end)); |
2667 | 0 | } |
2668 | 0 | } |
2669 | 0 | } |