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