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 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 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 int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
613 int has_rdate = 0, has_rrule = 0;
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 /* Step through each of the properties to find the DTSTART,
626 TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
627 since we need these properties before we can do that. */
629 while (prop) {
630 switch (icalproperty_isa(prop)) {
632 dtstart = icalproperty_get_dtstart(prop);
633 found_dtstart = 1;
634 break;
636 change.utc_offset = icalproperty_get_tzoffsetto(prop);
637 /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset); */
638 found_tzoffsetto = 1;
639 break;
641 change.prev_utc_offset = icalproperty_get_tzoffsetfrom(prop);
642 /*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset); */
643 found_tzoffsetfrom = 1;
644 break;
646 has_rdate = 1;
647 break;
649 has_rrule = 1;
650 break;
651 default:
652 /* Just ignore any other properties. */
653 break;
654 }
655
657 }
658
659 /* Microsoft Outlook for Mac (and possibly other versions) will create
660 timezones without a tzoffsetfrom property if it's a timezone that
661 doesn't change for DST. */
662 if (found_tzoffsetto && !found_tzoffsetfrom) {
663 change.prev_utc_offset = change.utc_offset;
664 found_tzoffsetfrom = 1;
665 }
666
667 /* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
668 ignore the component. FIXME: Add an error property? */
669 if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom) {
670 return;
671 }
672
673#ifdef ICALTIMEZONE_DEBUG_PRINT
674 printf("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
675 dtstart.year, dtstart.month, dtstart.day, dtstart.hour, dtstart.minute, dtstart.second);
676#endif
677
678 /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add
679 a single change for the DTSTART. */
680 if (!has_rrule) {
681 change.year = dtstart.year;
682 change.month = dtstart.month;
683 change.day = dtstart.day;
684 change.hour = dtstart.hour;
685 change.minute = dtstart.minute;
686 change.second = dtstart.second;
687
688 /* Convert to UTC. */
689 icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
690
691#ifdef ICALTIMEZONE_DEBUG_PRINT
692 printf(" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
693 change.year, change.month, change.day, change.hour, change.minute, change.second);
694#endif
695
696 /* Add the change to the array. */
697 icalarray_append(changes, &change);
698 }
699
700 const size_t max_rrule_search = icallimit_get(ICAL_LIMIT_RRULE_SEARCH);
701
702 /* The component has recurrence data, so we expand that now. */
704 while (prop && (has_rdate || has_rrule)) {
705#ifdef ICALTIMEZONE_DEBUG_PRINT
706 printf("Expanding property...\n");
707#endif
708 switch (icalproperty_isa(prop)) {
710 rdate = icalproperty_get_rdate(prop);
711 change.year = rdate.time.year;
712 change.month = rdate.time.month;
713 change.day = rdate.time.day;
714 /* RDATEs with a DATE value inherit the time from
715 the DTSTART. */
716 if (icaltime_is_date(rdate.time)) {
717 change.hour = dtstart.hour;
718 change.minute = dtstart.minute;
719 change.second = dtstart.second;
720 } else {
721 change.hour = rdate.time.hour;
722 change.minute = rdate.time.minute;
723 change.second = rdate.time.second;
724
725 /* The spec was a bit vague about whether RDATEs were in local
726 time or UTC so we support both to be safe. So if it is in
727 UTC we have to add the UTC offset to get a local time. */
728 if (!icaltime_is_utc(rdate.time)) {
729 icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
730 }
731 }
732
733#ifdef ICALTIMEZONE_DEBUG_PRINT
734 printf(" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
735 change.year, change.month, change.day,
736 change.hour, change.minute, change.second);
737#endif
738
739 icalarray_append(changes, &change);
740 break;
742 rrule = icalproperty_get_rrule(prop);
743 if (rrule) {
744 rrule = icalrecurrencetype_clone(rrule);
745 }
746
747 if (rrule) {
748 /* If the rrule UNTIL value is set and is in UTC, we convert it to
749 a local time, since the recurrence code has no way to convert
750 it itself. */
751 if (!icaltime_is_null_time(rrule->until) && icaltime_is_utc(rrule->until)) {
752#ifdef ICALTIMEZONE_DEBUG_PRINT
753 printf(" Found RRULE UNTIL in UTC.\n");
754#endif
755
756 /* To convert from UTC to a local time, we use the TZOFFSETFROM
757 since that is the offset from UTC that will be in effect
758 when each of the RRULE occurrences happens. */
759 icaltime_adjust(&rrule->until, 0, 0, 0, change.prev_utc_offset);
760 rrule->until.zone = NULL;
761 }
762
763 /* Add the dtstart to changes, otherwise some oddly-defined VTIMEZONE
764 components can cause the first year to get skipped. */
765 change.year = dtstart.year;
766 change.month = dtstart.month;
767 change.day = dtstart.day;
768 change.hour = dtstart.hour;
769 change.minute = dtstart.minute;
770 change.second = dtstart.second;
771
772#ifdef ICALTIMEZONE_DEBUG_PRINT
773 printf(" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
774 change.year, change.month, change.day,
775 change.hour, change.minute, change.second);
776#endif
777
778 icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
779
780 icalarray_append(changes, &change);
781
782 rrule_iterator = icalrecur_iterator_new(rrule, dtstart);
783 for (size_t rrule_iterator_count = 0; rrule_iterator && rrule_iterator_count < max_rrule_search; rrule_iterator_count++) {
784 occ = icalrecur_iterator_next(rrule_iterator);
785 /* Skip dtstart since we just added it */
786 if (icaltime_compare(dtstart, occ) == 0) {
787 continue;
788 }
789 if (occ.year > end_year || icaltime_is_null_time(occ)) {
790 break;
791 }
792 change.year = occ.year;
793 change.month = occ.month;
794 change.day = occ.day;
795 change.hour = occ.hour;
796 change.minute = occ.minute;
797 change.second = occ.second;
798
799#ifdef ICALTIMEZONE_DEBUG_PRINT
800 printf(" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
801 change.year, change.month, change.day,
802 change.hour, change.minute, change.second);
803#endif
804
805 icaltimezone_adjust_change(&change, 0, 0, 0, -change.prev_utc_offset);
806
807 icalarray_append(changes, &change);
808 }
809
810 icalrecur_iterator_free(rrule_iterator);
812 }
813 break;
814 default:
815 break;
816 }
817
819 }
820}
821
825static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2)
826{
827 const icaltimezonechange *change1, *change2;
828 int retval;
829
830 change1 = (const icaltimezonechange *)elem1;
831 change2 = (const icaltimezonechange *)elem2;
832
833 if (change1->year < change2->year) {
834 retval = -1;
835 } else if (change1->year > change2->year) {
836 retval = 1;
837 } else if (change1->month < change2->month) {
838 retval = -1;
839 } else if (change1->month > change2->month) {
840 retval = 1;
841 } else if (change1->day < change2->day) {
842 retval = -1;
843 } else if (change1->day > change2->day) {
844 retval = 1;
845 } else if (change1->hour < change2->hour) {
846 retval = -1;
847 } else if (change1->hour > change2->hour) {
848 retval = 1;
849 } else if (change1->minute < change2->minute) {
850 retval = -1;
851 } else if (change1->minute > change2->minute) {
852 retval = 1;
853 } else if (change1->second < change2->second) {
854 retval = -1;
855 } else if (change1->second > change2->second) {
856 retval = 1;
857 } else {
858 retval = 0;
859 }
860
861 return retval;
862}
863
865 icaltimezone *from_zone, icaltimezone *to_zone)
866{
867 int utc_offset, is_daylight;
868
869 /* If the time is a DATE value or both timezones are the same, or we are
870 converting a floating time, we don't need to do anything. */
871 if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL) {
872 return;
873 }
874
875 /* Convert the time to UTC by getting the UTC offset and subtracting it. */
876 utc_offset = icaltimezone_get_utc_offset(from_zone, tt, NULL);
877 icaltime_adjust(tt, 0, 0, 0, -utc_offset);
878
879 /* Now we convert the time to the new timezone by getting the UTC offset
880 of our UTC time and adding it. */
881 utc_offset = icaltimezone_get_utc_offset_of_utc_time(to_zone, tt, &is_daylight);
882 tt->is_daylight = is_daylight;
883 tt->zone = to_zone;
884 icaltime_adjust(tt, 0, 0, 0, utc_offset);
885}
886
887int icaltimezone_get_utc_offset(icaltimezone *zone, const struct icaltimetype *tt, int *is_daylight)
888{
889 const icaltimezonechange *zone_change;
890 const icaltimezonechange *prev_zone_change;
891 icaltimezonechange tt_change = {0}, tmp_change;
892 size_t change_num, change_num_to_use;
893 int found_change;
894 int step, utc_offset_change, cmp;
895 int want_daylight;
896
897 if (tt == NULL || tt->year > ICALTIMEZONE_MAX_YEAR) {
898 return 0;
899 }
900
901 if (is_daylight) {
902 *is_daylight = 0;
903 }
904
905 /* For local times and UTC return 0. */
906 if (zone == NULL || zone == &utc_timezone) {
907 return 0;
908 }
909
910 /* Use the builtin icaltimezone if possible. */
911 if (zone->builtin_timezone) {
912 zone = zone->builtin_timezone;
913 }
914
915 if (!icaltimezone_changes_lock()) {
916 return 0;
917 }
918
919 /* Make sure the changes array is expanded up to the given time. */
920 if (!icaltimezone_ensure_coverage(zone, tt->year)) {
921 return 0;
922 }
923
924 if (!zone->changes || zone->changes->num_elements == 0) {
925 (void)icaltimezone_changes_unlock();
926 return 0;
927 }
928
929 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
930 can use our comparison function on it. */
931 tt_change.year = tt->year;
932 tt_change.month = tt->month;
933 tt_change.day = tt->day;
934 tt_change.hour = tt->hour;
935 tt_change.minute = tt->minute;
936 tt_change.second = tt->second;
937
938 /* This should find a change close to the time, either the change before
939 it or the change after it. */
940 change_num = icaltimezone_find_nearby_change(zone, &tt_change);
941
942 /* Now move backwards or forwards to find the timezone change that applies
943 to tt. It should only have to do 1 or 2 steps. */
944 zone_change = icalarray_element_at(zone->changes, change_num);
945 step = 1;
946 found_change = 0;
947 change_num_to_use = (size_t)-1; // invalid on purpose
948 for (;;) {
949 /* Copy the change, so we can adjust it. */
950 tmp_change = *zone_change;
951
952 /* If the clock is going backward, check if it is in the region of time
953 that is used twice. If it is, use the change with the daylight
954 setting which matches tt, or use standard if we don't know. */
955 if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
956 /* If the time change is at 2:00AM local time and the clock is
957 going back to 1:00AM we adjust the change to 1:00AM. We may
958 have the wrong change but we'll figure that out later. */
959 icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.utc_offset);
960 } else {
961 icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset);
962 }
963
964 cmp = icaltimezone_compare_change_fn(&tt_change, &tmp_change);
965
966 /* If the given time is on or after this change, then this change may
967 apply, but we continue as a later change may be the right one.
968 If the given time is before this change, then if we have already
969 found a change which applies we can use that, else we need to step
970 backwards. */
971 if (cmp >= 0) {
972 found_change = 1;
973 change_num_to_use = change_num;
974 } else {
975 step = -1;
976 }
977
978 /* If we are stepping backwards through the changes and we have found
979 a change that applies, then we know this is the change to use so
980 we exit the loop. */
981 if (step == -1 && found_change == 1) {
982 break;
983 }
984
985 /* If we go past the start of the changes array, then we have no data
986 for this time so we return the prev UTC offset. */
987 if (change_num == 0 && step < 0) {
988 if (is_daylight) {
989 *is_daylight = !tmp_change.is_daylight;
990 }
991
992 if (!icaltimezone_changes_unlock()) {
993 return 0;
994 }
995
996 return tmp_change.prev_utc_offset;
997 }
998
999 change_num += (size_t)step;
1000
1001 if (change_num >= zone->changes->num_elements) {
1002 break;
1003 }
1004
1005 zone_change = icalarray_element_at(zone->changes, change_num);
1006 }
1007
1008 /* If we didn't find a change to use, then we have a bug! */
1009 icalerror_assert(found_change == 1, "No applicable timezone change found");
1010
1011 /* Now we just need to check if the time is in the overlapped region of
1012 time when clocks go back. */
1013 zone_change = icalarray_element_at(zone->changes, change_num_to_use);
1014
1015 utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
1016 if (utc_offset_change < 0 && change_num_to_use > 0) {
1017 tmp_change = *zone_change;
1018 icaltimezone_adjust_change(&tmp_change, 0, 0, 0, tmp_change.prev_utc_offset);
1019
1020 if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) < 0) {
1021 /* The time is in the overlapped region, so we may need to use
1022 either the current zone_change or the previous one. If the
1023 time has the is_daylight field set we use the matching change,
1024 else we use the change with standard time. */
1025 prev_zone_change = icalarray_element_at(zone->changes, change_num_to_use - 1);
1026
1027 /* I was going to add an is_daylight flag to struct icaltimetype,
1028 but iCalendar doesn't let us distinguish between standard and
1029 daylight time anyway, so there's no point. So we just use the
1030 standard time instead. */
1031 want_daylight = (tt->is_daylight == 1) ? 1 : 0;
1032
1033#ifdef ICALTIMEZONE_DEBUG_PRINT
1034 if (zone_change->is_daylight == prev_zone_change->is_daylight) {
1035 printf(" **** Same is_daylight setting\n");
1036 }
1037#endif
1038
1039 if (zone_change->is_daylight != want_daylight &&
1040 prev_zone_change->is_daylight == want_daylight) {
1041 zone_change = prev_zone_change;
1042 }
1043 }
1044 }
1045
1046 /* Now we know exactly which timezone change applies to the time, so
1047 we can return the UTC offset and whether it is a daylight time. */
1048 if (is_daylight) {
1049 *is_daylight = zone_change->is_daylight;
1050 }
1051 utc_offset_change = zone_change->utc_offset;
1052
1053 if (!icaltimezone_changes_unlock()) {
1054 return 0;
1055 }
1056
1057 return utc_offset_change;
1058}
1059
1061 const struct icaltimetype *tt, int *is_daylight)
1062{
1063 const icaltimezonechange *zone_change;
1064 icaltimezonechange tt_change, tmp_change;
1065 size_t change_num, change_num_to_use;
1066 int found_change = 1;
1067 int step, utc_offset;
1068
1069 if (is_daylight) {
1070 *is_daylight = 0;
1071 }
1072
1073 /* For local times and UTC return 0. */
1074 if (zone == NULL || zone == &utc_timezone) {
1075 return 0;
1076 }
1077
1078 /* Use the builtin icaltimezone if possible. */
1079 if (zone->builtin_timezone) {
1080 zone = zone->builtin_timezone;
1081 }
1082
1083 if (!icaltimezone_changes_lock()) {
1084 return 0;
1085 }
1086
1087 /* Make sure the changes array is expanded up to the given time. */
1088 if (!icaltimezone_ensure_coverage(zone, tt->year)) {
1089 return 0;
1090 }
1091
1092 if (!zone->changes || zone->changes->num_elements == 0) {
1093 (void)icaltimezone_changes_unlock();
1094 return 0;
1095 }
1096
1097 /* Copy the time parts of the icaltimetype to an icaltimezonechange so we
1098 can use our comparison function on it. */
1099 tt_change.year = tt->year;
1100 tt_change.month = tt->month;
1101 tt_change.day = tt->day;
1102 tt_change.hour = tt->hour;
1103 tt_change.minute = tt->minute;
1104 tt_change.second = tt->second;
1105
1106 /* This should find a change close to the time, either the change before
1107 it or the change after it. */
1108 change_num = icaltimezone_find_nearby_change(zone, &tt_change);
1109
1110 /* Now move backwards or forwards to find the timezone change that applies
1111 to tt. It should only have to do 1 or 2 steps. */
1112 zone_change = icalarray_element_at(zone->changes, change_num);
1113 step = 1;
1114 found_change = 0;
1115 change_num_to_use = (size_t)-1; // invalid on purpose
1116 for (;;) {
1117 /* Copy the change and adjust it to UTC. */
1118 tmp_change = *zone_change;
1119
1120 /* If the given time is on or after this change, then this change may
1121 apply, but we continue as a later change may be the right one.
1122 If the given time is before this change, then if we have already
1123 found a change which applies we can use that, else we need to step
1124 backwards. */
1125 if (icaltimezone_compare_change_fn(&tt_change, &tmp_change) >= 0) {
1126 found_change = 1;
1127 change_num_to_use = change_num;
1128 } else {
1129 step = -1;
1130 }
1131
1132 /* If we are stepping backwards through the changes and we have found
1133 a change that applies, then we know this is the change to use so
1134 we exit the loop. */
1135 if (step == -1 && found_change == 1) {
1136 break;
1137 }
1138
1139 /* If we go past the start of the changes array, then we have no data
1140 for this time so we return the prev UTC offset. */
1141 if (change_num == 0 && step < 0) {
1142 if (is_daylight) {
1143 *is_daylight = !tmp_change.is_daylight;
1144 }
1145
1146 if (!icaltimezone_changes_unlock()) {
1147 return 0;
1148 }
1149
1150 return tmp_change.prev_utc_offset;
1151 }
1152
1153 change_num += (size_t)step;
1154
1155 if (change_num >= zone->changes->num_elements) {
1156 break;
1157 }
1158
1159 zone_change = icalarray_element_at(zone->changes, change_num);
1160 }
1161
1162 /* If we didn't find a change to use, then we have a bug! */
1163 icalerror_assert(found_change == 1, "No applicable timezone change found");
1164
1165 /* Now we know exactly which timezone change applies to the time, so
1166 we can return the UTC offset and whether it is a daylight time. */
1167 zone_change = icalarray_element_at(zone->changes, change_num_to_use);
1168 if (is_daylight) {
1169 *is_daylight = zone_change->is_daylight;
1170 }
1171 utc_offset = zone_change->utc_offset;
1172
1173 if (!icaltimezone_changes_unlock()) {
1174 return 0;
1175 }
1176
1177 return utc_offset;
1178}
1179
1186static size_t icaltimezone_find_nearby_change(icaltimezone *zone, const icaltimezonechange *change)
1187{
1188 size_t lower, middle, upper;
1189
1190 /* Do a simple binary search. */
1191 lower = middle = 0;
1192 upper = zone->changes->num_elements;
1193
1194 while (lower < upper) {
1195 middle = (lower + upper) / 2;
1196 const icaltimezonechange *zone_change = icalarray_element_at(zone->changes, middle);
1197 int cmp = icaltimezone_compare_change_fn(change, zone_change);
1198 if (cmp == 0) {
1199 break;
1200 } else if (cmp < 0) {
1201 upper = middle;
1202 } else {
1203 lower = middle + 1;
1204 }
1205 }
1206
1207 return middle;
1208}
1209
1216static void icaltimezone_adjust_change(icaltimezonechange *tt,
1217 int days, int hours, int minutes, int seconds)
1218{
1219 int second, minute, hour, day;
1220 int minutes_overflow, hours_overflow, days_overflow;
1221
1222 /* Add on the seconds. */
1223 second = tt->second + seconds;
1224 tt->second = second % 60;
1225 minutes_overflow = second / 60;
1226 if (tt->second < 0) {
1227 tt->second += 60;
1228 minutes_overflow--;
1229 }
1230
1231 /* Add on the minutes. */
1232 minute = tt->minute + minutes + minutes_overflow;
1233 tt->minute = minute % 60;
1234 hours_overflow = minute / 60;
1235 if (tt->minute < 0) {
1236 tt->minute += 60;
1237 hours_overflow--;
1238 }
1239
1240 /* Add on the hours. */
1241 hour = tt->hour + hours + hours_overflow;
1242 tt->hour = hour % 24;
1243 days_overflow = hour / 24;
1244 if (tt->hour < 0) {
1245 tt->hour += 24;
1246 days_overflow--;
1247 }
1248
1249 /* Add on the days. */
1250 day = tt->day + days + days_overflow;
1251 if (day > 0) {
1252 for (;;) {
1253 int days_in_month = icaltime_days_in_month(tt->month, tt->year);
1254 if (day <= days_in_month) {
1255 break;
1256 }
1257
1258 tt->month++;
1259 if (tt->month >= 13) {
1260 tt->year++;
1261 tt->month = 1;
1262 }
1263
1264 day -= days_in_month;
1265 }
1266 } else {
1267 while (day <= 0) {
1268 if (tt->month == 1) {
1269 tt->year--;
1270 tt->month = 12;
1271 } else {
1272 tt->month--;
1273 }
1274
1275 day += icaltime_days_in_month(tt->month, tt->year);
1276 }
1277 }
1278 tt->day = day;
1279}
1280
1282{
1283 /* If this is a floating time, without a timezone, return NULL. */
1284 if (!zone) {
1285 return NULL;
1286 }
1287
1288 icaltimezone_load_builtin_timezone(zone);
1289
1290 return zone->tzid;
1291}
1292
1294{
1295 /* If this is a floating time, without a timezone, return NULL. */
1296 if (!zone) {
1297 return NULL;
1298 }
1299
1300 /* Note that for builtin timezones this comes from zones.tab so we don't
1301 need to check the timezone is loaded here. */
1302 return zone->location;
1303}
1304
1306{
1307 /* If this is a floating time, without a timezone, return NULL. */
1308 if (!zone) {
1309 return NULL;
1310 }
1311
1312 icaltimezone_load_builtin_timezone(zone);
1313
1314 return zone->tznames;
1315}
1316
1318{
1319 /* If this is a floating time, without a timezone, return 0. */
1320 if (!zone) {
1321 return 0.0;
1322 }
1323
1324 /* Note that for builtin timezones this comes from zones.tab so we don't
1325 need to check the timezone is loaded here. */
1326 return zone->latitude;
1327}
1328
1330{
1331 /* If this is a floating time, without a timezone, return 0. */
1332 if (!zone) {
1333 return 0.0;
1334 }
1335
1336 /* Note that for builtin timezones this comes from zones.tab so we don't
1337 need to check the timezone is loaded here. */
1338 return zone->longitude;
1339}
1340
1342{
1343 /* If this is a floating time, without a timezone, return NULL. */
1344 if (!zone) {
1345 return NULL;
1346 }
1347
1348 icaltimezone_load_builtin_timezone(zone);
1349
1350 return zone->component;
1351}
1352
1353bool icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp)
1354{
1355 icaltimezone_reset(zone);
1356 return icaltimezone_get_vtimezone_properties(zone, comp);
1357}
1358
1359static const char *skip_slashes(const char *text, int n_slashes)
1360{
1361 const char *pp;
1362 int num_slashes = 0;
1363
1364 if (!text) {
1365 return NULL;
1366 }
1367
1368 for (pp = text; *pp; pp++) {
1369 if (*pp == '/') {
1370 num_slashes++;
1371 if (num_slashes == n_slashes) {
1372 return pp + 1;
1373 }
1374 }
1375 }
1376
1377 return NULL;
1378}
1379
1381{
1382 char *display_name;
1383
1384 display_name = (char *)icaltimezone_get_location(zone);
1385 /* coverity[use_after_free] */
1386 if (!display_name) {
1387 display_name = (char *)icaltimezone_get_tznames(zone);
1388 }
1389 if (!display_name) {
1390 display_name = (char *)icaltimezone_get_tzid(zone);
1391 if (display_name) {
1392 /* Outlook strips out X-LIC-LOCATION property and all we get back
1393 * in the iTIP replies is the TZID. So we see if this is one of our TZIDs
1394 * and if so we jump to the city name at the end of it. */
1395 const char *tzid_prefix = icaltimezone_tzid_prefix();
1396 if (!strncmp(display_name, tzid_prefix, strlen(tzid_prefix))) {
1397 /* Skip past our prefix */
1398 display_name += strlen(tzid_prefix);
1399 }
1400 }
1401 }
1402
1403 return display_name;
1404}
1405
1407icalarray *icaltimezone_array_new(void)
1408{
1409 return icalarray_new(sizeof(icaltimezone), 16);
1410}
1411
1412void icaltimezone_array_append_from_vtimezone(icalarray *timezones, icalcomponent *child)
1413{
1414 icaltimezone zone;
1415
1416 icaltimezone_init(&zone);
1417 if (icaltimezone_get_vtimezone_properties(&zone, child)) {
1418 icalarray_append(timezones, &zone);
1419 }
1420}
1421
1422void icaltimezone_array_free(icalarray *timezones)
1423{
1424 if (timezones) {
1425 for (size_t i = 0; i < timezones->num_elements; i++) {
1426 icaltimezone *zone = icalarray_element_at(timezones, i);
1427 icaltimezone_free(zone, 0);
1428 }
1429
1430 icalarray_free(timezones);
1431 }
1432}
1434
1435/*
1436 * BUILTIN TIMEZONE HANDLING
1437 */
1438
1440{
1441 if (!builtin_timezones) {
1442 icaltimezone_init_builtin_timezones();
1443 }
1444
1445 return builtin_timezones;
1446}
1447
1449{
1450 (void)icaltimezone_builtin_lock();
1451 icaltimezone_array_free(builtin_timezones);
1452 builtin_timezones = 0;
1453 (void)icaltimezone_builtin_unlock();
1454}
1455
1457{
1458 icalcomponent *comp;
1459 icaltimezone *zone;
1460 size_t lower;
1461
1462 if (!location || !location[0]) {
1463 return NULL;
1464 }
1465
1466 if (!builtin_timezones) {
1467 icaltimezone_init_builtin_timezones();
1468 }
1469
1470 if (strcmp(location, "UTC") == 0 || strcmp(location, "GMT") == 0) {
1471 return &utc_timezone;
1472 }
1473
1474 /* The zones from the system are not stored in alphabetical order,
1475 so we just do a sequential search */
1476 for (lower = 0; lower < builtin_timezones->num_elements; lower++) {
1477 zone = icalarray_element_at(builtin_timezones, lower);
1478 const char *zone_location = icaltimezone_get_location(zone);
1479 if (zone_location && strcmp(location, zone_location) == 0) {
1480 return zone;
1481 }
1482 }
1483
1484 /* Check whether file exists, but is not mentioned in zone.tab.
1485 It means it's a deprecated timezone, but still available. */
1486 comp = icaltimezone_fetch_timezone(location);
1487 if (comp) {
1488 icaltimezone tz;
1489
1490 icaltimezone_init(&tz);
1491 if (icaltimezone_set_component(&tz, comp)) {
1492 icalarray_append(builtin_timezones, &tz);
1493 return icalarray_element_at(builtin_timezones, builtin_timezones->num_elements - 1);
1494 } else {
1495 icalcomponent_free(comp);
1496 }
1497 }
1498
1499 return NULL;
1500}
1501
1502static struct icaltimetype tm_to_icaltimetype(const struct tm *tm)
1503{
1504 struct icaltimetype itt;
1505
1506 memset(&itt, 0, sizeof(struct icaltimetype));
1507
1508 itt.second = tm->tm_sec;
1509 itt.minute = tm->tm_min;
1510 itt.hour = tm->tm_hour;
1511
1512 itt.day = tm->tm_mday;
1513 itt.month = tm->tm_mon + 1;
1514 itt.year = tm->tm_year + 1900;
1515
1516 itt.is_date = 0;
1517
1518 return itt;
1519}
1520
1521static int get_offset(icaltimezone *zone)
1522{
1523 struct tm local;
1524 struct icaltimetype tt;
1525 int offset;
1526 const icaltime_t now = icaltime(NULL);
1527
1528 memset(&local, 0, sizeof(struct tm));
1529 if (!icalgmtime_r(&now, &local)) {
1530 return 0;
1531 }
1532
1533 tt = tm_to_icaltimetype(&local);
1534 offset = icaltimezone_get_utc_offset(zone, &tt, NULL);
1535
1536 return offset;
1537}
1538
1540{
1541 if (!builtin_timezones) {
1542 icaltimezone_init_builtin_timezones();
1543 }
1544
1545 if (offset == 0) {
1546 return &utc_timezone;
1547 }
1548
1549 if (!tzname) {
1550 return NULL;
1551 }
1552
1553 size_t count = builtin_timezones->num_elements;
1554
1555 for (size_t i = 0; i < count; i++) {
1556 icaltimezone *zone = icalarray_element_at(builtin_timezones, i);
1557 if (zone) {
1558 icaltimezone_load_builtin_timezone(zone);
1559 int z_offset = get_offset(zone);
1560 if (z_offset == offset && zone->tznames && !strcmp(tzname, zone->tznames)) {
1561 return zone;
1562 }
1563 }
1564 }
1565
1566 return NULL;
1567}
1568
1570{
1571 const char *p, *zone_tzid, *tzid_prefix;
1573 int compat = 0;
1574
1575 if (!tzid || !tzid[0]) {
1576 return NULL;
1577 }
1578
1579 if (strcmp(tzid, "UTC") == 0 || strcmp(tzid, "GMT") == 0) {
1581 }
1582
1583 tzid_prefix = icaltimezone_tzid_prefix();
1584 /* Check that the TZID starts with our unique prefix. */
1585 if (strncmp(tzid, tzid_prefix, strlen(tzid_prefix)) != 0) {
1586 int ii;
1587
1588 for (ii = 0; glob_compat_tzids[ii].tzid; ii++) {
1589 if (strncmp(tzid, glob_compat_tzids[ii].tzid, strlen(glob_compat_tzids[ii].tzid)) == 0) {
1590 p = skip_slashes(tzid, glob_compat_tzids[ii].slashes);
1591 if (p) {
1593 /* Do not recheck the TZID matches exactly, it does not, because
1594 fallbacking with the compatibility timezone prefix here. */
1595 return zone;
1596 }
1597 break;
1598 }
1599 }
1600
1601 return NULL;
1602 }
1603
1604 /* Skip past our prefix */
1605 p = tzid + strlen(tzid_prefix);
1606
1607 /* Special-case "/freeassociation.sourceforge.net/Tzfile/"
1608 because it shares prefix with BUILTIN_TZID_PREFIX */
1609 if (strcmp(tzid_prefix, BUILTIN_TZID_PREFIX) == 0 &&
1610 strncmp(p, "Tzfile/", 7) == 0) {
1611 p += 7;
1612 compat = 1;
1613 }
1614
1615 /* Now we can use the function to get the builtin timezone from the
1616 location string. */
1618 if (!zone || compat) {
1619 return zone;
1620 }
1621
1622#if defined(USE_BUILTIN_TZDATA)
1623 if (use_builtin_tzdata) {
1624 return zone;
1625 }
1626#endif
1627
1628 /* Check that the builtin TZID matches exactly. We don't want to return
1629 a different version of the VTIMEZONE. */
1630 zone_tzid = icaltimezone_get_tzid(zone);
1631 if (!strcmp(zone_tzid, tzid)) {
1632 return zone;
1633 } else {
1634 return NULL;
1635 }
1636}
1637
1639{
1640 if (!builtin_timezones) {
1641 icaltimezone_init_builtin_timezones();
1642 }
1643
1644 return &utc_timezone;
1645}
1646
1653static bool icaltimezone_init_builtin_timezones(void)
1654{
1655 /* Initialize the special UTC timezone. */
1656 utc_timezone.tzid = (char *)"UTC";
1657
1658 if (!icaltimezone_builtin_lock()) {
1659 return false;
1660 }
1661 if (!builtin_timezones) {
1662 icaltimezone_parse_zone_tab();
1663 }
1664 if (!icaltimezone_builtin_unlock()) {
1665 return false;
1666 }
1667 return true;
1668}
1669
1670static bool parse_coord(const char *coord, int len, int *degrees, int *minutes, int *seconds)
1671{
1672 icalerror_check_arg_rz((coord != 0), "coord");
1673
1674 *degrees = 0;
1675 *minutes = 0;
1676 *seconds = 0;
1677
1678 bool fail = true;
1679 if (len == 5) {
1680 if (sscanf(coord + 1, "%2d%2d", degrees, minutes) == 2) {
1681 fail = false;
1682 }
1683 } else if (len == 6) {
1684 if (sscanf(coord + 1, "%3d%2d", degrees, minutes) == 2) {
1685 fail = false;
1686 }
1687 } else if (len == 7) {
1688 if (sscanf(coord + 1, "%2d%2d%2d", degrees, minutes, seconds) == 3) {
1689 fail = false;
1690 }
1691 } else if (len == 8) {
1692 if (sscanf(coord + 1, "%3d%2d%2d", degrees, minutes, seconds) == 3) {
1693 fail = false;
1694 }
1695 }
1696
1697 if (fail) {
1698 icalerrprintf("Invalid coordinate: %s\n", coord);
1699 } else {
1700 if (coord[0] == '-') {
1701 *degrees = -*degrees;
1702 }
1703 }
1704
1705 return fail;
1706}
1707
1708static bool fetch_lat_long_from_string(const char *str,
1709 int *latitude_degrees, int *latitude_minutes,
1710 int *latitude_seconds,
1711 int *longitude_degrees, int *longitude_minutes,
1712 int *longitude_seconds,
1713 char *location, size_t len_location)
1714{
1715 size_t len;
1716 const char *loc, *temp;
1717 char *sptr, *lat;
1718 const char *lon;
1719
1720 if (!location || !len_location) {
1721 return false;
1722 }
1723
1724 /* We need to parse the latitude/longitude coordinates and location fields */
1725 const size_t len_str = strlen(str);
1726 size_t i = 0;
1727 sptr = (char *)str;
1728 while ((*sptr != '\t') && (*sptr != '\0')) {
1729 sptr++;
1730 i++;
1731 }
1732 if (i >= len_str) {
1733 return false;
1734 }
1735 temp = ++sptr;
1736 while (*sptr != '\t' && *sptr != '\0') {
1737 sptr++;
1738 }
1739 len = (size_t)(ptrdiff_t)(sptr - temp);
1740 if (len == 0) {
1741 return false;
1742 }
1743 lat = (char *)icalmemory_new_buffer(len + 1);
1744 if (!lat) {
1745 return false;
1746 }
1747 memset(lat, '\0', len + 1);
1748 strncpy(lat, temp, len + 1);
1749 lat[len] = '\0';
1750 while ((*sptr != '\t') && (*sptr != '\0')) {
1751 sptr++;
1752 }
1753 loc = ++sptr;
1754 while (!isspace((int)(*sptr)) && (*sptr != '\0')) {
1755 sptr++;
1756 }
1757 len = (size_t)(ptrdiff_t)(sptr - loc);
1758 if (len >= len_location) {
1759 len = len_location - 1;
1760 }
1761 strncpy(location, loc, len);
1762 location[len] = '\0';
1763
1764 lon = lat + 1;
1765 while (*lon != '\0' && *lon != '+' && *lon != '-') {
1766 lon++;
1767 }
1768
1769 if (parse_coord(lat, (int)(lon - lat),
1770 latitude_degrees,
1771 latitude_minutes,
1772 latitude_seconds) == 1 ||
1773 parse_coord(lon, (int)strlen(lon),
1774 longitude_degrees, longitude_minutes, longitude_seconds) == 1) {
1776 return true;
1777 }
1778
1780
1781 return false;
1782}
1783
1794static void icaltimezone_parse_zone_tab(void)
1795{
1796 const char *zonedir, *zonetab;
1797 char *filename;
1798 FILE *fp;
1799 size_t filename_len;
1800
1801 icalerror_assert(builtin_timezones == NULL, "Parsing zones.tab file multiple times");
1802
1803 icalarray *timezones = icalarray_new(sizeof(icaltimezone), 1024);
1804
1805 if (!use_builtin_tzdata) {
1807 zonetab = ZONES_TAB_SYSTEM_FILENAME;
1808 } else {
1809 zonedir = get_zone_directory_builtin();
1810 zonetab = ZONES_TAB_FILENAME;
1811 }
1812
1813 filename_len = 0;
1814 if (zonedir) {
1815 filename_len = strlen(zonedir);
1816 }
1817
1818 icalerror_assert(filename_len > 0, "Unable to locate a zoneinfo dir");
1819 if (filename_len == 0) {
1820 // Set an empty builtin_timezones array if there's an error
1821 builtin_timezones = timezones;
1822
1824 return;
1825 }
1826
1827 filename_len += strlen(zonetab);
1828 filename_len += 2; /* for dir separator and final '\0' */
1829
1830 filename = (char *)icalmemory_new_buffer(filename_len);
1831 if (!filename) {
1832 // Set an empty builtin_timezones array if there's an error
1833 builtin_timezones = timezones;
1834
1836 return;
1837 }
1838 snprintf(filename, filename_len, "%s/%s", zonedir, zonetab);
1839
1840 fp = fopen(filename, "r");
1841 icalerror_assert(fp, "Cannot open the zonetab file for reading");
1842 if (!fp) {
1843 // Set an empty builtin_timezones array if there's an error
1844 builtin_timezones = timezones;
1845
1847 icalmemory_free_buffer(filename);
1848 return;
1849 }
1850
1851#if !defined(__clang_analyzer__) // avoid unix.BlockInCriticalSection
1852 char buf[1024];
1853 while (!feof(fp) && !ferror(fp) && fgets(buf, (int)sizeof(buf), fp)) {
1854 char location[1024] = {0}; /* Stores the city name when parsing buf. */
1855 int latitude_degrees = 360;
1856 int longitude_degrees = 360;
1857 int latitude_minutes = 0;
1858 int longitude_minutes = 0;
1859 int latitude_seconds = 0;
1860 int longitude_seconds = 0;
1861
1862 if (buf[0] == '\0') {
1863 break;
1864 }
1865 if (*buf == '#') {
1866 continue;
1867 }
1868
1869 if (use_builtin_tzdata) {
1870 /* The format of each line is: "[ latitude longitude ] location". */
1871 if (buf[0] != '+' && buf[0] != '-') {
1872 if (sscanf(buf, "%1000s", location) != 1) { /*limit location to 1000chars */
1873 /*increase as needed */
1874 /*see location and buf declarations */
1875 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1876 continue;
1877 }
1878 } else if (sscanf(buf, "%4d%2d%2d %4d%2d%2d %1000s", /*limit location to 1000chars */
1879 /*increase as needed */
1880 /*see location and buf declarations */
1881 &latitude_degrees, &latitude_minutes,
1882 &latitude_seconds,
1883 &longitude_degrees, &longitude_minutes,
1884 &longitude_seconds, location) != 7) {
1885 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1886 continue;
1887 }
1888 } else {
1889 if (fetch_lat_long_from_string(buf, &latitude_degrees, &latitude_minutes,
1890 &latitude_seconds,
1891 &longitude_degrees, &longitude_minutes,
1892 &longitude_seconds, location, sizeof(location))) {
1893 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1894 continue;
1895 }
1896 if (location[0] == '\0') {
1897 icalerrprintf("%s: Invalid timezone location: %s\n", filename, buf);
1898 continue;
1899 }
1900 }
1902 icaltimezone_init(&zone);
1903 /* coverity[resource_leak] */
1904 zone.location = icalmemory_strdup(location);
1905
1906 if (latitude_degrees >= 0) {
1907 zone.latitude =
1908 (double)latitude_degrees +
1909 (double)latitude_minutes / 60 +
1910 (double)latitude_seconds / 3600;
1911 } else {
1912 zone.latitude =
1913 (double)latitude_degrees -
1914 (double)latitude_minutes / 60 -
1915 (double)latitude_seconds / 3600;
1916 }
1917
1918 if (longitude_degrees >= 0) {
1919 zone.longitude =
1920 (double)longitude_degrees +
1921 (double)longitude_minutes / 60 +
1922 (double)longitude_seconds / 3600;
1923 } else {
1924 zone.longitude =
1925 (double)longitude_degrees -
1926 (double)longitude_minutes / 60 -
1927 (double)longitude_seconds / 3600;
1928 }
1929
1930 icalarray_append(timezones, &zone);
1931
1932#ifdef ICALTIMEZONE_DEBUG_PRINT
1933 printf("Found zone: %s %f %f\n", location, zone.latitude, zone.longitude);
1934#endif
1935 }
1936#endif // __clang_analyzer__
1937
1938 builtin_timezones = timezones;
1939
1940 icalmemory_free_buffer(filename);
1941 fclose(fp);
1942}
1943
1947static bool icaltimezone_load_builtin_timezone(icaltimezone *zone)
1948{
1949 icalcomponent *comp = 0, *subcomp;
1950
1951 /* Prevent blocking on mutex lock caused by recursive calls */
1952 if (zone->component) {
1953 return true;
1954 }
1955
1956 if (!icaltimezone_builtin_lock()) {
1957 return false;
1958 }
1959
1960 /* Try again, maybe it had been set by other thread while waiting for the lock */
1961 if (zone->component) {
1962 if (!icaltimezone_builtin_unlock()) {
1963 return false;
1964 }
1965 return true;
1966 }
1967
1968 /* If the location isn't set, it isn't a builtin timezone. */
1969 if (!zone->location || !zone->location[0]) {
1970 if (!icaltimezone_builtin_unlock()) {
1971 return false;
1972 }
1973 return true;
1974 }
1975
1976 if (use_builtin_tzdata) {
1977 char *filename;
1978 size_t filename_len;
1979 FILE *fp;
1980 icalparser *parser;
1981
1982 filename_len = strlen(get_zone_directory_builtin()) + strlen(zone->location) + 6;
1983
1984 filename = (char *)icalmemory_new_buffer(filename_len);
1985 if (!filename) {
1987 goto out;
1988 }
1989
1990 snprintf(filename, filename_len, "%s/%s.ics", get_zone_directory_builtin(), zone->location);
1991
1992 fp = fopen(filename, "r");
1993 icalmemory_free_buffer(filename);
1994 if (!fp) {
1996 goto out;
1997 }
1998
1999 /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
2000 this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
2001 icalerrprintf("** WARNING ** %s: %d %s\n",
2002 __FILE__, __LINE__, icalerror_strerror(icalerrno));
2003 */
2004
2005 parser = icalparser_new();
2006 icalparser_set_gen_data(parser, fp);
2007 comp = icalparser_parse(parser, icaltimezone_load_get_line_fn);
2008 icalparser_free(parser);
2009 fclose(fp);
2010
2011 /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
2013
2014 if (subcomp) {
2015 icalproperty *prop;
2016
2017 /* Ensure expected TZID */
2019 if (prop) {
2020 char *new_tzid;
2021 size_t new_tzid_len;
2022 const char *tzid_prefix = icaltimezone_tzid_prefix();
2023
2024 new_tzid_len = strlen(tzid_prefix) + strlen(zone->location) + 1;
2025 new_tzid = (char *)icalmemory_new_buffer(sizeof(char) * (new_tzid_len + 1));
2026 if (new_tzid) {
2027 snprintf(new_tzid, new_tzid_len, "%s%s", tzid_prefix, zone->location);
2028 icalproperty_set_tzid(prop, new_tzid);
2029 icalmemory_free_buffer(new_tzid);
2030 } else {
2032 }
2033 }
2034
2035 /* Ensure expected Location - it's for cases where one VTIMEZONE is shared
2036 between different locations (like Pacific/Midway is Pacific/Pago_Pago).
2037 This updates the properties, thus when the component is converted to
2038 the string and back to the component the Location will still match. */
2040 if (prop) {
2041 icalproperty_set_location(prop, zone->location);
2042 }
2043
2045 prop;
2047 const char *name;
2048
2049 name = icalproperty_get_x_name(prop);
2050 if (name && !strcasecmp(name, "X-LIC-LOCATION")) {
2051 icalproperty_set_x(prop, zone->location);
2052 break;
2053 }
2054 }
2055 }
2056 } else {
2057 subcomp = icaltimezone_fetch_timezone(zone->location);
2058 }
2059
2060 if (!subcomp) {
2062 goto out;
2063 }
2064
2065 icaltimezone_get_vtimezone_properties(zone, subcomp);
2066
2067 if (use_builtin_tzdata) {
2068 icalcomponent_remove_component(comp, subcomp);
2069 icalcomponent_free(comp);
2070 }
2071
2072out:
2073 if (!icaltimezone_builtin_unlock()) {
2074 return false;
2075 }
2076 return true;
2077}
2078
2082static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data)
2083{
2084 return fgets(s, (int)size, (FILE *)data);
2085}
2086
2087/*
2088 * DEBUGGING
2089 */
2090
2091bool icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp)
2092{
2093 static const char months[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2094 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2095 const icaltimezonechange *zone_change;
2096 size_t change_num;
2097 char buffer[8];
2098
2099 /* Make sure the changes array is expanded up to the given time. */
2100 if (!icaltimezone_ensure_coverage(zone, max_year)) {
2101 return false;
2102 }
2103
2104#ifdef ICALTIMEZONE_DEBUG_PRINT
2105 printf("Num changes: %zu\n", zone->changes->num_elements);
2106#endif
2107
2108 if (!icaltimezone_changes_lock()) {
2109 return false;
2110 }
2111
2112 for (change_num = 0; change_num < zone->changes->num_elements; change_num++) {
2113 zone_change = icalarray_element_at(zone->changes, change_num);
2114
2115 if (zone_change->year > max_year) {
2116 break;
2117 }
2118
2119 fprintf(fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
2120 zone->location,
2121 zone_change->day, months[zone_change->month - 1],
2122 zone_change->year, zone_change->hour, zone_change->minute, zone_change->second);
2123
2124 /* Wall Clock Time offset from UTC. */
2125 format_utc_offset(zone_change->utc_offset, buffer, sizeof(buffer));
2126 fprintf(fp, "\t%s", buffer);
2127
2128 fprintf(fp, "\n");
2129 }
2130
2131 if (!icaltimezone_changes_unlock()) {
2132 return false;
2133 }
2134
2135 return true;
2136}
2137
2143static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size)
2144{
2145 const char *sign = "+";
2146 int hours, minutes, seconds;
2147
2148 if (utc_offset < 0) {
2149 utc_offset = -utc_offset;
2150 sign = "-";
2151 }
2152
2153 hours = utc_offset / 3600;
2154 minutes = (utc_offset % 3600) / 60;
2155 seconds = utc_offset % 60;
2156
2157 /* Sanity check. Standard timezone offsets shouldn't be much more than 12
2158 hours, and daylight saving shouldn't change it by more than a few hours.
2159 (The maximum offset is 15 hours 56 minutes at present.) */
2160 if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 || seconds < 0) {
2161 icalerrprintf("Warning: Strange timezone offset: H:%i M:%i S:%i\n",
2162 hours, minutes, seconds);
2163 }
2164#if defined(__GNUC__) && !defined(__clang__)
2165#pragma GCC diagnostic push
2166#pragma GCC diagnostic ignored "-Wformat-truncation"
2167#endif
2168 if (seconds == 0) {
2169 snprintf(buffer, buffer_size, "%s%02i%02i", sign, hours, minutes);
2170 } else {
2171 snprintf(buffer, buffer_size, "%s%02i%02i%02i", sign, hours, minutes, seconds);
2172 }
2173}
2174#if defined(__GNUC__) && !defined(__clang__)
2175#pragma GCC diagnostic pop
2176#endif
2177
2178static const char *get_zone_directory_builtin(void)
2179{
2180#if !defined(_WIN32)
2181 return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
2182#else
2183 wchar_t wbuffer[1000];
2184
2185#if !defined(_WIN32_WCE)
2186 char buffer[1000], zoneinfodir[1000], dirname[1000];
2187 int used_default;
2188#else
2189 wchar_t zoneinfodir[1000], dirname[1000];
2190#endif
2191 static ICAL_GLOBAL_VAR char *cache = NULL;
2192
2193#if !defined(_WIN32_WCE)
2194 unsigned char *dirslash, *zislash;
2195 const char *zislashp1;
2196#else
2197 wchar_t *dirslash, *zislash;
2198#endif
2199 struct stat st;
2200
2201 if (zone_files_directory) {
2202 return zone_files_directory;
2203 }
2204
2205 /* cppcheck-suppress knownConditionTrueFalse; cppcheck should not be seeing this on non-Windows, yet it does */
2206 if (cache) {
2207 return cache;
2208 }
2209
2210 /* Get the filename of the application */
2211 if (!GetModuleFileNameW(NULL, wbuffer, sizeof(wbuffer) / sizeof(wbuffer[0]))) {
2212 return ZONEINFO_DIRECTORY;
2213 }
2214
2215/*wince supports only unicode*/
2216#if !defined(_WIN32_WCE)
2217 /* Convert to system codepage */
2218 if (!WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2219 NULL, &used_default) ||
2220 used_default) {
2221 /* Failed, try 8.3 format */
2222 if (!GetShortPathNameW(wbuffer, wbuffer,
2223 sizeof(wbuffer) / sizeof(wbuffer[0])) ||
2224 !WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2225 NULL, &used_default) ||
2226 used_default) {
2227 return ZONEINFO_DIRECTORY;
2228 }
2229 }
2230#endif
2231 /* Look for the zoneinfo directory somewhere in the path where
2232 * the app is installed. If the path to the app is
2233 *
2234 * C:\opt\evo-2.6\bin\evolution-2.6.exe
2235 *
2236 * and the compile-time ZONEINFO_DIRECTORY is
2237 *
2238 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
2239 *
2240 * we check the pathnames:
2241 *
2242 * C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2243 * C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
2244 * C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
2245 * C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo <===
2246 * C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
2247 * C:\opt\evo-2.6/zoneinfo
2248 * C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2249 * C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
2250 * C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
2251 * C:\opt/share/evolution-data-server-1.6/zoneinfo
2252 * C:\opt/evolution-data-server-1.6/zoneinfo
2253 * C:\opt/zoneinfo
2254 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2255 * C:/target/evo/share/evolution-data-server-1.6/zoneinfo
2256 * C:/evo/share/evolution-data-server-1.6/zoneinfo
2257 * C:/share/evolution-data-server-1.6/zoneinfo
2258 * C:/evolution-data-server-1.6/zoneinfo
2259 * C:/zoneinfo
2260 *
2261 * In Evolution's case, we would get a match already at the
2262 * fourth pathname check.
2263 */
2264
2265 /* Strip away basename of app .exe first */
2266#if !defined(_WIN32_WCE)
2267 dirslash = _mbsrchr((unsigned char *)buffer, '\\');
2268#else
2269 dirslash = wcsrchr(wbuffer, L'\\');
2270#endif
2271 if (dirslash) {
2272#if !defined(_WIN32_WCE)
2273 *dirslash = '\0';
2274#else
2275 *dirslash = L'\0';
2276#endif
2277 }
2278
2279#if defined(_WIN32_WCE)
2280 while ((dirslash = wcsrchr(wbuffer, '\\'))) {
2281 /* Strip one more directory from app .exe location */
2282 *dirslash = L'\0';
2283
2284 MultiByteToWideChar(CP_ACP, 0, ZONEINFO_DIRECTORY, -1, zoneinfodir, 1000);
2285
2286 while ((zislash = wcschr(zoneinfodir, L'/'))) {
2287 *zislash = L'.';
2288 wcscpy(dirname, wbuffer);
2289 wcscat(dirname, "/");
2290 wcscat(dirname, zislash + 1);
2291 if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2292 cache = wce_wctomb(dirname);
2293 return cache;
2294 }
2295 }
2296 }
2297#else
2298 while ((dirslash = _mbsrchr((unsigned char *)buffer, '\\'))) {
2299 /* Strip one more directory from app .exe location */
2300 *dirslash = '\0';
2301
2302 strcpy(zoneinfodir, ZONEINFO_DIRECTORY);
2303 while ((zislash = _mbschr((unsigned char *)zoneinfodir, '/'))) {
2304 *zislash = '.';
2305 strcpy(dirname, buffer);
2306 strcat(dirname, "/");
2307 zislashp1 = (const char *)(zislash + 1);
2308 strcat(dirname, zislashp1);
2309 if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2310 cache = icalmemory_strdup(dirname);
2311 return cache;
2312 }
2313 }
2314 }
2315#endif
2316 return ZONEINFO_DIRECTORY;
2317#endif
2318}
2319
2321{
2322 if ((zonepath == NULL) || (zonepath[0] == '\0')) {
2323 memset(s_zoneinfopath, 0, MAXPATHLEN);
2324 } else {
2325 strncpy(s_zoneinfopath, zonepath, MAXPATHLEN - 1);
2326 }
2327}
2328
2329static void set_zoneinfopath(void)
2330{
2331 char file_path[MAXPATHLEN] = {0};
2332 const char *fname = ZONES_TAB_SYSTEM_FILENAME;
2333 size_t i, num_zi_search_paths;
2334
2335 /* Search for the zone.tab file in the dir specified by the TZDIR environment */
2336 const char *env_tzdir = getenv("TZDIR");
2337 if (env_tzdir != NULL) {
2338 snprintf(file_path, MAXPATHLEN, "%s/%s", env_tzdir, fname);
2339 if (!access(file_path, F_OK | R_OK)) {
2340 strncpy(s_zoneinfopath, env_tzdir, MAXPATHLEN - 1);
2341 return;
2342 }
2343 }
2344
2345 /* Else, search for zone.tab in a list of well-known locations */
2346 num_zi_search_paths = sizeof(s_zoneinfo_search_paths) / sizeof(s_zoneinfo_search_paths[0]);
2347 for (i = 0; i < num_zi_search_paths; i++) {
2348 snprintf(file_path, MAXPATHLEN, "%s/%s", s_zoneinfo_search_paths[i], fname);
2349 if (!access(file_path, F_OK | R_OK)) {
2350 strncpy(s_zoneinfopath, s_zoneinfo_search_paths[i], MAXPATHLEN - 1);
2351 break;
2352 }
2353 }
2354}
2356{
2357 if (s_zoneinfopath[0] == '\0') {
2358 set_zoneinfopath();
2359 }
2360
2361 return s_zoneinfopath;
2362}
2363
2365{
2366 if (use_builtin_tzdata) {
2367 return get_zone_directory_builtin();
2368 } else {
2370 }
2371}
2372
2374{
2375 if (zone_files_directory) {
2377 }
2378
2379 const size_t len_path = strlen(path) + 1;
2380 zone_files_directory = icalmemory_new_buffer(len_path);
2381
2382 if (zone_files_directory != NULL) {
2383 strncpy(zone_files_directory, path, len_path);
2384 zone_files_directory[len_path - 1] = '\0';
2385 }
2386}
2387
2389{
2390 if (zone_files_directory != NULL) {
2391 icalmemory_free_buffer(zone_files_directory);
2392 zone_files_directory = NULL;
2393 }
2394}
2395
2396void icaltimezone_set_tzid_prefix(const char *new_prefix)
2397{
2398 if (new_prefix) {
2399 strncpy(s_ical_tzid_prefix, new_prefix, BUILTIN_TZID_PREFIX_LEN - 1);
2400 }
2401}
2402
2404{
2405 use_builtin_tzdata = set;
2406}
2407
2409{
2410 return use_builtin_tzdata;
2411}
2412
2413struct observance {
2414 const char *name;
2415 icaltimetype onset;
2416 int offset_from;
2417 int offset_to;
2418};
2419
2420static void check_tombstone(struct observance *tombstone,
2421 struct observance *obs)
2422{
2423 if (icaltime_compare(obs->onset, tombstone->onset) > 0) {
2424 /* onset is closer to cutoff than existing tombstone */
2425 tombstone->name = icalmemory_tmp_copy(obs->name);
2426 tombstone->offset_from = tombstone->offset_to = obs->offset_to;
2427 tombstone->onset = obs->onset;
2428 }
2429}
2430
2431struct rdate {
2432 icalproperty *prop;
2433 struct icaldatetimeperiodtype date;
2434};
2435
2436static int rdate_compare(const void *rdate1, const void *rdate2)
2437{
2438 return icaltime_compare(((struct rdate *)rdate1)->date.time,
2439 ((struct rdate *)rdate2)->date.time);
2440}
2441
2442void icaltimezone_truncate_vtimezone(icalcomponent *vtz,
2443 icaltimetype start, icaltimetype end,
2444 bool ms_compatible)
2445{
2446 icalcomponent *comp, *nextc, *tomb_std = NULL, *tomb_day = NULL;
2447 icalproperty *prop, *proleptic_prop = NULL;
2448 struct observance tombstone;
2449 unsigned need_tomb = (unsigned)!icaltime_is_null_time(start);
2450 unsigned need_tzuntil = (unsigned)!icaltime_is_null_time(end);
2451
2452 if (!need_tomb && !need_tzuntil) {
2453 /* Nothing to do */
2454 return;
2455 }
2456
2457 /* See if we have a proleptic tzname in VTIMEZONE */
2459 prop;
2461 if (!strcmp("X-PROLEPTIC-TZNAME", icalproperty_get_x_name(prop))) {
2462 proleptic_prop = prop;
2463 break;
2464 }
2465 }
2466
2467 memset(&tombstone, 0, sizeof(struct observance));
2468 tombstone.name = icalmemory_tmp_copy(proleptic_prop ? icalproperty_get_x(proleptic_prop) : "LMT");
2469 if (!proleptic_prop ||
2470 !icalproperty_get_parameter_as_string(proleptic_prop, "X-NO-BIG-BANG")) {
2471 tombstone.onset.year = -1;
2472 }
2473
2474 /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
2476 comp; comp = nextc) {
2477 icalproperty *dtstart_prop = NULL, *rrule_prop = NULL;
2478 icalarray *rdates = icalarray_new(sizeof(struct rdate), 10);
2479 icaltimetype dtstart;
2480 struct observance obs;
2481 size_t n;
2482 unsigned trunc_dtstart = 0;
2483 int r;
2484
2486
2487 memset(&obs, 0, sizeof(struct observance));
2488 obs.offset_from = obs.offset_to = INT_MAX;
2489 obs.onset.is_daylight =
2491
2492 /* Grab the properties that we require to expand recurrences */
2494 prop;
2496 switch (icalproperty_isa(prop)) {
2498 obs.name = icalproperty_get_tzname(prop);
2499 break;
2500
2502 dtstart_prop = prop;
2503 obs.onset = dtstart = icalproperty_get_dtstart(prop);
2504 break;
2505
2507 obs.offset_from = icalproperty_get_tzoffsetfrom(prop);
2508 break;
2509
2511 obs.offset_to = icalproperty_get_tzoffsetto(prop);
2512 break;
2513
2515 rrule_prop = prop;
2516 break;
2517
2518 case ICAL_RDATE_PROPERTY: {
2520 struct rdate rdate;
2521
2522 rdate.prop = prop;
2523 rdate.date.time = dtp.time;
2524 rdate.date.period = dtp.period;
2525
2526 icalarray_append(rdates, &rdate);
2527 break;
2528 }
2529
2530 default:
2531 /* ignore all other properties */
2532 break;
2533 }
2534 }
2535
2536 /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
2537 if (!dtstart_prop || !obs.name ||
2538 obs.offset_from == INT_MAX || obs.offset_to == INT_MAX) {
2539 icalarray_free(rdates);
2540 continue;
2541 }
2542
2543 /* Adjust DTSTART observance to UTC */
2544 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2546
2547 /* Check DTSTART vs window close */
2548 if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2549 /* All observances occur on/after window close - remove component */
2551 icalcomponent_free(comp);
2552
2553 /* Nothing else to do */
2554 icalarray_free(rdates);
2555 continue;
2556 }
2557
2558 /* Check DTSTART vs window open */
2559 r = icaltime_compare(obs.onset, start);
2560 if (r < 0) {
2561 /* DTSTART is prior to our window open - check it vs tombstone */
2562 if (need_tomb) {
2563 check_tombstone(&tombstone, &obs);
2564 }
2565
2566 /* Adjust it */
2567 trunc_dtstart = 1;
2568 } else if (r == 0) {
2569 /* DTSTART is on/after our window open */
2570 need_tomb = 0;
2571 }
2572
2573 if (rrule_prop) {
2574 struct icalrecurrencetype *rrule = icalproperty_get_rrule(rrule_prop);
2575 if (rrule) {
2576 unsigned eternal = (unsigned)icaltime_is_null_time(rrule->until);
2577 icalrecur_iterator *ritr = NULL;
2578 unsigned trunc_until = 0;
2579
2580 /* Check RRULE duration */
2581 if (!eternal && icaltime_compare(rrule->until, start) < 0) {
2582 /* RRULE ends prior to our window open -
2583 check UNTIL vs tombstone */
2584 obs.onset = rrule->until;
2585 if (need_tomb) {
2586 check_tombstone(&tombstone, &obs);
2587 }
2588
2589 /* Remove RRULE */
2590 icalcomponent_remove_property(comp, rrule_prop);
2591 icalproperty_free(rrule_prop);
2592 } else {
2593 /* RRULE ends on/after our window open */
2594 if (need_tzuntil &&
2595 (eternal || icaltime_compare(rrule->until, end) >= 0)) {
2596 /* RRULE ends after our window close - need to adjust it */
2597 trunc_until = 1;
2598 }
2599
2600 if (!eternal) {
2601 /* Adjust UNTIL to local time (for iterator) */
2602 icaltime_adjust(&rrule->until, 0, 0, 0, obs.offset_from);
2603 (void)icaltime_set_timezone(&rrule->until, NULL);
2604 }
2605
2606 ritr = icalrecur_iterator_new(rrule, dtstart);
2607
2608 if (ritr && trunc_dtstart) {
2609 /* Bump RRULE start to 1 year prior to our window open */
2610 icaltimetype newstart = dtstart;
2611 newstart.year = start.year - 1;
2612 newstart.month = start.month;
2613 newstart.day = start.day;
2614 icalrecur_iterator_set_start(ritr, newstart);
2615 }
2616 }
2617
2618 /* Process any RRULE observances within our window */
2619 if (ritr) {
2620 icaltimetype recur, prev_onset;
2621
2622 while (!icaltime_is_null_time(recur = icalrecur_iterator_next(ritr))) {
2623 unsigned ydiff;
2624
2625 obs.onset = recur;
2626
2627 /* Adjust observance to UTC */
2628 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2629 (void)icaltime_set_timezone(&obs.onset,
2631
2632 if (trunc_until && icaltime_compare(obs.onset, end) >= 0) {
2633 /* Observance is on/after window close */
2634
2635 /* Check if DSTART is within 1yr of prev onset */
2636 ydiff = (unsigned)(prev_onset.year - dtstart.year);
2637 if (ydiff <= 1) {
2638 /* Remove RRULE */
2639 icalcomponent_remove_property(comp, rrule_prop);
2640 icalproperty_free(rrule_prop);
2641
2642 if (ydiff) {
2643 /* Add previous onset as RDATE */
2644 struct icaldatetimeperiodtype rdate;
2645 rdate.time = prev_onset;
2646 rdate.period = icalperiodtype_null_period();
2647
2648 prop = icalproperty_new_rdate(rdate);
2649 icalcomponent_add_property(comp, prop);
2650 }
2651 } else {
2652 /* Set UNTIL to previous onset */
2653 rrule->until = prev_onset;
2654 icalproperty_set_rrule(rrule_prop, rrule);
2655 }
2656
2657 /* We're done */
2658 break;
2659 }
2660
2661 /* Check observance vs our window open */
2662 r = icaltime_compare(obs.onset, start);
2663 if (r < 0) {
2664 /* Observance is prior to our window open -
2665 check it vs tombstone */
2666 if (ms_compatible) {
2667 /* XXX We don't want to move DTSTART of the RRULE
2668 as Outlook/Exchange doesn't appear to like
2669 truncating the frontend of RRULEs */
2670 need_tomb = 0;
2671 trunc_dtstart = 0;
2672 if (proleptic_prop) {
2674 proleptic_prop);
2675 icalproperty_free(proleptic_prop);
2676 proleptic_prop = NULL;
2677 }
2678 }
2679 if (need_tomb) {
2680 check_tombstone(&tombstone, &obs);
2681 }
2682 } else {
2683 /* Observance is on/after our window open */
2684 if (r == 0) {
2685 need_tomb = 0;
2686 }
2687
2688 if (trunc_dtstart) {
2689 /* Make this observance the new DTSTART */
2690 icalproperty_set_dtstart(dtstart_prop, recur);
2691 dtstart = obs.onset;
2692 trunc_dtstart = 0;
2693
2694 /* Check if new DSTART is within 1yr of UNTIL */
2695 ydiff = (unsigned)(rrule->until.year - recur.year);
2696 if (!trunc_until && ydiff <= 1) {
2697 /* Remove RRULE */
2698 icalcomponent_remove_property(comp, rrule_prop);
2699 icalproperty_free(rrule_prop);
2700
2701 if (ydiff) {
2702 /* Add UNTIL as RDATE */
2703 struct icaldatetimeperiodtype rdate;
2704 rdate.time = rrule->until;
2705 rdate.period = icalperiodtype_null_period();
2706
2707 prop = icalproperty_new_rdate(rdate);
2708 icalcomponent_add_property(comp, prop);
2709 }
2710 }
2711 }
2712
2713 if (!trunc_until) {
2714 /* We're done */
2715 break;
2716 }
2717
2718 /* Check if observance is outside 1yr of window close */
2719 ydiff = (unsigned)(end.year - recur.year);
2720 if (ydiff > 1) {
2721 /* Bump RRULE to restart at 1 year prior to our window close */
2722 icaltimetype newstart = recur;
2723 newstart.year = end.year - 1;
2724 newstart.month = end.month;
2725 newstart.day = end.day;
2726 icalrecur_iterator_set_start(ritr, newstart);
2727 }
2728 }
2729 prev_onset = obs.onset;
2730 }
2732 }
2733 }
2734 }
2735
2736 /* Sort the RDATEs by onset */
2737 icalarray_sort(rdates, &rdate_compare);
2738
2739 /* Check RDATEs */
2740 for (n = 0; n < rdates->num_elements; n++) {
2741 struct rdate *rdate = icalarray_element_at(rdates, n);
2742
2743 /* RDATEs with a DATE value inherit the time from the DTSTART. */
2744 if (icaltime_is_date(rdate->date.time)) {
2745 rdate->date.time.hour = dtstart.hour;
2746 rdate->date.time.minute = dtstart.minute;
2747 rdate->date.time.second = dtstart.second;
2748 }
2749
2750 if (n == 0 && icaltime_compare(rdate->date.time, dtstart) == 0) {
2751 /* RDATE is same as DTSTART - remove it */
2752 icalcomponent_remove_property(comp, rdate->prop);
2753 icalproperty_free(rdate->prop);
2754 continue;
2755 }
2756
2757 obs.onset = rdate->date.time;
2758
2759 /* Adjust observance to UTC */
2760 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2762
2763 if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2764 /* RDATE is after our window close - remove it */
2765 icalcomponent_remove_property(comp, rdate->prop);
2766 icalproperty_free(rdate->prop);
2767
2768 continue;
2769 }
2770
2771 r = icaltime_compare(obs.onset, start);
2772 if (r < 0) {
2773 /* RDATE is prior to window open - check it vs tombstone */
2774 if (need_tomb) {
2775 check_tombstone(&tombstone, &obs);
2776 }
2777
2778 /* Remove it */
2779 icalcomponent_remove_property(comp, rdate->prop);
2780 icalproperty_free(rdate->prop);
2781 } else {
2782 /* RDATE is on/after our window open */
2783 if (r == 0) {
2784 need_tomb = 0;
2785 }
2786
2787 if (trunc_dtstart) {
2788 /* Make this RDATE the new DTSTART */
2789 icalproperty_set_dtstart(dtstart_prop,
2790 rdate->date.time);
2791 trunc_dtstart = 0;
2792
2793 icalcomponent_remove_property(comp, rdate->prop);
2794 icalproperty_free(rdate->prop);
2795 }
2796 }
2797 }
2798 icalarray_free(rdates);
2799
2800 /* Final check */
2801 if (trunc_dtstart) {
2802 /* All observances in comp occur prior to window open, remove it
2803 unless we haven't saved a tombstone comp of this type yet */
2805 if (!tomb_day) {
2806 tomb_day = comp;
2807 comp = NULL;
2808 }
2809 } else if (!tomb_std) {
2810 tomb_std = comp;
2811 comp = NULL;
2812 }
2813
2814 if (comp) {
2816 icalcomponent_free(comp);
2817 }
2818 }
2819 }
2820
2821 if (need_tomb && !icaltime_is_null_time(tombstone.onset)) {
2822 /* Need to add tombstone component/observance starting at window open
2823 as long as its not prior to start of TZ data */
2824 icalcomponent *tomb;
2825 icalproperty *tomb_prop, *nextp;
2826
2827 /* Determine which tombstone component we need */
2828 if (tombstone.onset.is_daylight) {
2829 tomb = tomb_day;
2830 tomb_day = NULL;
2831 } else {
2832 tomb = tomb_std;
2833 tomb_std = NULL;
2834 }
2835
2836 /* Set property values on our tombstone */
2838 tomb_prop; tomb_prop = nextp) {
2840
2841 switch (icalproperty_isa(tomb_prop)) {
2843 icalproperty_set_tzname(tomb_prop, tombstone.name);
2844 break;
2846 icalproperty_set_tzoffsetfrom(tomb_prop, tombstone.offset_from);
2847 break;
2849 icalproperty_set_tzoffsetto(tomb_prop, tombstone.offset_to);
2850 break;
2852 /* Adjust window open to local time */
2853 icaltime_adjust(&start, 0, 0, 0, tombstone.offset_from);
2854 (void)icaltime_set_timezone(&start, NULL);
2855
2856 icalproperty_set_dtstart(tomb_prop, start);
2857 break;
2858 default:
2859 icalcomponent_remove_property(tomb, tomb_prop);
2860 icalproperty_free(tomb_prop);
2861 break;
2862 }
2863 }
2864
2865 /* Remove X-PROLEPTIC-TZNAME as it no longer applies */
2866 if (proleptic_prop) {
2867 icalcomponent_remove_property(vtz, proleptic_prop);
2868 icalproperty_free(proleptic_prop);
2869 }
2870 }
2871
2872 /* Remove any unused tombstone components */
2873 if (tomb_std) {
2874 icalcomponent_remove_component(vtz, tomb_std);
2875 icalcomponent_free(tomb_std);
2876 }
2877 if (tomb_day) {
2878 icalcomponent_remove_component(vtz, tomb_day);
2879 icalcomponent_free(tomb_day);
2880 }
2881
2882 if (need_tzuntil) {
2883 /* Add TZUNTIL to VTIMEZONE */
2885
2886 if (prop) {
2887 icalproperty_set_tzuntil(prop, end);
2888 } else {
2890 }
2891 }
2892}
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:4108
struct icalrecurrencetype * icalrecurrencetype_clone(struct icalrecurrencetype *recur)
Definition icalrecur.c:832
void icalrecur_iterator_free(icalrecur_iterator *impl)
Definition icalrecur.c:2452
void icalrecurrencetype_unref(struct icalrecurrencetype *recur)
Definition icalrecur.c:790
icalrecur_iterator * icalrecur_iterator_new(struct icalrecurrencetype *rule, struct icaltimetype dtstart)
Definition icalrecur.c:2324
struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *impl)
Definition icalrecur.c:3697
bool icaltime_is_date(const struct icaltimetype t)
Definition icaltime.c:625
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:476
bool icaltime_is_utc(const struct icaltimetype t)
Definition icaltime.c:630
bool icaltime_is_null_time(const struct icaltimetype t)
Definition icaltime.c:635
int icaltime_compare(const struct icaltimetype a_in, const struct icaltimetype b_in)
Definition icaltime.c:644
struct icaltimetype icaltime_set_timezone(struct icaltimetype *t, const icaltimezone *zone)
Definition icaltime.c:888
void icaltime_adjust(struct icaltimetype *tt, const int days, const int hours, const int minutes, const int seconds)
Definition icaltime.c:760
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