$darkmode
Libical API Documentation 4.0 STABLE VERSION Visit the v3.0 documentation
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 
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:
42 static pthread_mutex_t builtin_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
43 #else
44 static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER;
45 #endif
46 // To avoid use-after-free in multithreaded applications when accessing icaltimezone::changes
47 static 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
64 
71 #define BUILTIN_TZID_PREFIX "/freeassociation.sourceforge.net/"
72 
73 /* Known prefixes from the old versions of libical */
74 static 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) */
84 static ICAL_GLOBAL_VAR char s_zoneinfopath[MAXPATHLEN] = {0};
85 
86 /* A few well-known locations for system zoneinfo; can be overridden with TZDIR environment */
87 static 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 */
94 static 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)
109 
111 #define ICALTIMEZONE_MAX_YEAR 2582
112 #else
113 
115 #define ICALTIMEZONE_MAX_YEAR 2037
116 #endif
117 
118 typedef struct _icaltimezonechange icaltimezonechange;
119 
120 struct _icaltimezonechange {
121  int utc_offset;
124  int prev_utc_offset;
127  int year;
128  int month;
129  int day;
130  int hour;
131  int minute;
132  int second;
137  int is_daylight;
139 };
140 
142 static ICAL_GLOBAL_VAR icalarray *builtin_timezones = NULL;
143 
145 static ICAL_GLOBAL_VAR icaltimezone utc_timezone = {0, 0, 0, 0, 0, 0, 0, 0, 0};
146 
147 static ICAL_GLOBAL_VAR char *zone_files_directory = NULL;
148 
149 #if defined(USE_BUILTIN_TZDATA)
150 static ICAL_GLOBAL_VAR int use_builtin_tzdata = true;
151 #else
152 static ICAL_GLOBAL_VAR int use_builtin_tzdata = false;
153 #endif
154 
155 static void icaltimezone_reset(icaltimezone *zone);
156 static void icaltimezone_expand_changes(icaltimezone *zone, int end_year);
157 static int icaltimezone_compare_change_fn(const void *elem1, const void *elem2);
158 
159 static size_t icaltimezone_find_nearby_change(icaltimezone *zone, const icaltimezonechange *change);
160 
161 static void icaltimezone_adjust_change(icaltimezonechange *tt,
162  int days, int hours, int minutes, int seconds);
163 
164 static void icaltimezone_init(icaltimezone *zone);
165 
172 static bool icaltimezone_get_vtimezone_properties(icaltimezone *zone, icalcomponent *component)
173 #if defined(THREAD_SANITIZER)
174  __attribute__((no_sanitize("thread")))
175 #endif
176  ;
177 
178 static bool icaltimezone_load_builtin_timezone(icaltimezone *zone)
179 #if defined(THREAD_SANITIZER)
180  __attribute__((no_sanitize("thread")))
181 #endif
182  ;
183 
184 static bool icaltimezone_ensure_coverage(icaltimezone *zone, int end_year);
185 
186 static bool icaltimezone_init_builtin_timezones(void);
187 
188 static void icaltimezone_parse_zone_tab(void);
189 
190 static char *icaltimezone_load_get_line_fn(char *s, size_t size, void *data);
191 
192 static void format_utc_offset(int utc_offset, char *buffer, size_t buffer_size);
193 static const char *get_zone_directory_builtin(void);
194 
195 static 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 
206 static 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 
217 static 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 
228 static 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 
239 const char *icaltimezone_tzid_prefix(void)
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 
308 void icaltimezone_free(icaltimezone *zone, int free_struct)
309 {
310  icaltimezone_reset(zone);
311  if (free_struct) {
313  }
314 }
315 
319 static 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 
341 static 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 
363 static 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 
396 char *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 
424 char *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 
530 static 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 */
568 static 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. */
586  comp = icalcomponent_get_first_component(zone->component, ICAL_ANY_COMPONENT);
587  while (comp) {
588  icaltimezone_expand_vtimezone(comp, end_year, changes);
589  comp = icalcomponent_get_next_component(zone->component, ICAL_ANY_COMPONENT);
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 
604 void 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 
825 static 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 
887 int 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 
1186 static 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 
1216 static 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 
1293 const char *icaltimezone_get_location(const icaltimezone *zone)
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 
1353 bool icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp)
1354 {
1355  icaltimezone_reset(zone);
1356  return icaltimezone_get_vtimezone_properties(zone, comp);
1357 }
1358 
1359 static 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 
1407 icalarray *icaltimezone_array_new(void)
1408 {
1409  return icalarray_new(sizeof(icaltimezone), 16);
1410 }
1411 
1412 void 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 
1422 void 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 
1502 static 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 
1521 static 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;
1572  icaltimezone *zone;
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) {
1580  return icaltimezone_get_builtin_timezone(tzid);
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 
1653 static 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 
1670 static 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 
1692 static 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 
1778 static 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 
1930 static 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 
2055 out:
2056  if (!icaltimezone_builtin_unlock()) {
2057  return false;
2058  }
2059  return true;
2060 }
2061 
2065 static 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 
2074 bool 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 
2126 static 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 
2161 static 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 
2303 void icaltimezone_set_system_zone_directory(const char *zonepath)
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 
2312 static 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 
2356 void icaltimezone_set_zone_directory(const char *path)
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 
2379 void 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 
2396 struct observance {
2397  const char *name;
2398  icaltimetype onset;
2399  int offset_from;
2400  int offset_to;
2401 };
2402 
2403 static 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 
2414 struct rdate {
2415  icalproperty *prop;
2416  struct icaldatetimeperiodtype date;
2417 };
2418 
2419 static 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 
2425 void 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 */
2533  icalcomponent_remove_component(vtz, comp);
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;
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;
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) {
2798  icalcomponent_remove_component(vtz, 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 }
icalcomponent * icalcomponent_get_first_component(icalcomponent *c, icalcomponent_kind kind)
const char * icalproperty_get_x_name(const icalproperty *prop)
Definition: icalproperty.c:857
void icaltimezone_set_zone_directory(const char *path)
#define BUILTIN_TZID_PREFIX_LEN
Definition: icaltimezone.c:63
double icaltimezone_get_longitude(const icaltimezone *zone)
Line-oriented parsing.
icalarray * icalarray_new(size_t element_size, size_t increment_size)
Definition: icalarray.c:36
void icalcomponent_remove_property(icalcomponent *component, icalproperty *property)
void icaltimezone_convert_time(struct icaltimetype *tt, icaltimezone *from_zone, icaltimezone *to_zone)
Definition: icaltimezone.c:864
const char * icaltimezone_get_zone_directory(void)
#define BUILTIN_TZID_PREFIX
Definition: icaltimezone.c:71
void icalarray_free(icalarray *array)
Definition: icalarray.c:104
Common memory management routines.
icalproperty_kind icalproperty_isa(const icalproperty *p)
Definition: icalproperty.c:466
Timezone handling routines.
icalproperty * icalcomponent_get_next_property(icalcomponent *c, icalproperty_kind kind)
void icaltimezone_truncate_vtimezone(icalcomponent *vtz, icaltimetype start, icaltimetype end, bool ms_compatible)
void icaltimezone_set_system_zone_directory(const char *zonepath)
const char * icalproperty_get_parameter_as_string(icalproperty *prop, const char *name)
Definition: icalproperty.c:543
int second
Definition: icaltime.h:96
const char * icaltimezone_get_location(const icaltimezone *zone)
void icalcomponent_remove_component(icalcomponent *parent, icalcomponent *child)
char * icalmemory_strdup(const char *s)
Creates a duplicate of a string.
Definition: icalmemory.c:242
void icalmemory_free_buffer(void *buf)
Releases a buffer.
Definition: icalmemory.c:355
bool icaltime_is_utc(const struct icaltimetype t)
Definition: icaltime.c:625
void icaltimezone_free_builtin_timezones(void)
Releases builtin timezone memory.
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
#define ICALTIMEZONE_EXTRA_COVERAGE
Definition: icaltimezone.c:106
void icaltimezone_expand_vtimezone(icalcomponent *comp, int end_year, icalarray *changes)
Definition: icaltimezone.c:604
const icaltimezone * zone
Definition: icaltime.h:102
#define ICALTIMEZONE_MAX_YEAR
Definition: icaltimezone.c:115
icaltimezone * icaltimezone_new(void)
Definition: icaltimezone.c:244
char * icaltimezone_get_tznames_from_vtimezone(icalcomponent *component)
Definition: icaltimezone.c:424
#define ZONEINFO_DIRECTORY
Definition: icaltimezone.c:58
icalarray * icaltimezone_get_builtin_timezones(void)
size_t icallimit_get(icallimits_kind kind)
Definition: icallimits.c:29
bool icaltimezone_get_builtin_tzdata(void)
icalarray * icalarray_copy(const icalarray *originalarray)
Definition: icalarray.c:69
bool icaltimezone_set_component(icaltimezone *zone, icalcomponent *comp)
struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *impl)
Definition: icalrecur.c:3694
void icalerror_set_errno(icalerrorenum x)
Sets the icalerrno to a given error.
Definition: icalerror.c:90
char * icaltimezone_get_location_from_vtimezone(icalcomponent *component)
Definition: icaltimezone.c:396
int icaltime_compare(const struct icaltimetype a_in, const struct icaltimetype b_in)
Definition: icaltime.c:639
void icaltime_adjust(struct icaltimetype *tt, const int days, const int hours, const int minutes, const int seconds)
Definition: icaltime.c:755
icaltimezone * icaltimezone_get_builtin_timezone_from_tzid(const char *tzid)
void icaltimezone_free(icaltimezone *zone, int free_struct)
Frees all memory used for the icaltimezone.
Definition: icaltimezone.c:308
int icaltimezone_get_utc_offset(icaltimezone *zone, const struct icaltimetype *tt, int *is_daylight)
Definition: icaltimezone.c:887
void icalparser_free(icalparser *parser)
Frees an icalparser object.
Definition: icalparser.c:112
Error handling for libical.
void icalproperty_free(icalproperty *p)
Definition: icalproperty.c:184
const char * icaltimezone_get_tznames(icaltimezone *zone)
int minute
Definition: icaltime.h:95
struct icaltimetype icaltime_null_time(void)
Definition: icaltime.c:579
struct icaltimetype icaltime_set_timezone(struct icaltimetype *t, const icaltimezone *zone)
Definition: icaltime.c:883
const char * icaltimezone_get_display_name(icaltimezone *zone)
icaltimezone * icaltimezone_get_builtin_timezone(const char *location)
struct icaltimetype time
Definition: icaltypes.h:30
struct _icaltimezone icaltimezone
Definition: icaltimezone.h:29
icaltimezone * icaltimezone_get_builtin_timezone_from_offset(int offset, const char *tzname)
double icaltimezone_get_latitude(const icaltimezone *zone)
icalcomponent * icalcomponent_get_next_component(icalcomponent *c, icalcomponent_kind kind)
void icalarray_append(icalarray *array, const void *element)
Appends an element to an array.
Definition: icalarray.c:119
void * icalmemory_new_buffer(size_t size)
Creates new buffer with the specified size.
Definition: icalmemory.c:315
#define ZONES_TAB_FILENAME
Definition: icaltimezone.c:100
Defines the interface for getting/setting internal library limits.
bool icaltime_is_date(const struct icaltimetype t)
Definition: icaltime.c:620
icalcomponent * icaltimezone_get_component(icaltimezone *zone)
void icaltimezone_set_tzid_prefix(const char *new_prefix)
void * icalarray_element_at(icalarray *array, size_t position)
Access an array element.
Definition: icalarray.c:135
void icalrecur_iterator_free(icalrecur_iterator *impl)
Definition: icalrecur.c:2451
icaltimezone * icaltimezone_get_utc_timezone(void)
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
struct icaltimetype until
Definition: icalrecur.h:258
const char * icaltimezone_get_system_zone_directory(void)
icalcomponent_kind
Definition: icalenums.h:28
icalcomponent_kind icalcomponent_isa(const icalcomponent *component)
icaltimezone * icaltimezone_copy(const icaltimezone *originalzone)
Definition: icaltimezone.c:259
int icaltimezone_get_utc_offset_of_utc_time(icaltimezone *zone, const struct icaltimetype *tt, int *is_daylight)
void icalrecurrencetype_unref(struct icalrecurrencetype *recur)
Definition: icalrecur.c:790
struct icalperiodtype icalperiodtype_null_period(void)
Definition: icalperiod.c:127
icalparser * icalparser_new(void)
Creates a new icalparser.
Definition: icalparser.c:89
An array of arbitrarily-sized elements which grows dynamically as elements are added.
int icaltime_days_in_month(const int month, const int year)
Definition: icaltime.c:471
bool icaltime_is_null_time(const struct icaltimetype t)
Definition: icaltime.c:630
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
bool icalrecur_iterator_set_start(icalrecur_iterator *impl, struct icaltimetype start)
Definition: icalrecur.c:4105
icalrecur_iterator * icalrecur_iterator_new(struct icalrecurrencetype *rule, struct icaltimetype dtstart)
Definition: icalrecur.c:2323
bool icaltimezone_dump_changes(icaltimezone *zone, int max_year, FILE *fp)
struct icalrecurrencetype * icalrecurrencetype_clone(struct icalrecurrencetype *recur)
Definition: icalrecur.c:832
void icalcomponent_free(icalcomponent *c)
void icalcomponent_add_property(icalcomponent *component, icalproperty *property)
int is_daylight
Definition: icaltime.h:100
struct icaltimetype icaltime_today(void)
Convenience constructor.
Definition: icaltime.c:254
icalcomponent * icalparser_parse(icalparser *parser, icalparser_line_gen_func line_gen_func)
Message oriented parsing.
Definition: icalparser.c:588
struct icalperiodtype period
Definition: icaltypes.h:31
void icaltimezone_set_builtin_tzdata(bool set)
const char * icaltimezone_get_tzid(icaltimezone *zone)
const char * icaltimezone_tzid_prefix(void)
Definition: icaltimezone.c:239
void icaltimezone_free_zone_directory(void)
icalproperty * icalcomponent_get_first_property(icalcomponent *c, icalproperty_kind kind)