1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2
/*======================================================================
4
CREATOR: Damon Chaplin 15 March 2001
6
$Id: icaltimezone.c 564286 2006-07-19 19:25:58Z chehrlic $
9
(C) COPYRIGHT 2001, Damon Chaplin
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of either:
14
The LGPL as published by the Free Software Foundation, version
15
2.1, available at: http://www.fsf.org/copyleft/lesser.html
19
The Mozilla Public License Version 1.0. You may obtain a copy of
20
the License at http://www.mozilla.org/MPL/
23
======================================================================*/
25
/** @file icaltimezone.c
26
* @brief implementation of timezone handling routines
36
#include "icalproperty.h"
37
#include "icalarray.h"
38
#include "icalerror.h"
39
#include "icalparser.h"
40
#include "icaltimezone.h"
43
#define snprintf _snprintf
46
/** This is the toplevel directory where the timezone data is installed in. */
47
#define ZONEINFO_DIRECTORY PACKAGE_DATA_DIR "/zoneinfo"
49
/** The prefix we use to uniquely identify TZIDs. */
50
#define TZID_PREFIX "/softwarestudio.org/"
51
#define TZID_PREFIX_LEN 20
53
/** This is the filename of the file containing the city names and
54
coordinates of all the builtin timezones. */
55
#define ZONES_TAB_FILENAME "zones.tab"
57
/** This is the number of years of extra coverage we do when expanding
58
the timezone changes. */
59
#define ICALTIMEZONE_EXTRA_COVERAGE 5
61
/** This is the maximum year we will expand to. time_t values only go up to
62
somewhere around 2037. */
63
#define ICALTIMEZONE_MAX_YEAR 2035
65
struct _icaltimezone {
67
/**< The unique ID of this timezone,
68
e.g. "/softwarestudio.org/Olson_20010601_1/Africa/Banjul".
69
This should only be used to identify a VTIMEZONE. It is not
70
meant to be displayed to the user in any form. */
73
/**< The location for the timezone, e.g. "Africa/Accra" for the
74
Olson database. We look for this in the "LOCATION" or
75
"X-LIC-LOCATION" properties of the VTIMEZONE component. It
76
isn't a standard property yet. This will be NULL if no location
77
is found in the VTIMEZONE. */
80
/**< This will be set to a combination of the TZNAME properties
81
from the last STANDARD and DAYLIGHT components in the
82
VTIMEZONE, e.g. "EST/EDT". If they both use the same TZNAME,
83
or only one type of component is found, then only one TZNAME
84
will appear, e.g. "AZOT". If no TZNAME is found this will be
89
/**< The coordinates of the city, in degrees. */
91
icalcomponent *component;
92
/**< The toplevel VTIMEZONE component loaded from the .ics file for this
93
timezone. If we need to regenerate the changes data we need this. */
95
icaltimezone *builtin_timezone;
96
/**< If this is not NULL it points to the builtin icaltimezone
97
that the above TZID refers to. This icaltimezone should be used
98
instead when accessing the timezone changes data, so that the
99
expanded timezone changes data is shared between calendar
103
/**< This is the last year for which we have expanded the data to.
104
If we need to calculate a date past this we need to expand the
105
timezone component data from scratch. */
108
/**< A dynamically-allocated array of time zone changes, sorted by the
109
time of the change in local time. So we can do fast binary-searches
110
to convert from local time to UTC. */
113
typedef struct _icaltimezonechange icaltimezonechange;
115
struct _icaltimezonechange {
117
/**< The offset to add to UTC to get local time, in seconds. */
120
/**< The offset to add to UTC, before this change, in seconds. */
122
int year; /**< Actual year, e.g. 2001. */
123
int month; /**< 1 (Jan) to 12 (Dec). */
128
/**< The time that the change came into effect, in UTC.
129
Note that the prev_utc_offset applies to this local time,
130
since we haven't changed to the new offset yet. */
133
/**< Whether this is STANDARD or DAYLIGHT time. */
137
/** An array of icaltimezones for the builtin timezones. */
138
static icalarray *builtin_timezones = NULL;
140
/** This is the special UTC timezone, which isn't in builtin_timezones. */
141
static icaltimezone utc_timezone = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
143
static char* zone_files_directory = NULL;
145
static void icaltimezone_reset (icaltimezone *zone);
146
static char* icaltimezone_get_location_from_vtimezone (icalcomponent *component);
147
static char* icaltimezone_get_tznames_from_vtimezone (icalcomponent *component);
148
static void icaltimezone_expand_changes (icaltimezone *zone,
150
static void icaltimezone_expand_vtimezone (icalcomponent *comp,
153
static int icaltimezone_compare_change_fn (const void *elem1,
156
static int icaltimezone_find_nearby_change (icaltimezone *zone,
157
icaltimezonechange *change);
159
static void icaltimezone_adjust_change (icaltimezonechange *tt,
165
static void icaltimezone_init (icaltimezone *zone);
167
/** Gets the TZID, LOCATION/X-LIC-LOCATION, and TZNAME properties from the
168
VTIMEZONE component and places them in the icaltimezone. It returns 1 on
169
success, or 0 if the TZID can't be found. */
170
static int icaltimezone_get_vtimezone_properties (icaltimezone *zone,
171
icalcomponent *component);
174
static void icaltimezone_load_builtin_timezone (icaltimezone *zone);
176
static void icaltimezone_ensure_coverage (icaltimezone *zone,
180
static void icaltimezone_init_builtin_timezones(void);
182
static void icaltimezone_parse_zone_tab (void);
184
static char* icaltimezone_load_get_line_fn (char *s,
188
static void format_utc_offset (int utc_offset,
191
static const char* get_zone_directory(void);
194
/** Creates a new icaltimezone. */
196
icaltimezone_new (void)
200
zone = (icaltimezone*) malloc (sizeof (icaltimezone));
202
icalerror_set_errno (ICAL_NEWFAILED_ERROR);
206
icaltimezone_init (zone);
212
/** Frees all memory used for the icaltimezone. */
214
icaltimezone_free (icaltimezone *zone,
217
icaltimezone_reset (zone);
223
/** Resets the icaltimezone to the initial state, freeing most of the fields. */
225
icaltimezone_reset (icaltimezone *zone)
230
free (zone->location);
232
free (zone->tznames);
234
icalcomponent_free (zone->component);
236
icalarray_free (zone->changes);
238
icaltimezone_init (zone);
242
/** Initializes an icaltimezone. */
244
icaltimezone_init (icaltimezone *zone)
247
zone->location = NULL;
248
zone->tznames = NULL;
249
zone->latitude = 0.0;
250
zone->longitude = 0.0;
251
zone->component = NULL;
252
zone->builtin_timezone = NULL;
254
zone->changes = NULL;
258
/** Gets the TZID, LOCATION/X-LIC-LOCATION and TZNAME properties of
259
the VTIMEZONE component and stores them in the icaltimezone. It
260
returns 1 on success, or 0 if the TZID can't be found. Note that
261
it expects the zone to be initialized or reset - it doesn't free
264
icaltimezone_get_vtimezone_properties (icaltimezone *zone,
265
icalcomponent *component)
270
prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
274
/* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
275
tzid = icalproperty_get_tzid (prop);
279
zone->tzid = strdup (tzid);
280
zone->component = component;
281
if ( zone->location != 0 ) free ( zone->location );
282
zone->location = icaltimezone_get_location_from_vtimezone (component);
283
zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
288
/** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
290
icaltimezone_get_location_from_vtimezone (icalcomponent *component)
293
const char *location;
296
prop = icalcomponent_get_first_property (component,
297
ICAL_LOCATION_PROPERTY);
299
location = icalproperty_get_location (prop);
301
return strdup (location);
304
prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
306
name = icalproperty_get_x_name (prop);
307
if (name && !strcasecmp (name, "X-LIC-LOCATION")) {
308
location = icalproperty_get_x (prop);
310
return strdup (location);
312
prop = icalcomponent_get_next_property (component,
320
/** Gets the TZNAMEs used for the last STANDARD & DAYLIGHT components
321
in a VTIMEZONE. If both STANDARD and DAYLIGHT components use the
322
same TZNAME, it returns that. If they use different TZNAMEs, it
323
formats them like "EST/EDT". The returned string should be freed by
326
icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
329
icalcomponent_kind type;
331
struct icaltimetype dtstart;
332
struct icaldatetimeperiodtype rdate;
333
const char *current_tzname;
334
const char *standard_tzname = NULL, *daylight_tzname = NULL;
335
struct icaltimetype standard_max_date, daylight_max_date;
336
struct icaltimetype current_max_date;
338
standard_max_date = icaltime_null_time();
339
daylight_max_date = icaltime_null_time();
341
/* Step through the STANDARD & DAYLIGHT subcomponents. */
342
comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
344
type = icalcomponent_isa (comp);
345
if (type == ICAL_XSTANDARD_COMPONENT
346
|| type == ICAL_XDAYLIGHT_COMPONENT) {
347
current_max_date = icaltime_null_time ();
348
current_tzname = NULL;
350
/* Step through the properties. We want to find the TZNAME, and
351
the largest DTSTART or RDATE. */
352
prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
354
switch (icalproperty_isa (prop)) {
355
case ICAL_TZNAME_PROPERTY:
356
current_tzname = icalproperty_get_tzname (prop);
359
case ICAL_DTSTART_PROPERTY:
360
dtstart = icalproperty_get_dtstart (prop);
361
if (icaltime_compare (dtstart, current_max_date) > 0)
362
current_max_date = dtstart;
366
case ICAL_RDATE_PROPERTY:
367
rdate = icalproperty_get_rdate (prop);
368
if (icaltime_compare (rdate.time, current_max_date) > 0)
369
current_max_date = rdate.time;
377
prop = icalcomponent_get_next_property (comp,
381
if (current_tzname) {
382
if (type == ICAL_XSTANDARD_COMPONENT) {
384
|| icaltime_compare (current_max_date,
385
standard_max_date) > 0) {
386
standard_max_date = current_max_date;
387
standard_tzname = current_tzname;
391
|| icaltime_compare (current_max_date,
392
daylight_max_date) > 0) {
393
daylight_max_date = current_max_date;
394
daylight_tzname = current_tzname;
400
comp = icalcomponent_get_next_component (component,
404
/* Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
405
strings, which is totally useless. So we return NULL in that case. */
406
if (standard_tzname && !strcmp (standard_tzname, "Standard Time"))
409
/* If both standard and daylight TZNAMEs were found, if they are the same
410
we return just one, else we format them like "EST/EDT". */
411
if (standard_tzname && daylight_tzname) {
412
unsigned int standard_len, daylight_len;
415
if (!strcmp (standard_tzname, daylight_tzname))
416
return strdup (standard_tzname);
418
standard_len = strlen (standard_tzname);
419
daylight_len = strlen (daylight_tzname);
420
tznames = malloc (standard_len + daylight_len + 2);
421
strcpy (tznames, standard_tzname);
422
tznames[standard_len] = '/';
423
strcpy (tznames + standard_len + 1, daylight_tzname);
428
/* If either of the TZNAMEs was found just return that, else NULL. */
429
tznames = standard_tzname ? standard_tzname : daylight_tzname;
430
return tznames ? strdup (tznames) : NULL;
436
icaltimezone_ensure_coverage (icaltimezone *zone,
439
/* When we expand timezone changes we always expand at least up to this
440
year, plus ICALTIMEZONE_EXTRA_COVERAGE. */
441
static int icaltimezone_minimum_expansion_year = -1;
443
int changes_end_year;
445
if (!zone->component)
446
icaltimezone_load_builtin_timezone (zone);
448
if (icaltimezone_minimum_expansion_year == -1) {
449
struct icaltimetype today = icaltime_today();
450
icaltimezone_minimum_expansion_year = today.year;
453
changes_end_year = end_year;
454
if (changes_end_year < icaltimezone_minimum_expansion_year)
455
changes_end_year = icaltimezone_minimum_expansion_year;
457
changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
459
if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
460
changes_end_year = ICALTIMEZONE_MAX_YEAR;
462
if (!zone->changes || zone->end_year < end_year)
463
icaltimezone_expand_changes (zone, changes_end_year);
468
icaltimezone_expand_changes (icaltimezone *zone,
475
printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
478
changes = icalarray_new (sizeof (icaltimezonechange), 32);
482
/* Scan the STANDARD and DAYLIGHT subcomponents. */
483
comp = icalcomponent_get_first_component (zone->component,
486
icaltimezone_expand_vtimezone (comp, end_year, changes);
487
comp = icalcomponent_get_next_component (zone->component,
491
/* Sort the changes. We may have duplicates but I don't think it will
493
icalarray_sort (changes, icaltimezone_compare_change_fn);
496
icalarray_free (zone->changes);
498
zone->changes = changes;
499
zone->end_year = end_year;
504
icaltimezone_expand_vtimezone (icalcomponent *comp,
508
icaltimezonechange change;
510
struct icaltimetype dtstart, occ;
511
struct icalrecurrencetype rrule;
512
icalrecur_iterator* rrule_iterator;
513
struct icaldatetimeperiodtype rdate;
514
int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
515
int has_recurrence = 0;
517
/* First we check if it is a STANDARD or DAYLIGHT component, and
518
just return if it isn't. */
519
if (icalcomponent_isa (comp) == ICAL_XSTANDARD_COMPONENT)
520
change.is_daylight = 0;
521
else if (icalcomponent_isa (comp) == ICAL_XDAYLIGHT_COMPONENT)
522
change.is_daylight = 1;
526
/* Step through each of the properties to find the DTSTART,
527
TZOFFSETFROM and TZOFFSETTO. We can't expand recurrences here
528
since we need these properties before we can do that. */
529
prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
531
switch (icalproperty_isa (prop)) {
532
case ICAL_DTSTART_PROPERTY:
533
dtstart = icalproperty_get_dtstart (prop);
536
case ICAL_TZOFFSETTO_PROPERTY:
537
change.utc_offset = icalproperty_get_tzoffsetto (prop);
538
/*printf ("Found TZOFFSETTO: %i\n", change.utc_offset);*/
539
found_tzoffsetto = 1;
541
case ICAL_TZOFFSETFROM_PROPERTY:
542
change.prev_utc_offset = icalproperty_get_tzoffsetfrom (prop);
543
/*printf ("Found TZOFFSETFROM: %i\n", change.prev_utc_offset);*/
544
found_tzoffsetfrom = 1;
546
case ICAL_RDATE_PROPERTY:
547
case ICAL_RRULE_PROPERTY:
551
/* Just ignore any other properties. */
555
prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
558
/* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
559
ignore the component. FIXME: Add an error property? */
560
if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
564
printf ("\n Expanding component DTSTART (Y/M/D): %i/%i/%i %i:%02i:%02i\n",
565
dtstart.year, dtstart.month, dtstart.day,
566
dtstart.hour, dtstart.minute, dtstart.second);
569
/* If the STANDARD/DAYLIGHT component has no recurrence data, we just add
570
a single change for the DTSTART. */
571
if (!has_recurrence) {
572
change.year = dtstart.year;
573
change.month = dtstart.month;
574
change.day = dtstart.day;
575
change.hour = dtstart.hour;
576
change.minute = dtstart.minute;
577
change.second = dtstart.second;
579
/* Convert to UTC. */
580
icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
583
printf (" Appending single DTSTART (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
584
change.year, change.month, change.day,
585
change.hour, change.minute, change.second);
588
/* Add the change to the array. */
589
icalarray_append (changes, &change);
593
/* The component has recurrence data, so we expand that now. */
594
prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
597
printf ("Expanding property...\n");
599
switch (icalproperty_isa (prop)) {
600
case ICAL_RDATE_PROPERTY:
601
rdate = icalproperty_get_rdate (prop);
602
change.year = rdate.time.year;
603
change.month = rdate.time.month;
604
change.day = rdate.time.day;
605
/* RDATEs with a DATE value inherit the time from
607
if (icaltime_is_date(rdate.time)) {
608
change.hour = dtstart.hour;
609
change.minute = dtstart.minute;
610
change.second = dtstart.second;
612
change.hour = rdate.time.hour;
613
change.minute = rdate.time.minute;
614
change.second = rdate.time.second;
616
/* The spec was a bit vague about whether RDATEs were in local
617
time or UTC so we support both to be safe. So if it is in
618
UTC we have to add the UTC offset to get a local time. */
619
if (!icaltime_is_utc(rdate.time))
620
icaltimezone_adjust_change (&change, 0, 0, 0,
621
-change.prev_utc_offset);
625
printf (" Appending RDATE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
626
change.year, change.month, change.day,
627
change.hour, change.minute, change.second);
630
icalarray_append (changes, &change);
632
case ICAL_RRULE_PROPERTY:
633
rrule = icalproperty_get_rrule (prop);
635
/* If the rrule UNTIL value is set and is in UTC, we convert it to
636
a local time, since the recurrence code has no way to convert
638
if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
640
printf (" Found RRULE UNTIL in UTC.\n");
643
/* To convert from UTC to a local time, we use the TZOFFSETFROM
644
since that is the offset from UTC that will be in effect
645
when each of the RRULE occurrences happens. */
646
icaltime_adjust (&rrule.until, 0, 0, 0,
647
change.prev_utc_offset);
648
rrule.until.is_utc = 0;
651
rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
653
occ = icalrecur_iterator_next (rrule_iterator);
654
if (occ.year > end_year || icaltime_is_null_time (occ))
657
change.year = occ.year;
658
change.month = occ.month;
659
change.day = occ.day;
660
change.hour = occ.hour;
661
change.minute = occ.minute;
662
change.second = occ.second;
665
printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
666
change.year, change.month, change.day,
667
change.hour, change.minute, change.second);
670
icaltimezone_adjust_change (&change, 0, 0, 0,
671
-change.prev_utc_offset);
673
icalarray_append (changes, &change);
676
icalrecur_iterator_free (rrule_iterator);
682
prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
687
/** A function to compare 2 icaltimezonechange elements, used for qsort(). */
689
icaltimezone_compare_change_fn (const void *elem1,
692
icaltimezonechange *change1, *change2;
695
change1 = (icaltimezonechange *)elem1;
696
change2 = (icaltimezonechange *)elem2;
698
if (change1->year < change2->year)
700
else if (change1->year > change2->year)
703
else if (change1->month < change2->month)
705
else if (change1->month > change2->month)
708
else if (change1->day < change2->day)
710
else if (change1->day > change2->day)
713
else if (change1->hour < change2->hour)
715
else if (change1->hour > change2->hour)
718
else if (change1->minute < change2->minute)
720
else if (change1->minute > change2->minute)
723
else if (change1->second < change2->second)
725
else if (change1->second > change2->second)
737
icaltimezone_convert_time (struct icaltimetype *tt,
738
icaltimezone *from_zone,
739
icaltimezone *to_zone)
741
int utc_offset, is_daylight;
743
/* If the time is a DATE value or both timezones are the same, or we are
744
converting a floating time, we don't need to do anything. */
745
if (icaltime_is_date(*tt) || from_zone == to_zone || from_zone == NULL)
748
/* Convert the time to UTC by getting the UTC offset and subtracting it. */
749
utc_offset = icaltimezone_get_utc_offset (from_zone, tt, NULL);
750
icaltime_adjust (tt, 0, 0, 0, -utc_offset);
752
/* Now we convert the time to the new timezone by getting the UTC offset
753
of our UTC time and adding it. */
754
utc_offset = icaltimezone_get_utc_offset_of_utc_time (to_zone, tt,
756
tt->is_daylight = is_daylight;
757
icaltime_adjust (tt, 0, 0, 0, utc_offset);
763
/** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
764
timezone. Also, this takes a pointer instead of the struct. */
765
/* Calculates the UTC offset of a given local time in the given
766
timezone. It is the number of seconds to add to UTC to get local
767
time. The is_daylight flag is set to 1 if the time is in
768
daylight-savings time. */
770
icaltimezone_get_utc_offset (icaltimezone *zone,
771
struct icaltimetype *tt,
774
icaltimezonechange *zone_change, *prev_zone_change, tt_change, tmp_change;
775
int change_num, step, utc_offset_change, cmp;
776
int change_num_to_use;
785
/* For local times and UTC return 0. */
786
if (zone == NULL || zone == &utc_timezone)
789
/* Use the builtin icaltimezone if possible. */
790
if (zone->builtin_timezone)
791
zone = zone->builtin_timezone;
793
/* Make sure the changes array is expanded up to the given time. */
794
icaltimezone_ensure_coverage (zone, tt->year);
796
if (!zone->changes || zone->changes->num_elements == 0)
799
/* Copy the time parts of the icaltimetype to an icaltimezonechange so we
800
can use our comparison function on it. */
801
tt_change.year = tt->year;
802
tt_change.month = tt->month;
803
tt_change.day = tt->day;
804
tt_change.hour = tt->hour;
805
tt_change.minute = tt->minute;
806
tt_change.second = tt->second;
808
/* This should find a change close to the time, either the change before
809
it or the change after it. */
810
change_num = icaltimezone_find_nearby_change (zone, &tt_change);
813
icalerror_assert (change_num >= 0,
814
"Negative timezone change index");
815
icalerror_assert (change_num < zone->changes->num_elements,
816
"Timezone change index out of bounds");
818
/* Now move backwards or forwards to find the timezone change that applies
819
to tt. It should only have to do 1 or 2 steps. */
820
zone_change = icalarray_element_at (zone->changes, change_num);
822
change_num_to_use = -1;
824
/* Copy the change, so we can adjust it. */
825
tmp_change = *zone_change;
827
/* If the clock is going backward, check if it is in the region of time
828
that is used twice. If it is, use the change with the daylight
829
setting which matches tt, or use standard if we don't know. */
830
if (tmp_change.utc_offset < tmp_change.prev_utc_offset) {
831
/* If the time change is at 2:00AM local time and the clock is
832
going back to 1:00AM we adjust the change to 1:00AM. We may
833
have the wrong change but we'll figure that out later. */
834
icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
835
tmp_change.utc_offset);
837
icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
838
tmp_change.prev_utc_offset);
841
cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
843
/* If the given time is on or after this change, then this change may
844
apply, but we continue as a later change may be the right one.
845
If the given time is before this change, then if we have already
846
found a change which applies we can use that, else we need to step
849
change_num_to_use = change_num;
853
/* If we are stepping backwards through the changes and we have found
854
a change that applies, then we know this is the change to use so
856
if (step == -1 && change_num_to_use != -1)
861
/* If we go past the start of the changes array, then we have no data
862
for this time so we return a UTC offset of 0. */
866
if ((unsigned int)change_num >= zone->changes->num_elements)
869
zone_change = icalarray_element_at (zone->changes, change_num);
872
/* If we didn't find a change to use, then we have a bug! */
873
icalerror_assert (change_num_to_use != -1,
874
"No applicable timezone change found");
876
/* Now we just need to check if the time is in the overlapped region of
877
time when clocks go back. */
878
zone_change = icalarray_element_at (zone->changes, change_num_to_use);
880
utc_offset_change = zone_change->utc_offset - zone_change->prev_utc_offset;
881
if (utc_offset_change < 0 && change_num_to_use > 0) {
882
tmp_change = *zone_change;
883
icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
884
tmp_change.prev_utc_offset);
886
if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) < 0) {
887
/* The time is in the overlapped region, so we may need to use
888
either the current zone_change or the previous one. If the
889
time has the is_daylight field set we use the matching change,
890
else we use the change with standard time. */
891
prev_zone_change = icalarray_element_at (zone->changes,
892
change_num_to_use - 1);
894
/* I was going to add an is_daylight flag to struct icaltimetype,
895
but iCalendar doesn't let us distinguish between standard and
896
daylight time anyway, so there's no point. So we just use the
897
standard time instead. */
898
want_daylight = (tt->is_daylight == 1) ? 1 : 0;
901
if (zone_change->is_daylight == prev_zone_change->is_daylight)
902
printf (" **** Same is_daylight setting\n");
905
if (zone_change->is_daylight != want_daylight
906
&& prev_zone_change->is_daylight == want_daylight)
907
zone_change = prev_zone_change;
911
/* Now we know exactly which timezone change applies to the time, so
912
we can return the UTC offset and whether it is a daylight time. */
914
*is_daylight = zone_change->is_daylight;
915
return zone_change->utc_offset;
919
/** @deprecated This API wasn't updated when we changed icaltimetype to contain its own
920
timezone. Also, this takes a pointer instead of the struct. */
921
/** Calculates the UTC offset of a given UTC time in the given
922
timezone. It is the number of seconds to add to UTC to get local
923
time. The is_daylight flag is set to 1 if the time is in
924
daylight-savings time. */
926
icaltimezone_get_utc_offset_of_utc_time (icaltimezone *zone,
927
struct icaltimetype *tt,
930
icaltimezonechange *zone_change, tt_change, tmp_change;
931
int change_num, step, change_num_to_use;
936
/* For local times and UTC return 0. */
937
if (zone == NULL || zone == &utc_timezone)
940
/* Use the builtin icaltimezone if possible. */
941
if (zone->builtin_timezone)
942
zone = zone->builtin_timezone;
944
/* Make sure the changes array is expanded up to the given time. */
945
icaltimezone_ensure_coverage (zone, tt->year);
947
if (!zone->changes || zone->changes->num_elements == 0)
950
/* Copy the time parts of the icaltimetype to an icaltimezonechange so we
951
can use our comparison function on it. */
952
tt_change.year = tt->year;
953
tt_change.month = tt->month;
954
tt_change.day = tt->day;
955
tt_change.hour = tt->hour;
956
tt_change.minute = tt->minute;
957
tt_change.second = tt->second;
959
/* This should find a change close to the time, either the change before
960
it or the change after it. */
961
change_num = icaltimezone_find_nearby_change (zone, &tt_change);
964
icalerror_assert (change_num >= 0,
965
"Negative timezone change index");
966
icalerror_assert (change_num < zone->changes->num_elements,
967
"Timezone change index out of bounds");
969
/* Now move backwards or forwards to find the timezone change that applies
970
to tt. It should only have to do 1 or 2 steps. */
971
zone_change = icalarray_element_at (zone->changes, change_num);
973
change_num_to_use = -1;
975
/* Copy the change and adjust it to UTC. */
976
tmp_change = *zone_change;
978
/* If the given time is on or after this change, then this change may
979
apply, but we continue as a later change may be the right one.
980
If the given time is before this change, then if we have already
981
found a change which applies we can use that, else we need to step
983
if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
984
change_num_to_use = change_num;
988
/* If we are stepping backwards through the changes and we have found
989
a change that applies, then we know this is the change to use so
991
if (step == -1 && change_num_to_use != -1)
996
/* If we go past the start of the changes array, then we have no data
997
for this time so we return a UTC offset of 0. */
1001
if ((unsigned int)change_num >= zone->changes->num_elements)
1004
zone_change = icalarray_element_at (zone->changes, change_num);
1007
/* If we didn't find a change to use, then we have a bug! */
1008
icalerror_assert (change_num_to_use != -1,
1009
"No applicable timezone change found");
1011
/* Now we know exactly which timezone change applies to the time, so
1012
we can return the UTC offset and whether it is a daylight time. */
1013
zone_change = icalarray_element_at (zone->changes, change_num_to_use);
1015
*is_daylight = zone_change->is_daylight;
1017
return zone_change->utc_offset;
1021
/** Returns the index of a timezone change which is close to the time
1024
icaltimezone_find_nearby_change (icaltimezone *zone,
1025
icaltimezonechange *change)
1027
icaltimezonechange *zone_change;
1028
int lower, upper, middle, cmp;
1030
/* Do a simple binary search. */
1032
upper = zone->changes->num_elements;
1034
while (lower < upper) {
1035
middle = (lower + upper) / 2;
1036
zone_change = icalarray_element_at (zone->changes, middle);
1037
cmp = icaltimezone_compare_change_fn (change, zone_change);
1052
/** Adds (or subtracts) a time from a icaltimezonechange. NOTE: This
1053
function is exactly the same as icaltime_adjust() except for the
1054
type of the first parameter. */
1056
icaltimezone_adjust_change (icaltimezonechange *tt,
1062
int second, minute, hour, day;
1063
int minutes_overflow, hours_overflow, days_overflow;
1066
/* Add on the seconds. */
1067
second = tt->second + seconds;
1068
tt->second = second % 60;
1069
minutes_overflow = second / 60;
1070
if (tt->second < 0) {
1075
/* Add on the minutes. */
1076
minute = tt->minute + minutes + minutes_overflow;
1077
tt->minute = minute % 60;
1078
hours_overflow = minute / 60;
1079
if (tt->minute < 0) {
1084
/* Add on the hours. */
1085
hour = tt->hour + hours + hours_overflow;
1086
tt->hour = hour % 24;
1087
days_overflow = hour / 24;
1093
/* Add on the days. */
1094
day = tt->day + days + days_overflow;
1097
days_in_month = icaltime_days_in_month (tt->month, tt->year);
1098
if (day <= days_in_month)
1102
if (tt->month >= 13) {
1107
day -= days_in_month;
1111
if (tt->month == 1) {
1118
day += icaltime_days_in_month (tt->month, tt->year);
1126
icaltimezone_get_tzid (icaltimezone *zone)
1128
/* If this is a floating time, without a timezone, return NULL. */
1133
icaltimezone_load_builtin_timezone (zone);
1140
icaltimezone_get_location (icaltimezone *zone)
1142
/* If this is a floating time, without a timezone, return NULL. */
1146
/* Note that for builtin timezones this comes from zones.tab so we don't
1147
need to check the timezone is loaded here. */
1148
return zone->location;
1153
icaltimezone_get_tznames (icaltimezone *zone)
1155
/* If this is a floating time, without a timezone, return NULL. */
1159
if (!zone->component)
1160
icaltimezone_load_builtin_timezone (zone);
1162
return zone->tznames;
1166
/** Returns the latitude of a builtin timezone. */
1168
icaltimezone_get_latitude (icaltimezone *zone)
1170
/* If this is a floating time, without a timezone, return 0. */
1174
/* Note that for builtin timezones this comes from zones.tab so we don't
1175
need to check the timezone is loaded here. */
1176
return zone->latitude;
1180
/** Returns the longitude of a builtin timezone. */
1182
icaltimezone_get_longitude (icaltimezone *zone)
1184
/* If this is a floating time, without a timezone, return 0. */
1188
/* Note that for builtin timezones this comes from zones.tab so we don't
1189
need to check the timezone is loaded here. */
1190
return zone->longitude;
1194
/** Returns the VTIMEZONE component of a timezone. */
1196
icaltimezone_get_component (icaltimezone *zone)
1198
/* If this is a floating time, without a timezone, return NULL. */
1202
if (!zone->component)
1203
icaltimezone_load_builtin_timezone (zone);
1205
return zone->component;
1209
/** Sets the VTIMEZONE component of an icaltimezone, initializing the
1210
tzid, location & tzname fields. It returns 1 on success or 0 on
1211
failure, i.e. no TZID was found. */
1213
icaltimezone_set_component (icaltimezone *zone,
1214
icalcomponent *comp)
1216
icaltimezone_reset (zone);
1217
return icaltimezone_get_vtimezone_properties (zone, comp);
1222
icaltimezone_array_new (void)
1224
return icalarray_new (sizeof (icaltimezone), 16);
1229
icaltimezone_array_append_from_vtimezone (icalarray *timezones,
1230
icalcomponent *child)
1234
icaltimezone_init (&zone);
1235
if (icaltimezone_get_vtimezone_properties (&zone, child))
1236
icalarray_append (timezones, &zone);
1241
icaltimezone_array_free (icalarray *timezones)
1248
for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
1249
zone = icalarray_element_at (timezones, i);
1250
icaltimezone_free (zone, 0);
1253
icalarray_free (timezones);
1259
* BUILTIN TIMEZONE HANDLING
1263
/** Returns an icalarray of icaltimezone structs, one for each builtin
1264
timezone. This will load and parse the zones.tab file to get the
1265
timezone names and their coordinates. It will not load the
1266
VTIMEZONE data for any timezones. */
1268
icaltimezone_get_builtin_timezones (void)
1270
if (!builtin_timezones)
1271
icaltimezone_init_builtin_timezones ();
1273
return builtin_timezones;
1276
/** Release builtin timezone memory */
1278
icaltimezone_free_builtin_timezones(void)
1280
icaltimezone_array_free(builtin_timezones);
1284
/** Returns a single builtin timezone, given its Olson city name. */
1286
icaltimezone_get_builtin_timezone (const char *location)
1289
int lower, upper, middle, cmp;
1290
const char *zone_location;
1292
if (!location || !location[0])
1295
if (!strcmp (location, "UTC"))
1296
return &utc_timezone;
1298
if (!builtin_timezones)
1299
icaltimezone_init_builtin_timezones ();
1301
/* Do a simple binary search. */
1303
upper = builtin_timezones->num_elements;
1305
while (lower < upper) {
1306
middle = (lower + upper) / 2;
1307
zone = icalarray_element_at (builtin_timezones, middle);
1308
zone_location = icaltimezone_get_location (zone);
1309
cmp = strcmp (location, zone_location);
1322
/** Returns a single builtin timezone, given its TZID. */
1324
icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
1326
int num_slashes = 0;
1327
const char *p, *zone_tzid;
1330
if (!tzid || !tzid[0])
1333
/* Check that the TZID starts with our unique prefix. */
1334
if (strncmp (tzid, TZID_PREFIX, TZID_PREFIX_LEN))
1337
/* Get the location, which is after the 3rd '/' character. */
1339
for (p = tzid; *p; p++) {
1342
if (num_slashes == 3)
1347
if (num_slashes != 3)
1352
/* Now we can use the function to get the builtin timezone from the
1354
zone = icaltimezone_get_builtin_timezone (p);
1358
/* Check that the builtin TZID matches exactly. We don't want to return
1359
a different version of the VTIMEZONE. */
1360
zone_tzid = icaltimezone_get_tzid (zone);
1361
if (!strcmp (zone_tzid, tzid))
1368
/** Returns the special UTC timezone. */
1370
icaltimezone_get_utc_timezone (void)
1372
if (!builtin_timezones)
1373
icaltimezone_init_builtin_timezones ();
1375
return &utc_timezone;
1380
/** This initializes the builtin timezone data, i.e. the
1381
builtin_timezones array and the special UTC timezone. It should be
1382
called before any code that uses the timezone functions. */
1384
icaltimezone_init_builtin_timezones (void)
1386
/* Initialize the special UTC timezone. */
1387
utc_timezone.tzid = (char *)"UTC";
1389
icaltimezone_parse_zone_tab ();
1393
/** This parses the zones.tab file containing the names and locations
1394
of the builtin timezones. It creates the builtin_timezones array
1395
which is an icalarray of icaltimezone structs. It only fills in the
1396
location, latitude and longtude fields; the rest are left
1397
blank. The VTIMEZONE component is loaded later if it is needed. The
1398
timezones in the zones.tab file are sorted by their name, which is
1399
useful for binary searches. */
1401
icaltimezone_parse_zone_tab (void)
1405
char buf[1024]; /* Used to store each line of zones.tab as it is read. */
1406
char location[1024]; /* Stores the city name when parsing buf. */
1407
unsigned int filename_len;
1408
int latitude_degrees, latitude_minutes, latitude_seconds;
1409
int longitude_degrees, longitude_minutes, longitude_seconds;
1412
icalerror_assert (builtin_timezones == NULL,
1413
"Parsing zones.tab file multiple times");
1415
builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
1417
filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
1420
filename = (char*) malloc (filename_len);
1422
icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1426
snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
1427
ZONES_TAB_FILENAME);
1429
fp = fopen (filename, "r");
1432
icalerror_set_errno(ICAL_FILE_ERROR);
1436
while (fgets (buf, sizeof(buf), fp)) {
1437
if (*buf == '#') continue;
1439
/* The format of each line is: "latitude longitude location". */
1440
if (sscanf (buf, "%4d%2d%2d %4d%2d%2d %s",
1441
&latitude_degrees, &latitude_minutes,
1443
&longitude_degrees, &longitude_minutes,
1446
fprintf (stderr, "Invalid timezone description line: %s\n", buf);
1450
icaltimezone_init (&zone);
1451
zone.location = strdup (location);
1453
if (latitude_degrees >= 0)
1454
zone.latitude = (double) latitude_degrees
1455
+ (double) latitude_minutes / 60
1456
+ (double) latitude_seconds / 3600;
1458
zone.latitude = (double) latitude_degrees
1459
- (double) latitude_minutes / 60
1460
- (double) latitude_seconds / 3600;
1462
if (longitude_degrees >= 0)
1463
zone.longitude = (double) longitude_degrees
1464
+ (double) longitude_minutes / 60
1465
+ (double) longitude_seconds / 3600;
1467
zone.longitude = (double) longitude_degrees
1468
- (double) longitude_minutes / 60
1469
- (double) longitude_seconds / 3600;
1471
icalarray_append (builtin_timezones, &zone);
1474
printf ("Found zone: %s %f %f\n",
1475
location, zone.latitude, zone.longitude);
1483
/** Loads the builtin VTIMEZONE data for the given timezone. */
1485
icaltimezone_load_builtin_timezone (icaltimezone *zone)
1488
unsigned int filename_len;
1491
icalcomponent *comp, *subcomp;
1493
/* If the location isn't set, it isn't a builtin timezone. */
1494
if (!zone->location || !zone->location[0])
1497
filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
1499
filename = (char*) malloc (filename_len);
1501
icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1505
snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
1508
fp = fopen (filename, "r");
1511
icalerror_set_errno(ICAL_FILE_ERROR);
1516
/* ##### B.# Sun, 11 Nov 2001 04:04:29 +1100
1517
this is where the MALFORMEDDATA error is being set, after the call to 'icalparser_parse'
1518
fprintf(stderr, "** WARNING ** %s: %d %s\n", __FILE__, __LINE__, icalerror_strerror(icalerrno));
1521
parser = icalparser_new ();
1522
icalparser_set_gen_data (parser, fp);
1523
comp = icalparser_parse (parser, icaltimezone_load_get_line_fn);
1524
icalparser_free (parser);
1529
/* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
1530
subcomp = icalcomponent_get_first_component (comp,
1531
ICAL_VTIMEZONE_COMPONENT);
1533
icalerror_set_errno(ICAL_PARSE_ERROR);
1537
icaltimezone_get_vtimezone_properties (zone, subcomp);
1539
icalcomponent_remove_component(comp,subcomp);
1541
icalcomponent_free(comp);
1546
/** Callback used from icalparser_parse() */
1548
icaltimezone_load_get_line_fn (char *s,
1552
return fgets (s, (int)size, (FILE*) data);
1563
* This outputs a list of timezone changes for the given timezone to the
1564
* given file, up to the maximum year given. We compare this output with the
1565
* output from 'vzic --dump-changes' to make sure that we are consistent.
1566
* (vzic is the Olson timezone database to VTIMEZONE converter.)
1568
* The output format is:
1570
* Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
1572
* The Date and Time fields specify the time change in UTC.
1574
* The UTC Offset is for local (wall-clock) time. It is the amount of time
1575
* to add to UTC to get local time.
1578
icaltimezone_dump_changes (icaltimezone *zone,
1582
static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1583
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1584
icaltimezonechange *zone_change;
1588
/* Make sure the changes array is expanded up to the given time. */
1589
icaltimezone_ensure_coverage (zone, max_year);
1592
printf ("Num changes: %i\n", zone->changes->num_elements);
1596
for (change_num = 0; (unsigned int)change_num < zone->changes->num_elements; change_num++) {
1597
zone_change = icalarray_element_at (zone->changes, change_num);
1599
if (zone_change->year > max_year)
1602
fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
1604
zone_change->day, months[zone_change->month - 1],
1606
zone_change->hour, zone_change->minute, zone_change->second);
1608
/* Wall Clock Time offset from UTC. */
1609
format_utc_offset (zone_change->utc_offset, buffer);
1610
fprintf (fp, "\t%s", buffer);
1618
/** This formats a UTC offset as "+HHMM" or "+HHMMSS".
1619
buffer should have space for 8 characters. */
1621
format_utc_offset (int utc_offset,
1624
const char *sign = "+";
1625
int hours, minutes, seconds;
1627
if (utc_offset < 0) {
1628
utc_offset = -utc_offset;
1632
hours = utc_offset / 3600;
1633
minutes = (utc_offset % 3600) / 60;
1634
seconds = utc_offset % 60;
1636
/* Sanity check. Standard timezone offsets shouldn't be much more than 12
1637
hours, and daylight saving shouldn't change it by more than a few hours.
1638
(The maximum offset is 15 hours 56 minutes at present.) */
1639
if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
1640
|| seconds < 0 || seconds >= 60) {
1641
fprintf (stderr, "Warning: Strange timezone offset: H:%i M:%i S:%i\n",
1642
hours, minutes, seconds);
1646
snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
1648
snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
1651
static const char* get_zone_directory(void)
1653
return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
1656
void set_zone_directory(char *path)
1658
zone_files_directory = malloc(strlen(path)+1);
1659
if ( zone_files_directory != NULL )
1661
strcpy(zone_files_directory,path);
1665
void free_zone_directory(void)
1667
if ( zone_files_directory != NULL )
1669
free(zone_files_directory);