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