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
368 prop = icalcomponent_get_first_property(component, ICAL_TZID_PROPERTY);
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
401 prop = icalcomponent_get_first_property(component, ICAL_LOCATION_PROPERTY);
402 if (prop) {
403 location = icalproperty_get_location(prop);
404 if (location) {
405 return icalmemory_strdup(location);
406 }
407 }
408
409 prop = icalcomponent_get_first_property(component, ICAL_X_PROPERTY);
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 }
418 prop = icalcomponent_get_next_property(component, ICAL_X_PROPERTY);
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)) {
449 case ICAL_TZNAME_PROPERTY:
450 current_tzname = icalproperty_get_tzname(prop);
451 break;
452
453 case ICAL_DTSTART_PROPERTY:
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
461 case ICAL_RDATE_PROPERTY:
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
473 prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
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. */
628 prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
629 while (prop) {
630 switch (icalproperty_isa(prop)) {
631 case ICAL_DTSTART_PROPERTY:
632 dtstart = icalproperty_get_dtstart(prop);
633 found_dtstart = 1;
634 break;
635 case ICAL_TZOFFSETTO_PROPERTY:
636 change.utc_offset = icalproperty_get_tzoffsetto(prop);
637 /*printf ("Found TZOFFSETTO: %i\n", change.utc_offset); */
638 found_tzoffsetto = 1;
639 break;
640 case ICAL_TZOFFSETFROM_PROPERTY:
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;
645 case ICAL_RDATE_PROPERTY:
646 has_rdate = 1;
647 break;
648 case ICAL_RRULE_PROPERTY:
649 has_rrule = 1;
650 break;
651 default:
652 /* Just ignore any other properties. */
653 break;
654 }
655
656 prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
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. */
703 prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
704 while (prop && (has_rdate || has_rrule)) {
705#ifdef ICALTIMEZONE_DEBUG_PRINT
706 printf("Expanding property...\n");
707#endif
708 switch (icalproperty_isa(prop)) {
709 case ICAL_RDATE_PROPERTY:
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;
741 case ICAL_RRULE_PROPERTY:
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
818 prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
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 if (len == 5) {
1673 sscanf(coord + 1, "%2d%2d", degrees, minutes);
1674 } else if (len == 6) {
1675 sscanf(coord + 1, "%3d%2d", degrees, minutes);
1676 } else if (len == 7) {
1677 sscanf(coord + 1, "%2d%2d%2d", degrees, minutes, seconds);
1678 } else if (len == 8) {
1679 sscanf(coord + 1, "%3d%2d%2d", degrees, minutes, seconds);
1680 } else {
1681 icalerrprintf("Invalid coordinate: %s\n", coord);
1682 return true;
1683 }
1684
1685 if (coord[0] == '-') {
1686 *degrees = -*degrees;
1687 }
1688
1689 return false;
1690}
1691
1692static bool fetch_lat_long_from_string(const char *str,
1693 int *latitude_degrees, int *latitude_minutes,
1694 int *latitude_seconds,
1695 int *longitude_degrees, int *longitude_minutes,
1696 int *longitude_seconds,
1697 char *location, size_t len_location)
1698{
1699 size_t len;
1700 const char *loc, *temp;
1701 char *sptr, *lat;
1702 const char *lon;
1703
1704 if (!location || !len_location) {
1705 return false;
1706 }
1707
1708 /* We need to parse the latitude/longitude coordinates and location fields */
1709 const size_t len_str = strlen(str);
1710 size_t i = 0;
1711 sptr = (char *)str;
1712 while ((*sptr != '\t') && (*sptr != '\0')) {
1713 sptr++;
1714 i++;
1715 }
1716 if (i >= len_str) {
1717 return false;
1718 }
1719 temp = ++sptr;
1720 while (*sptr != '\t' && *sptr != '\0') {
1721 sptr++;
1722 }
1723 len = (size_t)(ptrdiff_t)(sptr - temp);
1724 if (len == 0) {
1725 return false;
1726 }
1727 lat = (char *)icalmemory_new_buffer(len + 1);
1728 if (!lat) {
1729 return false;
1730 }
1731 memset(lat, '\0', len + 1);
1732 strncpy(lat, temp, len + 1);
1733 lat[len] = '\0';
1734 while ((*sptr != '\t') && (*sptr != '\0')) {
1735 sptr++;
1736 }
1737 loc = ++sptr;
1738 while (!isspace((int)(*sptr)) && (*sptr != '\0')) {
1739 sptr++;
1740 }
1741 len = (size_t)(ptrdiff_t)(sptr - loc);
1742 if (len >= len_location) {
1743 len = len_location - 1;
1744 }
1745 strncpy(location, loc, len);
1746 location[len] = '\0';
1747
1748 lon = lat + 1;
1749 while (*lon != '\0' && *lon != '+' && *lon != '-') {
1750 lon++;
1751 }
1752
1753 if (parse_coord(lat, (int)(lon - lat),
1754 latitude_degrees,
1755 latitude_minutes,
1756 latitude_seconds) == 1 ||
1757 parse_coord(lon, (int)strlen(lon),
1758 longitude_degrees, longitude_minutes, longitude_seconds) == 1) {
1760 return true;
1761 }
1762
1764
1765 return false;
1766}
1767
1778static void icaltimezone_parse_zone_tab(void)
1779{
1780 const char *zonedir, *zonetab;
1781 char *filename;
1782 FILE *fp;
1783 size_t filename_len;
1784
1785 icalerror_assert(builtin_timezones == NULL, "Parsing zones.tab file multiple times");
1786
1787 icalarray *timezones = icalarray_new(sizeof(icaltimezone), 1024);
1788
1789 if (!use_builtin_tzdata) {
1791 zonetab = ZONES_TAB_SYSTEM_FILENAME;
1792 } else {
1793 zonedir = get_zone_directory_builtin();
1794 zonetab = ZONES_TAB_FILENAME;
1795 }
1796
1797 filename_len = 0;
1798 if (zonedir) {
1799 filename_len = strlen(zonedir);
1800 }
1801
1802 icalerror_assert(filename_len > 0, "Unable to locate a zoneinfo dir");
1803 if (filename_len == 0) {
1804 // Set an empty builtin_timezones array if there's an error
1805 builtin_timezones = timezones;
1806
1808 return;
1809 }
1810
1811 filename_len += strlen(zonetab);
1812 filename_len += 2; /* for dir separator and final '\0' */
1813
1814 filename = (char *)icalmemory_new_buffer(filename_len);
1815 if (!filename) {
1816 // Set an empty builtin_timezones array if there's an error
1817 builtin_timezones = timezones;
1818
1820 return;
1821 }
1822 snprintf(filename, filename_len, "%s/%s", zonedir, zonetab);
1823
1824 fp = fopen(filename, "r");
1825 icalerror_assert(fp, "Cannot open the zonetab file for reading");
1826 if (!fp) {
1827 // Set an empty builtin_timezones array if there's an error
1828 builtin_timezones = timezones;
1829
1831 icalmemory_free_buffer(filename);
1832 return;
1833 }
1834
1835#if !defined(__clang_analyzer__) // avoid unix.BlockInCriticalSection
1836 char buf[1024];
1837 while (!feof(fp) && !ferror(fp) && fgets(buf, (int)sizeof(buf), fp)) {
1838 char location[1024] = {0}; /* Stores the city name when parsing buf. */
1839 int longitude_degrees, longitude_minutes, longitude_seconds;
1840 int latitude_degrees, latitude_minutes, latitude_seconds;
1841
1842 if (buf[0] == '\0') {
1843 break;
1844 }
1845 if (*buf == '#') {
1846 continue;
1847 }
1848
1849 if (use_builtin_tzdata) {
1850 /* The format of each line is: "[ latitude longitude ] location". */
1851 if (buf[0] != '+' && buf[0] != '-') {
1852 latitude_degrees = longitude_degrees = 360;
1853 latitude_minutes = longitude_minutes = 0;
1854 latitude_seconds = longitude_seconds = 0;
1855 if (sscanf(buf, "%1000s", location) != 1) { /*limit location to 1000chars */
1856 /*increase as needed */
1857 /*see location and buf declarations */
1858 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1859 continue;
1860 }
1861 } else if (sscanf(buf, "%4d%2d%2d %4d%2d%2d %1000s", /*limit location to 1000chars */
1862 /*increase as needed */
1863 /*see location and buf declarations */
1864 &latitude_degrees, &latitude_minutes,
1865 &latitude_seconds,
1866 &longitude_degrees, &longitude_minutes,
1867 &longitude_seconds, location) != 7) {
1868 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1869 continue;
1870 }
1871 } else {
1872 if (fetch_lat_long_from_string(buf, &latitude_degrees, &latitude_minutes,
1873 &latitude_seconds,
1874 &longitude_degrees, &longitude_minutes,
1875 &longitude_seconds, location, sizeof(location))) {
1876 icalerrprintf("%s: Invalid timezone description line: %s\n", filename, buf);
1877 continue;
1878 }
1879 if (location[0] == '\0') {
1880 icalerrprintf("%s: Invalid timezone location: %s\n", filename, buf);
1881 continue;
1882 }
1883 }
1885 icaltimezone_init(&zone);
1886 /* coverity[resource_leak] */
1887 zone.location = icalmemory_strdup(location);
1888
1889 if (latitude_degrees >= 0) {
1890 zone.latitude =
1891 (double)latitude_degrees +
1892 (double)latitude_minutes / 60 +
1893 (double)latitude_seconds / 3600;
1894 } else {
1895 zone.latitude =
1896 (double)latitude_degrees -
1897 (double)latitude_minutes / 60 -
1898 (double)latitude_seconds / 3600;
1899 }
1900
1901 if (longitude_degrees >= 0) {
1902 zone.longitude =
1903 (double)longitude_degrees +
1904 (double)longitude_minutes / 60 +
1905 (double)longitude_seconds / 3600;
1906 } else {
1907 zone.longitude =
1908 (double)longitude_degrees -
1909 (double)longitude_minutes / 60 -
1910 (double)longitude_seconds / 3600;
1911 }
1912
1913 icalarray_append(timezones, &zone);
1914
1915#ifdef ICALTIMEZONE_DEBUG_PRINT
1916 printf("Found zone: %s %f %f\n", location, zone.latitude, zone.longitude);
1917#endif
1918 }
1919#endif // __clang_analyzer__
1920
1921 builtin_timezones = timezones;
1922
1923 icalmemory_free_buffer(filename);
1924 fclose(fp);
1925}
1926
1930static bool icaltimezone_load_builtin_timezone(icaltimezone *zone)
1931{
1932 icalcomponent *comp = 0, *subcomp;
1933
1934 /* Prevent blocking on mutex lock caused by recursive calls */
1935 if (zone->component) {
1936 return true;
1937 }
1938
1939 if (!icaltimezone_builtin_lock()) {
1940 return false;
1941 }
1942
1943 /* Try again, maybe it had been set by other thread while waiting for the lock */
1944 if (zone->component) {
1945 if (!icaltimezone_builtin_unlock()) {
1946 return false;
1947 }
1948 return true;
1949 }
1950
1951 /* If the location isn't set, it isn't a builtin timezone. */
1952 if (!zone->location || !zone->location[0]) {
1953 if (!icaltimezone_builtin_unlock()) {
1954 return false;
1955 }
1956 return true;
1957 }
1958
1959 if (use_builtin_tzdata) {
1960 char *filename;
1961 size_t filename_len;
1962 FILE *fp;
1963 icalparser *parser;
1964
1965 filename_len = strlen(get_zone_directory_builtin()) + strlen(zone->location) + 6;
1966
1967 filename = (char *)icalmemory_new_buffer(filename_len);
1968 if (!filename) {
1970 goto out;
1971 }
1972
1973 snprintf(filename, filename_len, "%s/%s.ics", get_zone_directory_builtin(), zone->location);
1974
1975 fp = fopen(filename, "r");
1976 icalmemory_free_buffer(filename);
1977 if (!fp) {
1979 goto out;
1980 }
1981
1982 /* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1983 this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1984 icalerrprintf("** WARNING ** %s: %d %s\n",
1985 __FILE__, __LINE__, icalerror_strerror(icalerrno));
1986 */
1987
1988 parser = icalparser_new();
1989 icalparser_set_gen_data(parser, fp);
1990 comp = icalparser_parse(parser, icaltimezone_load_get_line_fn);
1991 icalparser_free(parser);
1992 fclose(fp);
1993
1994 /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1996
1997 if (subcomp) {
1998 icalproperty *prop;
1999
2000 /* Ensure expected TZID */
2001 prop = icalcomponent_get_first_property(subcomp, ICAL_TZID_PROPERTY);
2002 if (prop) {
2003 char *new_tzid;
2004 size_t new_tzid_len;
2005 const char *tzid_prefix = icaltimezone_tzid_prefix();
2006
2007 new_tzid_len = strlen(tzid_prefix) + strlen(zone->location) + 1;
2008 new_tzid = (char *)icalmemory_new_buffer(sizeof(char) * (new_tzid_len + 1));
2009 if (new_tzid) {
2010 snprintf(new_tzid, new_tzid_len, "%s%s", tzid_prefix, zone->location);
2011 icalproperty_set_tzid(prop, new_tzid);
2012 icalmemory_free_buffer(new_tzid);
2013 } else {
2015 }
2016 }
2017
2018 /* Ensure expected Location - it's for cases where one VTIMEZONE is shared
2019 between different locations (like Pacific/Midway is Pacific/Pago_Pago).
2020 This updates the properties, thus when the component is converted to
2021 the string and back to the component the Location will still match. */
2022 prop = icalcomponent_get_first_property(subcomp, ICAL_LOCATION_PROPERTY);
2023 if (prop) {
2024 icalproperty_set_location(prop, zone->location);
2025 }
2026
2027 for (prop = icalcomponent_get_first_property(subcomp, ICAL_X_PROPERTY);
2028 prop;
2029 prop = icalcomponent_get_next_property(subcomp, ICAL_X_PROPERTY)) {
2030 const char *name;
2031
2032 name = icalproperty_get_x_name(prop);
2033 if (name && !strcasecmp(name, "X-LIC-LOCATION")) {
2034 icalproperty_set_x(prop, zone->location);
2035 break;
2036 }
2037 }
2038 }
2039 } else {
2040 subcomp = icaltimezone_fetch_timezone(zone->location);
2041 }
2042
2043 if (!subcomp) {
2045 goto out;
2046 }
2047
2048 icaltimezone_get_vtimezone_properties(zone, subcomp);
2049
2050 if (use_builtin_tzdata) {
2051 icalcomponent_remove_component(comp, subcomp);
2052 icalcomponent_free(comp);
2053 }
2054
2055out:
2056 if (!icaltimezone_builtin_unlock()) {
2057 return false;
2058 }
2059 return true;
2060}
2061
2065static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data)
2066{
2067 return fgets(s, (int)size, (FILE *)data);
2068}
2069
2070/*
2071 * DEBUGGING
2072 */
2073
2074bool icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp)
2075{
2076 static const char months[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2077 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2078 const icaltimezonechange *zone_change;
2079 size_t change_num;
2080 char buffer[8];
2081
2082 /* Make sure the changes array is expanded up to the given time. */
2083 if (!icaltimezone_ensure_coverage(zone, max_year)) {
2084 return false;
2085 }
2086
2087#ifdef ICALTIMEZONE_DEBUG_PRINT
2088 printf("Num changes: %zu\n", zone->changes->num_elements);
2089#endif
2090
2091 if (!icaltimezone_changes_lock()) {
2092 return false;
2093 }
2094
2095 for (change_num = 0; change_num < zone->changes->num_elements; change_num++) {
2096 zone_change = icalarray_element_at(zone->changes, change_num);
2097
2098 if (zone_change->year > max_year) {
2099 break;
2100 }
2101
2102 fprintf(fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
2103 zone->location,
2104 zone_change->day, months[zone_change->month - 1],
2105 zone_change->year, zone_change->hour, zone_change->minute, zone_change->second);
2106
2107 /* Wall Clock Time offset from UTC. */
2108 format_utc_offset(zone_change->utc_offset, buffer, sizeof(buffer));
2109 fprintf(fp, "\t%s", buffer);
2110
2111 fprintf(fp, "\n");
2112 }
2113
2114 if (!icaltimezone_changes_unlock()) {
2115 return false;
2116 }
2117
2118 return true;
2119}
2120
2126static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size)
2127{
2128 const char *sign = "+";
2129 int hours, minutes, seconds;
2130
2131 if (utc_offset < 0) {
2132 utc_offset = -utc_offset;
2133 sign = "-";
2134 }
2135
2136 hours = utc_offset / 3600;
2137 minutes = (utc_offset % 3600) / 60;
2138 seconds = utc_offset % 60;
2139
2140 /* Sanity check. Standard timezone offsets shouldn't be much more than 12
2141 hours, and daylight saving shouldn't change it by more than a few hours.
2142 (The maximum offset is 15 hours 56 minutes at present.) */
2143 if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 || seconds < 0) {
2144 icalerrprintf("Warning: Strange timezone offset: H:%i M:%i S:%i\n",
2145 hours, minutes, seconds);
2146 }
2147#if defined(__GNUC__) && !defined(__clang__)
2148#pragma GCC diagnostic push
2149#pragma GCC diagnostic ignored "-Wformat-truncation"
2150#endif
2151 if (seconds == 0) {
2152 snprintf(buffer, buffer_size, "%s%02i%02i", sign, hours, minutes);
2153 } else {
2154 snprintf(buffer, buffer_size, "%s%02i%02i%02i", sign, hours, minutes, seconds);
2155 }
2156}
2157#if defined(__GNUC__) && !defined(__clang__)
2158#pragma GCC diagnostic pop
2159#endif
2160
2161static const char *get_zone_directory_builtin(void)
2162{
2163#if !defined(_WIN32)
2164 return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
2165#else
2166 wchar_t wbuffer[1000];
2167
2168#if !defined(_WIN32_WCE)
2169 char buffer[1000], zoneinfodir[1000], dirname[1000];
2170 int used_default;
2171#else
2172 wchar_t zoneinfodir[1000], dirname[1000];
2173#endif
2174 static ICAL_GLOBAL_VAR char *cache = NULL;
2175
2176#if !defined(_WIN32_WCE)
2177 unsigned char *dirslash, *zislash;
2178 const char *zislashp1;
2179#else
2180 wchar_t *dirslash, *zislash;
2181#endif
2182 struct stat st;
2183
2184 if (zone_files_directory) {
2185 return zone_files_directory;
2186 }
2187
2188 /* cppcheck-suppress knownConditionTrueFalse; cppcheck should not be seeing this on non-Windows, yet it does */
2189 if (cache) {
2190 return cache;
2191 }
2192
2193 /* Get the filename of the application */
2194 if (!GetModuleFileNameW(NULL, wbuffer, sizeof(wbuffer) / sizeof(wbuffer[0]))) {
2195 return ZONEINFO_DIRECTORY;
2196 }
2197
2198/*wince supports only unicode*/
2199#if !defined(_WIN32_WCE)
2200 /* Convert to system codepage */
2201 if (!WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2202 NULL, &used_default) ||
2203 used_default) {
2204 /* Failed, try 8.3 format */
2205 if (!GetShortPathNameW(wbuffer, wbuffer,
2206 sizeof(wbuffer) / sizeof(wbuffer[0])) ||
2207 !WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer),
2208 NULL, &used_default) ||
2209 used_default) {
2210 return ZONEINFO_DIRECTORY;
2211 }
2212 }
2213#endif
2214 /* Look for the zoneinfo directory somewhere in the path where
2215 * the app is installed. If the path to the app is
2216 *
2217 * C:\opt\evo-2.6\bin\evolution-2.6.exe
2218 *
2219 * and the compile-time ZONEINFO_DIRECTORY is
2220 *
2221 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo,
2222 *
2223 * we check the pathnames:
2224 *
2225 * C:\opt\evo-2.6/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2226 * C:\opt\evo-2.6/target/evo/share/evolution-data-server-1.6/zoneinfo
2227 * C:\opt\evo-2.6/evo/share/evolution-data-server-1.6/zoneinfo
2228 * C:\opt\evo-2.6/share/evolution-data-server-1.6/zoneinfo <===
2229 * C:\opt\evo-2.6/evolution-data-server-1.6/zoneinfo
2230 * C:\opt\evo-2.6/zoneinfo
2231 * C:\opt/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2232 * C:\opt/target/evo/share/evolution-data-server-1.6/zoneinfo
2233 * C:\opt/evo/share/evolution-data-server-1.6/zoneinfo
2234 * C:\opt/share/evolution-data-server-1.6/zoneinfo
2235 * C:\opt/evolution-data-server-1.6/zoneinfo
2236 * C:\opt/zoneinfo
2237 * C:/devel/target/evo/share/evolution-data-server-1.6/zoneinfo
2238 * C:/target/evo/share/evolution-data-server-1.6/zoneinfo
2239 * C:/evo/share/evolution-data-server-1.6/zoneinfo
2240 * C:/share/evolution-data-server-1.6/zoneinfo
2241 * C:/evolution-data-server-1.6/zoneinfo
2242 * C:/zoneinfo
2243 *
2244 * In Evolution's case, we would get a match already at the
2245 * fourth pathname check.
2246 */
2247
2248 /* Strip away basename of app .exe first */
2249#if !defined(_WIN32_WCE)
2250 dirslash = _mbsrchr((unsigned char *)buffer, '\\');
2251#else
2252 dirslash = wcsrchr(wbuffer, L'\\');
2253#endif
2254 if (dirslash) {
2255#if !defined(_WIN32_WCE)
2256 *dirslash = '\0';
2257#else
2258 *dirslash = L'\0';
2259#endif
2260 }
2261
2262#if defined(_WIN32_WCE)
2263 while ((dirslash = wcsrchr(wbuffer, '\\'))) {
2264 /* Strip one more directory from app .exe location */
2265 *dirslash = L'\0';
2266
2267 MultiByteToWideChar(CP_ACP, 0, ZONEINFO_DIRECTORY, -1, zoneinfodir, 1000);
2268
2269 while ((zislash = wcschr(zoneinfodir, L'/'))) {
2270 *zislash = L'.';
2271 wcscpy(dirname, wbuffer);
2272 wcscat(dirname, "/");
2273 wcscat(dirname, zislash + 1);
2274 if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2275 cache = wce_wctomb(dirname);
2276 return cache;
2277 }
2278 }
2279 }
2280#else
2281 while ((dirslash = _mbsrchr((unsigned char *)buffer, '\\'))) {
2282 /* Strip one more directory from app .exe location */
2283 *dirslash = '\0';
2284
2285 strcpy(zoneinfodir, ZONEINFO_DIRECTORY);
2286 while ((zislash = _mbschr((unsigned char *)zoneinfodir, '/'))) {
2287 *zislash = '.';
2288 strcpy(dirname, buffer);
2289 strcat(dirname, "/");
2290 zislashp1 = (const char *)(zislash + 1);
2291 strcat(dirname, zislashp1);
2292 if (stat(dirname, &st) == 0 && S_ISDIR(st.st_mode)) {
2293 cache = icalmemory_strdup(dirname);
2294 return cache;
2295 }
2296 }
2297 }
2298#endif
2299 return ZONEINFO_DIRECTORY;
2300#endif
2301}
2302
2304{
2305 if ((zonepath == NULL) || (zonepath[0] == '\0')) {
2306 memset(s_zoneinfopath, 0, MAXPATHLEN);
2307 } else {
2308 strncpy(s_zoneinfopath, zonepath, MAXPATHLEN - 1);
2309 }
2310}
2311
2312static void set_zoneinfopath(void)
2313{
2314 char file_path[MAXPATHLEN] = {0};
2315 const char *fname = ZONES_TAB_SYSTEM_FILENAME;
2316 size_t i, num_zi_search_paths;
2317
2318 /* Search for the zone.tab file in the dir specified by the TZDIR environment */
2319 const char *env_tzdir = getenv("TZDIR");
2320 if (env_tzdir != NULL) {
2321 snprintf(file_path, MAXPATHLEN, "%s/%s", env_tzdir, fname);
2322 if (!access(file_path, F_OK | R_OK)) {
2323 strncpy(s_zoneinfopath, env_tzdir, MAXPATHLEN - 1);
2324 return;
2325 }
2326 }
2327
2328 /* Else, search for zone.tab in a list of well-known locations */
2329 num_zi_search_paths = sizeof(s_zoneinfo_search_paths) / sizeof(s_zoneinfo_search_paths[0]);
2330 for (i = 0; i < num_zi_search_paths; i++) {
2331 snprintf(file_path, MAXPATHLEN, "%s/%s", s_zoneinfo_search_paths[i], fname);
2332 if (!access(file_path, F_OK | R_OK)) {
2333 strncpy(s_zoneinfopath, s_zoneinfo_search_paths[i], MAXPATHLEN - 1);
2334 break;
2335 }
2336 }
2337}
2339{
2340 if (s_zoneinfopath[0] == '\0') {
2341 set_zoneinfopath();
2342 }
2343
2344 return s_zoneinfopath;
2345}
2346
2348{
2349 if (use_builtin_tzdata) {
2350 return get_zone_directory_builtin();
2351 } else {
2353 }
2354}
2355
2357{
2358 if (zone_files_directory) {
2360 }
2361
2362 const size_t len_path = strlen(path) + 1;
2363 zone_files_directory = icalmemory_new_buffer(len_path);
2364
2365 if (zone_files_directory != NULL) {
2366 strncpy(zone_files_directory, path, len_path);
2367 zone_files_directory[len_path - 1] = '\0';
2368 }
2369}
2370
2372{
2373 if (zone_files_directory != NULL) {
2374 icalmemory_free_buffer(zone_files_directory);
2375 zone_files_directory = NULL;
2376 }
2377}
2378
2379void icaltimezone_set_tzid_prefix(const char *new_prefix)
2380{
2381 if (new_prefix) {
2382 strncpy(s_ical_tzid_prefix, new_prefix, BUILTIN_TZID_PREFIX_LEN - 1);
2383 }
2384}
2385
2387{
2388 use_builtin_tzdata = set;
2389}
2390
2392{
2393 return use_builtin_tzdata;
2394}
2395
2396struct observance {
2397 const char *name;
2398 icaltimetype onset;
2399 int offset_from;
2400 int offset_to;
2401};
2402
2403static void check_tombstone(struct observance *tombstone,
2404 struct observance *obs)
2405{
2406 if (icaltime_compare(obs->onset, tombstone->onset) > 0) {
2407 /* onset is closer to cutoff than existing tombstone */
2408 tombstone->name = icalmemory_tmp_copy(obs->name);
2409 tombstone->offset_from = tombstone->offset_to = obs->offset_to;
2410 tombstone->onset = obs->onset;
2411 }
2412}
2413
2414struct rdate {
2415 icalproperty *prop;
2416 struct icaldatetimeperiodtype date;
2417};
2418
2419static int rdate_compare(const void *rdate1, const void *rdate2)
2420{
2421 return icaltime_compare(((struct rdate *)rdate1)->date.time,
2422 ((struct rdate *)rdate2)->date.time);
2423}
2424
2425void icaltimezone_truncate_vtimezone(icalcomponent *vtz,
2426 icaltimetype start, icaltimetype end,
2427 bool ms_compatible)
2428{
2429 icalcomponent *comp, *nextc, *tomb_std = NULL, *tomb_day = NULL;
2430 icalproperty *prop, *proleptic_prop = NULL;
2431 struct observance tombstone;
2432 unsigned need_tomb = (unsigned)!icaltime_is_null_time(start);
2433 unsigned need_tzuntil = (unsigned)!icaltime_is_null_time(end);
2434
2435 if (!need_tomb && !need_tzuntil) {
2436 /* Nothing to do */
2437 return;
2438 }
2439
2440 /* See if we have a proleptic tzname in VTIMEZONE */
2441 for (prop = icalcomponent_get_first_property(vtz, ICAL_X_PROPERTY);
2442 prop;
2443 prop = icalcomponent_get_next_property(vtz, ICAL_X_PROPERTY)) {
2444 if (!strcmp("X-PROLEPTIC-TZNAME", icalproperty_get_x_name(prop))) {
2445 proleptic_prop = prop;
2446 break;
2447 }
2448 }
2449
2450 memset(&tombstone, 0, sizeof(struct observance));
2451 tombstone.name = icalmemory_tmp_copy(proleptic_prop ? icalproperty_get_x(proleptic_prop) : "LMT");
2452 if (!proleptic_prop ||
2453 !icalproperty_get_parameter_as_string(proleptic_prop, "X-NO-BIG-BANG")) {
2454 tombstone.onset.year = -1;
2455 }
2456
2457 /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
2459 comp; comp = nextc) {
2460 icalproperty *dtstart_prop = NULL, *rrule_prop = NULL;
2461 icalarray *rdates = icalarray_new(sizeof(struct rdate), 10);
2462 icaltimetype dtstart;
2463 struct observance obs;
2464 size_t n;
2465 unsigned trunc_dtstart = 0;
2466 int r;
2467
2469
2470 memset(&obs, 0, sizeof(struct observance));
2471 obs.offset_from = obs.offset_to = INT_MAX;
2472 obs.onset.is_daylight =
2474
2475 /* Grab the properties that we require to expand recurrences */
2476 for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
2477 prop;
2478 prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) {
2479 switch (icalproperty_isa(prop)) {
2480 case ICAL_TZNAME_PROPERTY:
2481 obs.name = icalproperty_get_tzname(prop);
2482 break;
2483
2484 case ICAL_DTSTART_PROPERTY:
2485 dtstart_prop = prop;
2486 obs.onset = dtstart = icalproperty_get_dtstart(prop);
2487 break;
2488
2489 case ICAL_TZOFFSETFROM_PROPERTY:
2490 obs.offset_from = icalproperty_get_tzoffsetfrom(prop);
2491 break;
2492
2493 case ICAL_TZOFFSETTO_PROPERTY:
2494 obs.offset_to = icalproperty_get_tzoffsetto(prop);
2495 break;
2496
2497 case ICAL_RRULE_PROPERTY:
2498 rrule_prop = prop;
2499 break;
2500
2501 case ICAL_RDATE_PROPERTY: {
2502 struct icaldatetimeperiodtype dtp = icalproperty_get_rdate(prop);
2503 struct rdate rdate;
2504
2505 rdate.prop = prop;
2506 rdate.date.time = dtp.time;
2507 rdate.date.period = dtp.period;
2508
2509 icalarray_append(rdates, &rdate);
2510 break;
2511 }
2512
2513 default:
2514 /* ignore all other properties */
2515 break;
2516 }
2517 }
2518
2519 /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
2520 if (!dtstart_prop || !obs.name ||
2521 obs.offset_from == INT_MAX || obs.offset_to == INT_MAX) {
2522 icalarray_free(rdates);
2523 continue;
2524 }
2525
2526 /* Adjust DTSTART observance to UTC */
2527 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2529
2530 /* Check DTSTART vs window close */
2531 if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2532 /* All observances occur on/after window close - remove component */
2534 icalcomponent_free(comp);
2535
2536 /* Nothing else to do */
2537 icalarray_free(rdates);
2538 continue;
2539 }
2540
2541 /* Check DTSTART vs window open */
2542 r = icaltime_compare(obs.onset, start);
2543 if (r < 0) {
2544 /* DTSTART is prior to our window open - check it vs tombstone */
2545 if (need_tomb) {
2546 check_tombstone(&tombstone, &obs);
2547 }
2548
2549 /* Adjust it */
2550 trunc_dtstart = 1;
2551 } else if (r == 0) {
2552 /* DTSTART is on/after our window open */
2553 need_tomb = 0;
2554 }
2555
2556 if (rrule_prop) {
2557 struct icalrecurrencetype *rrule = icalproperty_get_rrule(rrule_prop);
2558 if (rrule) {
2559 unsigned eternal = (unsigned)icaltime_is_null_time(rrule->until);
2560 icalrecur_iterator *ritr = NULL;
2561 unsigned trunc_until = 0;
2562
2563 /* Check RRULE duration */
2564 if (!eternal && icaltime_compare(rrule->until, start) < 0) {
2565 /* RRULE ends prior to our window open -
2566 check UNTIL vs tombstone */
2567 obs.onset = rrule->until;
2568 if (need_tomb) {
2569 check_tombstone(&tombstone, &obs);
2570 }
2571
2572 /* Remove RRULE */
2573 icalcomponent_remove_property(comp, rrule_prop);
2574 icalproperty_free(rrule_prop);
2575 } else {
2576 /* RRULE ends on/after our window open */
2577 if (need_tzuntil &&
2578 (eternal || icaltime_compare(rrule->until, end) >= 0)) {
2579 /* RRULE ends after our window close - need to adjust it */
2580 trunc_until = 1;
2581 }
2582
2583 if (!eternal) {
2584 /* Adjust UNTIL to local time (for iterator) */
2585 icaltime_adjust(&rrule->until, 0, 0, 0, obs.offset_from);
2586 (void)icaltime_set_timezone(&rrule->until, NULL);
2587 }
2588
2589 ritr = icalrecur_iterator_new(rrule, dtstart);
2590
2591 if (ritr && trunc_dtstart) {
2592 /* Bump RRULE start to 1 year prior to our window open */
2593 icaltimetype newstart = dtstart;
2594 newstart.year = start.year - 1;
2595 newstart.month = start.month;
2596 newstart.day = start.day;
2597 icalrecur_iterator_set_start(ritr, newstart);
2598 }
2599 }
2600
2601 /* Process any RRULE observances within our window */
2602 if (ritr) {
2603 icaltimetype recur, prev_onset;
2604
2605 while (!icaltime_is_null_time(recur = icalrecur_iterator_next(ritr))) {
2606 unsigned ydiff;
2607
2608 obs.onset = recur;
2609
2610 /* Adjust observance to UTC */
2611 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2612 (void)icaltime_set_timezone(&obs.onset,
2614
2615 if (trunc_until && icaltime_compare(obs.onset, end) >= 0) {
2616 /* Observance is on/after window close */
2617
2618 /* Check if DSTART is within 1yr of prev onset */
2619 ydiff = (unsigned)(prev_onset.year - dtstart.year);
2620 if (ydiff <= 1) {
2621 /* Remove RRULE */
2622 icalcomponent_remove_property(comp, rrule_prop);
2623 icalproperty_free(rrule_prop);
2624
2625 if (ydiff) {
2626 /* Add previous onset as RDATE */
2627 struct icaldatetimeperiodtype rdate;
2628 rdate.time = prev_onset;
2629 rdate.period = icalperiodtype_null_period();
2630
2631 prop = icalproperty_new_rdate(rdate);
2632 icalcomponent_add_property(comp, prop);
2633 }
2634 } else {
2635 /* Set UNTIL to previous onset */
2636 rrule->until = prev_onset;
2637 icalproperty_set_rrule(rrule_prop, rrule);
2638 }
2639
2640 /* We're done */
2641 break;
2642 }
2643
2644 /* Check observance vs our window open */
2645 r = icaltime_compare(obs.onset, start);
2646 if (r < 0) {
2647 /* Observance is prior to our window open -
2648 check it vs tombstone */
2649 if (ms_compatible) {
2650 /* XXX We don't want to move DTSTART of the RRULE
2651 as Outlook/Exchange doesn't appear to like
2652 truncating the frontend of RRULEs */
2653 need_tomb = 0;
2654 trunc_dtstart = 0;
2655 if (proleptic_prop) {
2657 proleptic_prop);
2658 icalproperty_free(proleptic_prop);
2659 proleptic_prop = NULL;
2660 }
2661 }
2662 if (need_tomb) {
2663 check_tombstone(&tombstone, &obs);
2664 }
2665 } else {
2666 /* Observance is on/after our window open */
2667 if (r == 0) {
2668 need_tomb = 0;
2669 }
2670
2671 if (trunc_dtstart) {
2672 /* Make this observance the new DTSTART */
2673 icalproperty_set_dtstart(dtstart_prop, recur);
2674 dtstart = obs.onset;
2675 trunc_dtstart = 0;
2676
2677 /* Check if new DSTART is within 1yr of UNTIL */
2678 ydiff = (unsigned)(rrule->until.year - recur.year);
2679 if (!trunc_until && ydiff <= 1) {
2680 /* Remove RRULE */
2681 icalcomponent_remove_property(comp, rrule_prop);
2682 icalproperty_free(rrule_prop);
2683
2684 if (ydiff) {
2685 /* Add UNTIL as RDATE */
2686 struct icaldatetimeperiodtype rdate;
2687 rdate.time = rrule->until;
2688 rdate.period = icalperiodtype_null_period();
2689
2690 prop = icalproperty_new_rdate(rdate);
2691 icalcomponent_add_property(comp, prop);
2692 }
2693 }
2694 }
2695
2696 if (!trunc_until) {
2697 /* We're done */
2698 break;
2699 }
2700
2701 /* Check if observance is outside 1yr of window close */
2702 ydiff = (unsigned)(end.year - recur.year);
2703 if (ydiff > 1) {
2704 /* Bump RRULE to restart at 1 year prior to our window close */
2705 icaltimetype newstart = recur;
2706 newstart.year = end.year - 1;
2707 newstart.month = end.month;
2708 newstart.day = end.day;
2709 icalrecur_iterator_set_start(ritr, newstart);
2710 }
2711 }
2712 prev_onset = obs.onset;
2713 }
2715 }
2716 }
2717 }
2718
2719 /* Sort the RDATEs by onset */
2720 icalarray_sort(rdates, &rdate_compare);
2721
2722 /* Check RDATEs */
2723 for (n = 0; n < rdates->num_elements; n++) {
2724 struct rdate *rdate = icalarray_element_at(rdates, n);
2725
2726 /* RDATEs with a DATE value inherit the time from the DTSTART. */
2727 if (icaltime_is_date(rdate->date.time)) {
2728 rdate->date.time.hour = dtstart.hour;
2729 rdate->date.time.minute = dtstart.minute;
2730 rdate->date.time.second = dtstart.second;
2731 }
2732
2733 if (n == 0 && icaltime_compare(rdate->date.time, dtstart) == 0) {
2734 /* RDATE is same as DTSTART - remove it */
2735 icalcomponent_remove_property(comp, rdate->prop);
2736 icalproperty_free(rdate->prop);
2737 continue;
2738 }
2739
2740 obs.onset = rdate->date.time;
2741
2742 /* Adjust observance to UTC */
2743 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
2745
2746 if (need_tzuntil && icaltime_compare(obs.onset, end) >= 0) {
2747 /* RDATE is after our window close - remove it */
2748 icalcomponent_remove_property(comp, rdate->prop);
2749 icalproperty_free(rdate->prop);
2750
2751 continue;
2752 }
2753
2754 r = icaltime_compare(obs.onset, start);
2755 if (r < 0) {
2756 /* RDATE is prior to window open - check it vs tombstone */
2757 if (need_tomb) {
2758 check_tombstone(&tombstone, &obs);
2759 }
2760
2761 /* Remove it */
2762 icalcomponent_remove_property(comp, rdate->prop);
2763 icalproperty_free(rdate->prop);
2764 } else {
2765 /* RDATE is on/after our window open */
2766 if (r == 0) {
2767 need_tomb = 0;
2768 }
2769
2770 if (trunc_dtstart) {
2771 /* Make this RDATE the new DTSTART */
2772 icalproperty_set_dtstart(dtstart_prop,
2773 rdate->date.time);
2774 trunc_dtstart = 0;
2775
2776 icalcomponent_remove_property(comp, rdate->prop);
2777 icalproperty_free(rdate->prop);
2778 }
2779 }
2780 }
2781 icalarray_free(rdates);
2782
2783 /* Final check */
2784 if (trunc_dtstart) {
2785 /* All observances in comp occur prior to window open, remove it
2786 unless we haven't saved a tombstone comp of this type yet */
2788 if (!tomb_day) {
2789 tomb_day = comp;
2790 comp = NULL;
2791 }
2792 } else if (!tomb_std) {
2793 tomb_std = comp;
2794 comp = NULL;
2795 }
2796
2797 if (comp) {
2799 icalcomponent_free(comp);
2800 }
2801 }
2802 }
2803
2804 if (need_tomb && !icaltime_is_null_time(tombstone.onset)) {
2805 /* Need to add tombstone component/observance starting at window open
2806 as long as its not prior to start of TZ data */
2807 icalcomponent *tomb;
2808 icalproperty *tomb_prop, *nextp;
2809
2810 /* Determine which tombstone component we need */
2811 if (tombstone.onset.is_daylight) {
2812 tomb = tomb_day;
2813 tomb_day = NULL;
2814 } else {
2815 tomb = tomb_std;
2816 tomb_std = NULL;
2817 }
2818
2819 /* Set property values on our tombstone */
2820 for (tomb_prop = icalcomponent_get_first_property(tomb, ICAL_ANY_PROPERTY);
2821 tomb_prop; tomb_prop = nextp) {
2822 nextp = icalcomponent_get_next_property(tomb, ICAL_ANY_PROPERTY);
2823
2824 switch (icalproperty_isa(tomb_prop)) {
2825 case ICAL_TZNAME_PROPERTY:
2826 icalproperty_set_tzname(tomb_prop, tombstone.name);
2827 break;
2828 case ICAL_TZOFFSETFROM_PROPERTY:
2829 icalproperty_set_tzoffsetfrom(tomb_prop, tombstone.offset_from);
2830 break;
2831 case ICAL_TZOFFSETTO_PROPERTY:
2832 icalproperty_set_tzoffsetto(tomb_prop, tombstone.offset_to);
2833 break;
2834 case ICAL_DTSTART_PROPERTY:
2835 /* Adjust window open to local time */
2836 icaltime_adjust(&start, 0, 0, 0, tombstone.offset_from);
2837 (void)icaltime_set_timezone(&start, NULL);
2838
2839 icalproperty_set_dtstart(tomb_prop, start);
2840 break;
2841 default:
2842 icalcomponent_remove_property(tomb, tomb_prop);
2843 icalproperty_free(tomb_prop);
2844 break;
2845 }
2846 }
2847
2848 /* Remove X-PROLEPTIC-TZNAME as it no longer applies */
2849 if (proleptic_prop) {
2850 icalcomponent_remove_property(vtz, proleptic_prop);
2851 icalproperty_free(proleptic_prop);
2852 }
2853 }
2854
2855 /* Remove any unused tombstone components */
2856 if (tomb_std) {
2857 icalcomponent_remove_component(vtz, tomb_std);
2858 icalcomponent_free(tomb_std);
2859 }
2860 if (tomb_day) {
2861 icalcomponent_remove_component(vtz, tomb_day);
2862 icalcomponent_free(tomb_day);
2863 }
2864
2865 if (need_tzuntil) {
2866 /* Add TZUNTIL to VTIMEZONE */
2867 prop = icalcomponent_get_first_property(vtz, ICAL_TZUNTIL_PROPERTY);
2868
2869 if (prop) {
2870 icalproperty_set_tzuntil(prop, end);
2871 } else {
2872 icalcomponent_add_property(vtz, icalproperty_new_tzuntil(end));
2873 }
2874 }
2875}
icalarray * icalarray_copy(const icalarray *originalarray)
Definition icalarray.c:69
void * icalarray_element_at(icalarray *array, size_t position)
Access an array element.
Definition icalarray.c:135
void icalarray_free(icalarray *array)
Definition icalarray.c:104
void icalarray_sort(icalarray *array, int(*compare)(const void *, const void *))
Sorts the elements of an icalarray using the given comparison function.
Definition icalarray.c:182
void icalarray_append(icalarray *array, const void *element)
Appends an element to an array.
Definition icalarray.c:119
icalarray * icalarray_new(size_t element_size, size_t increment_size)
Definition icalarray.c:36
An array of arbitrarily-sized elements which grows dynamically as elements are added.
icalproperty * icalcomponent_get_first_property(icalcomponent *c, icalproperty_kind kind)
icalcomponent * icalcomponent_get_next_component(icalcomponent *c, icalcomponent_kind kind)
icalcomponent * icalcomponent_get_first_component(icalcomponent *c, icalcomponent_kind kind)
void icalcomponent_remove_property(icalcomponent *component, icalproperty *property)
void icalcomponent_remove_component(icalcomponent *parent, icalcomponent *child)
void icalcomponent_add_property(icalcomponent *component, icalproperty *property)
icalcomponent_kind icalcomponent_isa(const icalcomponent *component)
void icalcomponent_free(icalcomponent *c)
icalproperty * icalcomponent_get_next_property(icalcomponent *c, icalproperty_kind kind)
icalcomponent_kind
Definition icalenums.h:29
@ ICAL_XDAYLIGHT_COMPONENT
Definition icalenums.h:47
@ ICAL_XSTANDARD_COMPONENT
Definition icalenums.h:46
@ ICAL_ANY_COMPONENT
Definition icalenums.h:31
@ ICAL_VTIMEZONE_COMPONENT
Definition icalenums.h:45
void icalerror_set_errno(icalerrorenum x)
Sets the icalerrno to a given error.
Definition icalerror.c:90
Error handling for libical.
@ ICAL_NEWFAILED_ERROR
Definition icalerror.h:50
@ ICAL_FILE_ERROR
Definition icalerror.h: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:4105
struct icalrecurrencetype * icalrecurrencetype_clone(struct icalrecurrencetype *recur)
Definition icalrecur.c:832
void icalrecur_iterator_free(icalrecur_iterator *impl)
Definition icalrecur.c:2451
void icalrecurrencetype_unref(struct icalrecurrencetype *recur)
Definition icalrecur.c:790
icalrecur_iterator * icalrecur_iterator_new(struct icalrecurrencetype *rule, struct icaltimetype dtstart)
Definition icalrecur.c:2323
struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *impl)
Definition icalrecur.c:3694
bool icaltime_is_date(const struct icaltimetype t)
Definition icaltime.c:616
struct icaltimetype icaltime_today(void)
Convenience constructor.
Definition icaltime.c:254
int icaltime_days_in_month(const int month, const int year)
Definition icaltime.c:471
bool icaltime_is_utc(const struct icaltimetype t)
Definition icaltime.c:621
bool icaltime_is_null_time(const struct icaltimetype t)
Definition icaltime.c:626
int icaltime_compare(const struct icaltimetype a_in, const struct icaltimetype b_in)
Definition icaltime.c:635
struct icaltimetype icaltime_set_timezone(struct icaltimetype *t, const icaltimezone *zone)
Definition icaltime.c:892
void icaltime_adjust(struct icaltimetype *tt, const int days, const int hours, const int minutes, const int seconds)
Definition icaltime.c:764
struct icaltimetype icaltime_null_time(void)
Definition icaltime.c:575
icaltimezone * icaltimezone_new(void)
void icaltimezone_set_tzid_prefix(const char *new_prefix)
icaltimezone * icaltimezone_get_builtin_timezone_from_offset(int offset, const char *tzname)
double icaltimezone_get_longitude(const icaltimezone *zone)
void icaltimezone_free_zone_directory(void)
const char * icaltimezone_get_location(const icaltimezone *zone)
void icaltimezone_truncate_vtimezone(icalcomponent *vtz, icaltimetype start, icaltimetype end, bool ms_compatible)
void icaltimezone_set_builtin_tzdata(bool set)
icaltimezone * icaltimezone_copy(const icaltimezone *originalzone)
#define BUILTIN_TZID_PREFIX
#define ZONES_TAB_FILENAME
char * icaltimezone_get_location_from_vtimezone(icalcomponent *component)
#define ICALTIMEZONE_MAX_YEAR
char * icaltimezone_get_tznames_from_vtimezone(icalcomponent *component)
double icaltimezone_get_latitude(const icaltimezone *zone)
void icaltimezone_expand_vtimezone(icalcomponent *comp, int end_year, icalarray *changes)
void icaltimezone_set_system_zone_directory(const char *zonepath)
int icaltimezone_get_utc_offset_of_utc_time(icaltimezone *zone, const struct icaltimetype *tt, int *is_daylight)
icalcomponent * icaltimezone_get_component(icaltimezone *zone)
const char * icaltimezone_get_system_zone_directory(void)
#define BUILTIN_TZID_PREFIX_LEN
void icaltimezone_free_builtin_timezones(void)
Releases builtin timezone memory.
int icaltimezone_get_utc_offset(icaltimezone *zone, const struct icaltimetype *tt, int *is_daylight)
const char * icaltimezone_get_tznames(icaltimezone *zone)
icaltimezone * icaltimezone_get_builtin_timezone(const char *location)
icalarray * icaltimezone_get_builtin_timezones(void)
const char * icaltimezone_get_tzid(icaltimezone *zone)
icaltimezone * icaltimezone_get_utc_timezone(void)
void icaltimezone_set_zone_directory(const char *path)
#define ZONEINFO_DIRECTORY
void icaltimezone_free(icaltimezone *zone, int free_struct)
Frees all memory used for the icaltimezone.
const char * icaltimezone_get_display_name(icaltimezone *zone)
const char * icaltimezone_get_zone_directory(void)
icaltimezone * icaltimezone_get_builtin_timezone_from_tzid(const char *tzid)
void icaltimezone_convert_time(struct icaltimetype *tt, icaltimezone *from_zone, icaltimezone *to_zone)
#define ICALTIMEZONE_EXTRA_COVERAGE
bool icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp)
bool icaltimezone_get_builtin_tzdata(void)
bool icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp)
const char * icaltimezone_tzid_prefix(void)
Timezone handling routines.
struct _icaltimezone icaltimezone
struct icaltimetype time
Definition icaltypes.h:30
struct icalperiodtype period
Definition icaltypes.h:31
struct icaltimetype until
Definition icalrecur.h:258
int is_daylight
Definition icaltime.h:100
const icaltimezone * zone
Definition icaltime.h:102