~ubuntu-branches/ubuntu/quantal/kdepimlibs/quantal-proposed

« back to all changes in this revision

Viewing changes to kcal/libical/src/libical/icaltimezone.c

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2006-09-06 22:45:39 UTC
  • Revision ID: james.westby@ubuntu.com-20060906224539-fiq8t03qdbqu7z3i
Tags: upstream-3.80.1
ImportĀ upstreamĀ versionĀ 3.80.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 
2
/*======================================================================
 
3
 FILE: icaltimezone.c
 
4
 CREATOR: Damon Chaplin 15 March 2001
 
5
 
 
6
 $Id: icaltimezone.c 564286 2006-07-19 19:25:58Z chehrlic $
 
7
 $Locker:  $
 
8
 
 
9
 (C) COPYRIGHT 2001, Damon Chaplin
 
10
 
 
11
 This program is free software; you can redistribute it and/or modify
 
12
 it under the terms of either: 
 
13
 
 
14
    The LGPL as published by the Free Software Foundation, version
 
15
    2.1, available at: http://www.fsf.org/copyleft/lesser.html
 
16
 
 
17
  Or:
 
18
 
 
19
    The Mozilla Public License Version 1.0. You may obtain a copy of
 
20
    the License at http://www.mozilla.org/MPL/
 
21
 
 
22
 
 
23
======================================================================*/
 
24
 
 
25
/** @file icaltimezone.c
 
26
 *  @brief implementation of timezone handling routines
 
27
 **/
 
28
 
 
29
#ifdef HAVE_CONFIG_H
 
30
#include "config.h"
 
31
#endif
 
32
 
 
33
#include <stdio.h>
 
34
#include <stdlib.h>
 
35
#include <string.h>
 
36
#include "icalproperty.h"
 
37
#include "icalarray.h"
 
38
#include "icalerror.h"
 
39
#include "icalparser.h"
 
40
#include "icaltimezone.h"
 
41
 
 
42
#ifdef WIN32
 
43
#define snprintf _snprintf
 
44
#endif
 
45
 
 
46
/** This is the toplevel directory where the timezone data is installed in. */
 
47
#define ZONEINFO_DIRECTORY      PACKAGE_DATA_DIR "/zoneinfo"
 
48
 
 
49
/** The prefix we use to uniquely identify TZIDs. */
 
50
#define TZID_PREFIX             "/softwarestudio.org/"
 
51
#define TZID_PREFIX_LEN         20
 
52
 
 
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"
 
56
 
 
57
/** This is the number of years of extra coverage we do when expanding
 
58
    the timezone changes. */
 
59
#define ICALTIMEZONE_EXTRA_COVERAGE     5
 
60
 
 
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
 
64
 
 
65
struct _icaltimezone {
 
66
    char                *tzid;
 
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. */
 
71
 
 
72
    char                *location;
 
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. */
 
78
 
 
79
    char                *tznames;
 
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
 
85
       NULL. */
 
86
 
 
87
    double               latitude;
 
88
    double               longitude;
 
89
    /**< The coordinates of the city, in degrees. */
 
90
 
 
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. */
 
94
 
 
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
 
100
       components. */
 
101
 
 
102
    int                  end_year;
 
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. */
 
106
 
 
107
    icalarray           *changes;
 
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. */
 
111
};
 
112
 
 
113
typedef struct _icaltimezonechange      icaltimezonechange;
 
114
 
 
115
struct _icaltimezonechange {
 
116
    int          utc_offset;
 
117
    /**< The offset to add to UTC to get local time, in seconds. */
 
118
 
 
119
    int          prev_utc_offset;
 
120
    /**< The offset to add to UTC, before this change, in seconds. */
 
121
 
 
122
    int          year;          /**< Actual year, e.g. 2001. */
 
123
    int          month;         /**< 1 (Jan) to 12 (Dec). */
 
124
    int          day;
 
125
    int          hour;
 
126
    int          minute;
 
127
    int          second;
 
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. */
 
131
 
 
132
    int          is_daylight;
 
133
    /**< Whether this is STANDARD or DAYLIGHT time. */
 
134
};
 
135
 
 
136
 
 
137
/** An array of icaltimezones for the builtin timezones. */
 
138
static icalarray *builtin_timezones = NULL;
 
139
 
 
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 };
 
142
 
 
143
static char* zone_files_directory = NULL;
 
144
 
 
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,
 
149
                                                 int             end_year);
 
150
static void  icaltimezone_expand_vtimezone      (icalcomponent  *comp,
 
151
                                                 int             end_year,
 
152
                                                 icalarray      *changes);
 
153
static int   icaltimezone_compare_change_fn     (const void     *elem1,
 
154
                                                 const void     *elem2);
 
155
 
 
156
static int   icaltimezone_find_nearby_change    (icaltimezone *zone,
 
157
                                                 icaltimezonechange *change);
 
158
 
 
159
static void  icaltimezone_adjust_change         (icaltimezonechange *tt,
 
160
                                                 int             days,
 
161
                                                 int             hours,
 
162
                                                 int             minutes,
 
163
                                                 int             seconds);
 
164
 
 
165
static void  icaltimezone_init                  (icaltimezone *zone);
 
166
 
 
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);
 
172
 
 
173
 
 
174
static void  icaltimezone_load_builtin_timezone (icaltimezone *zone);
 
175
 
 
176
static void  icaltimezone_ensure_coverage       (icaltimezone *zone,
 
177
                                                 int             end_year);
 
178
 
 
179
 
 
180
static void  icaltimezone_init_builtin_timezones(void);
 
181
 
 
182
static void  icaltimezone_parse_zone_tab        (void);
 
183
 
 
184
static char* icaltimezone_load_get_line_fn      (char           *s,
 
185
                                                 size_t          size,
 
186
                                                 void           *data);
 
187
 
 
188
static void  format_utc_offset                  (int             utc_offset,
 
189
                                                 char           *buffer);
 
190
 
 
191
static const char* get_zone_directory(void);
 
192
 
 
193
 
 
194
/** Creates a new icaltimezone. */
 
195
icaltimezone*
 
196
icaltimezone_new                        (void)
 
197
{
 
198
    icaltimezone *zone;
 
199
 
 
200
    zone = (icaltimezone*) malloc (sizeof (icaltimezone));
 
201
    if (!zone) {
 
202
        icalerror_set_errno (ICAL_NEWFAILED_ERROR);
 
203
        return NULL;
 
204
    }
 
205
 
 
206
    icaltimezone_init (zone);
 
207
 
 
208
    return zone;
 
209
}
 
210
 
 
211
 
 
212
/** Frees all memory used for the icaltimezone. */
 
213
void
 
214
icaltimezone_free                       (icaltimezone *zone,
 
215
                                         int           free_struct)
 
216
{
 
217
    icaltimezone_reset (zone);
 
218
    if (free_struct)
 
219
        free ((void *)zone);
 
220
}
 
221
 
 
222
 
 
223
/** Resets the icaltimezone to the initial state, freeing most of the fields. */
 
224
static void
 
225
icaltimezone_reset                      (icaltimezone *zone)
 
226
{
 
227
    if (zone->tzid)
 
228
                free (zone->tzid);
 
229
    if (zone->location)
 
230
                free (zone->location);
 
231
    if (zone->tznames)
 
232
                free (zone->tznames);
 
233
    if (zone->component)
 
234
                icalcomponent_free (zone->component);
 
235
    if (zone->changes)
 
236
                icalarray_free (zone->changes);
 
237
        
 
238
    icaltimezone_init (zone);
 
239
}
 
240
 
 
241
 
 
242
/** Initializes an icaltimezone. */
 
243
static void
 
244
icaltimezone_init                       (icaltimezone *zone)
 
245
{
 
246
    zone->tzid = NULL;
 
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;
 
253
    zone->end_year = 0;
 
254
    zone->changes = NULL;
 
255
}
 
256
 
 
257
 
 
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
 
262
   any old values. */
 
263
static int
 
264
icaltimezone_get_vtimezone_properties   (icaltimezone *zone,
 
265
                                         icalcomponent  *component)
 
266
{
 
267
    icalproperty *prop;
 
268
    const char *tzid;
 
269
 
 
270
    prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
 
271
    if (!prop)
 
272
        return 0;
 
273
 
 
274
    /* A VTIMEZONE MUST have a TZID, or a lot of our code won't work. */
 
275
    tzid = icalproperty_get_tzid (prop);
 
276
    if (!tzid)
 
277
        return 0;
 
278
 
 
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);
 
284
 
 
285
    return 1;
 
286
}
 
287
 
 
288
/** Gets the LOCATION or X-LIC-LOCATION property from a VTIMEZONE. */
 
289
static char*
 
290
icaltimezone_get_location_from_vtimezone (icalcomponent *component)
 
291
{
 
292
    icalproperty *prop;
 
293
    const char *location;
 
294
    const char *name;
 
295
 
 
296
    prop = icalcomponent_get_first_property (component,
 
297
                                             ICAL_LOCATION_PROPERTY);
 
298
    if (prop) {
 
299
        location = icalproperty_get_location (prop);
 
300
        if (location)
 
301
            return strdup (location);
 
302
    }
 
303
 
 
304
    prop = icalcomponent_get_first_property (component, ICAL_X_PROPERTY);
 
305
    while (prop) {
 
306
        name = icalproperty_get_x_name (prop);
 
307
        if (name && !strcasecmp (name, "X-LIC-LOCATION")) {
 
308
            location = icalproperty_get_x (prop);
 
309
            if (location)
 
310
                return strdup (location);
 
311
        }
 
312
        prop = icalcomponent_get_next_property (component,
 
313
                                                ICAL_X_PROPERTY);
 
314
    }
 
315
 
 
316
    return NULL;
 
317
}
 
318
 
 
319
 
 
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
 
324
   the caller. */
 
325
static char*
 
326
icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
 
327
{
 
328
    icalcomponent *comp;
 
329
    icalcomponent_kind type;
 
330
    icalproperty *prop;
 
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;
 
337
 
 
338
    standard_max_date = icaltime_null_time();
 
339
    daylight_max_date = icaltime_null_time();
 
340
 
 
341
    /* Step through the STANDARD & DAYLIGHT subcomponents. */
 
342
    comp = icalcomponent_get_first_component (component, ICAL_ANY_COMPONENT);
 
343
    while (comp) {
 
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;
 
349
 
 
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);
 
353
            while (prop) {
 
354
                switch (icalproperty_isa (prop)) {
 
355
                case ICAL_TZNAME_PROPERTY:
 
356
                    current_tzname = icalproperty_get_tzname (prop);
 
357
                    break;
 
358
 
 
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;
 
363
 
 
364
                    break;
 
365
 
 
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;
 
370
 
 
371
                    break;
 
372
 
 
373
                default:
 
374
                    break;
 
375
                }
 
376
 
 
377
                prop = icalcomponent_get_next_property (comp,
 
378
                                                        ICAL_ANY_PROPERTY);
 
379
            }
 
380
 
 
381
            if (current_tzname) {
 
382
                if (type == ICAL_XSTANDARD_COMPONENT) {
 
383
                    if (!standard_tzname
 
384
                        || icaltime_compare (current_max_date,
 
385
                                             standard_max_date) > 0) {
 
386
                        standard_max_date = current_max_date;
 
387
                        standard_tzname = current_tzname;
 
388
                    }
 
389
                } else {
 
390
                    if (!daylight_tzname
 
391
                        || icaltime_compare (current_max_date,
 
392
                                             daylight_max_date) > 0) {
 
393
                        daylight_max_date = current_max_date;
 
394
                        daylight_tzname = current_tzname;
 
395
                    }
 
396
                }
 
397
            }
 
398
        }
 
399
 
 
400
        comp = icalcomponent_get_next_component (component,
 
401
                                                 ICAL_ANY_COMPONENT);
 
402
    }
 
403
 
 
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"))
 
407
        return NULL;
 
408
 
 
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;
 
413
        char *tznames;
 
414
 
 
415
        if (!strcmp (standard_tzname, daylight_tzname))
 
416
            return strdup (standard_tzname);
 
417
 
 
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);
 
424
        return tznames;
 
425
    } else {
 
426
        const char *tznames;
 
427
 
 
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;
 
431
    }
 
432
}
 
433
 
 
434
 
 
435
static void
 
436
icaltimezone_ensure_coverage            (icaltimezone *zone,
 
437
                                         int             end_year)
 
438
{
 
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;
 
442
 
 
443
    int changes_end_year;
 
444
 
 
445
    if (!zone->component)
 
446
        icaltimezone_load_builtin_timezone (zone);
 
447
 
 
448
    if (icaltimezone_minimum_expansion_year == -1) {
 
449
        struct icaltimetype today = icaltime_today();
 
450
        icaltimezone_minimum_expansion_year = today.year;
 
451
    }
 
452
 
 
453
    changes_end_year = end_year;
 
454
    if (changes_end_year < icaltimezone_minimum_expansion_year)
 
455
        changes_end_year = icaltimezone_minimum_expansion_year;
 
456
 
 
457
    changes_end_year += ICALTIMEZONE_EXTRA_COVERAGE;
 
458
 
 
459
    if (changes_end_year > ICALTIMEZONE_MAX_YEAR)
 
460
        changes_end_year = ICALTIMEZONE_MAX_YEAR;
 
461
 
 
462
    if (!zone->changes || zone->end_year < end_year)
 
463
        icaltimezone_expand_changes (zone, changes_end_year);
 
464
}
 
465
 
 
466
 
 
467
static void
 
468
icaltimezone_expand_changes             (icaltimezone *zone,
 
469
                                         int             end_year)
 
470
{
 
471
    icalarray *changes;
 
472
    icalcomponent *comp;
 
473
 
 
474
#if 0
 
475
    printf ("\nExpanding changes for: %s to year: %i\n", zone->tzid, end_year);
 
476
#endif
 
477
 
 
478
    changes = icalarray_new (sizeof (icaltimezonechange), 32);
 
479
    if (!changes)
 
480
        return;
 
481
 
 
482
    /* Scan the STANDARD and DAYLIGHT subcomponents. */
 
483
    comp = icalcomponent_get_first_component (zone->component,
 
484
                                              ICAL_ANY_COMPONENT);
 
485
    while (comp) {
 
486
        icaltimezone_expand_vtimezone (comp, end_year, changes);
 
487
        comp = icalcomponent_get_next_component (zone->component,
 
488
                                                 ICAL_ANY_COMPONENT);
 
489
    }
 
490
 
 
491
    /* Sort the changes. We may have duplicates but I don't think it will
 
492
       matter. */
 
493
    icalarray_sort (changes, icaltimezone_compare_change_fn);
 
494
 
 
495
    if (zone->changes)
 
496
        icalarray_free (zone->changes);
 
497
 
 
498
    zone->changes = changes;
 
499
    zone->end_year = end_year;
 
500
}
 
501
 
 
502
 
 
503
static void
 
504
icaltimezone_expand_vtimezone           (icalcomponent  *comp,
 
505
                                         int             end_year,
 
506
                                         icalarray      *changes)
 
507
{
 
508
    icaltimezonechange change;
 
509
    icalproperty *prop;
 
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;
 
516
 
 
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;
 
523
    else 
 
524
        return;
 
525
 
 
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);
 
530
    while (prop) {
 
531
        switch (icalproperty_isa (prop)) {
 
532
        case ICAL_DTSTART_PROPERTY:
 
533
            dtstart = icalproperty_get_dtstart (prop);
 
534
            found_dtstart = 1;
 
535
            break;
 
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;
 
540
            break;
 
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;
 
545
            break;
 
546
        case ICAL_RDATE_PROPERTY:
 
547
        case ICAL_RRULE_PROPERTY:
 
548
            has_recurrence = 1;
 
549
            break;
 
550
        default:
 
551
            /* Just ignore any other properties. */
 
552
            break;
 
553
        }
 
554
 
 
555
        prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
 
556
    }
 
557
 
 
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)
 
561
        return;
 
562
 
 
563
#if 0
 
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);
 
567
#endif
 
568
 
 
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;
 
578
 
 
579
        /* Convert to UTC. */
 
580
        icaltimezone_adjust_change (&change, 0, 0, 0, -change.prev_utc_offset);
 
581
 
 
582
#if 0
 
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);
 
586
#endif
 
587
 
 
588
        /* Add the change to the array. */
 
589
        icalarray_append (changes, &change);
 
590
        return;
 
591
    }
 
592
 
 
593
    /* The component has recurrence data, so we expand that now. */
 
594
    prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
 
595
    while (prop) {
 
596
#if 0
 
597
        printf ("Expanding property...\n");
 
598
#endif
 
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
 
606
               the DTSTART. */
 
607
            if (icaltime_is_date(rdate.time)) {
 
608
                change.hour   = dtstart.hour;
 
609
                change.minute = dtstart.minute;
 
610
                change.second = dtstart.second;
 
611
            } else {
 
612
                change.hour   = rdate.time.hour;
 
613
                change.minute = rdate.time.minute;
 
614
                change.second = rdate.time.second;
 
615
 
 
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);
 
622
            }
 
623
 
 
624
#if 0
 
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);
 
628
#endif
 
629
 
 
630
            icalarray_append (changes, &change);
 
631
            break;
 
632
        case ICAL_RRULE_PROPERTY:
 
633
            rrule = icalproperty_get_rrule (prop);
 
634
 
 
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
 
637
               it itself. */
 
638
            if (!icaltime_is_null_time (rrule.until) && rrule.until.is_utc) {
 
639
#if 0
 
640
                printf ("  Found RRULE UNTIL in UTC.\n");
 
641
#endif
 
642
 
 
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;
 
649
            }
 
650
 
 
651
            rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
 
652
            for (;;) {
 
653
                occ = icalrecur_iterator_next (rrule_iterator);
 
654
                if (occ.year > end_year || icaltime_is_null_time (occ))
 
655
                    break;
 
656
 
 
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;
 
663
 
 
664
#if 0
 
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);
 
668
#endif
 
669
 
 
670
                icaltimezone_adjust_change (&change, 0, 0, 0,
 
671
                                            -change.prev_utc_offset);
 
672
 
 
673
                icalarray_append (changes, &change);
 
674
            }
 
675
 
 
676
            icalrecur_iterator_free (rrule_iterator);
 
677
            break;
 
678
        default:
 
679
            break;
 
680
        }
 
681
 
 
682
        prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
 
683
    }
 
684
}
 
685
 
 
686
 
 
687
/** A function to compare 2 icaltimezonechange elements, used for qsort(). */
 
688
static int
 
689
icaltimezone_compare_change_fn          (const void     *elem1,
 
690
                                         const void     *elem2)
 
691
{
 
692
    icaltimezonechange *change1, *change2;
 
693
    int retval;
 
694
 
 
695
    change1 = (icaltimezonechange *)elem1;
 
696
    change2 = (icaltimezonechange *)elem2;
 
697
 
 
698
    if (change1->year < change2->year)
 
699
        retval = -1;
 
700
    else if (change1->year > change2->year)
 
701
        retval = 1;
 
702
 
 
703
    else if (change1->month < change2->month)
 
704
        retval = -1;
 
705
    else if (change1->month > change2->month)
 
706
        retval = 1;
 
707
 
 
708
    else if (change1->day < change2->day)
 
709
        retval = -1;
 
710
    else if (change1->day > change2->day)
 
711
        retval = 1;
 
712
 
 
713
    else if (change1->hour < change2->hour)
 
714
        retval = -1;
 
715
    else if (change1->hour > change2->hour)
 
716
        retval = 1;
 
717
 
 
718
    else if (change1->minute < change2->minute)
 
719
        retval = -1;
 
720
    else if (change1->minute > change2->minute)
 
721
        retval = 1;
 
722
 
 
723
    else if (change1->second < change2->second)
 
724
        retval = -1;
 
725
    else if (change1->second > change2->second)
 
726
        retval = 1;
 
727
 
 
728
    else
 
729
        retval = 0;
 
730
 
 
731
    return retval;
 
732
}
 
733
 
 
734
 
 
735
 
 
736
void
 
737
icaltimezone_convert_time               (struct icaltimetype *tt,
 
738
                                         icaltimezone *from_zone,
 
739
                                         icaltimezone *to_zone)
 
740
{
 
741
    int utc_offset, is_daylight;
 
742
 
 
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)
 
746
        return;
 
747
 
 
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);
 
751
 
 
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,
 
755
                                                          &is_daylight);
 
756
    tt->is_daylight = is_daylight;
 
757
    icaltime_adjust (tt, 0, 0, 0, utc_offset);
 
758
}
 
759
 
 
760
 
 
761
 
 
762
 
 
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. */
 
769
int
 
770
icaltimezone_get_utc_offset             (icaltimezone   *zone,
 
771
                                         struct icaltimetype    *tt,
 
772
                                         int            *is_daylight)
 
773
{
 
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;
 
777
    int want_daylight;
 
778
 
 
779
    if (tt == NULL)
 
780
        return 0;
 
781
 
 
782
    if (is_daylight)
 
783
        *is_daylight = 0;
 
784
 
 
785
    /* For local times and UTC return 0. */
 
786
    if (zone == NULL || zone == &utc_timezone)
 
787
        return 0;
 
788
 
 
789
    /* Use the builtin icaltimezone if possible. */
 
790
    if (zone->builtin_timezone)
 
791
        zone = zone->builtin_timezone;
 
792
 
 
793
    /* Make sure the changes array is expanded up to the given time. */
 
794
    icaltimezone_ensure_coverage (zone, tt->year);
 
795
 
 
796
    if (!zone->changes || zone->changes->num_elements == 0)
 
797
        return 0;
 
798
 
 
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;
 
807
 
 
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);
 
811
 
 
812
    /* Sanity check. */
 
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");
 
817
 
 
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);
 
821
    step = 1;
 
822
    change_num_to_use = -1;
 
823
    for (;;) {
 
824
        /* Copy the change, so we can adjust it. */
 
825
        tmp_change = *zone_change;
 
826
 
 
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);
 
836
        } else {
 
837
            icaltimezone_adjust_change (&tmp_change, 0, 0, 0,
 
838
                                        tmp_change.prev_utc_offset);
 
839
        }
 
840
 
 
841
        cmp = icaltimezone_compare_change_fn (&tt_change, &tmp_change);
 
842
 
 
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
 
847
           backwards. */
 
848
        if (cmp >= 0)
 
849
            change_num_to_use = change_num;
 
850
        else
 
851
            step = -1;
 
852
 
 
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
 
855
           we exit the loop. */
 
856
        if (step == -1 && change_num_to_use != -1)
 
857
            break;
 
858
 
 
859
        change_num += step;
 
860
 
 
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. */
 
863
        if (change_num < 0)
 
864
            return 0;
 
865
 
 
866
        if ((unsigned int)change_num >= zone->changes->num_elements)
 
867
            break;
 
868
 
 
869
        zone_change = icalarray_element_at (zone->changes, change_num);
 
870
    }
 
871
 
 
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");
 
875
 
 
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);
 
879
 
 
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);
 
885
 
 
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);
 
893
 
 
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;
 
899
 
 
900
#if 0
 
901
            if (zone_change->is_daylight == prev_zone_change->is_daylight)
 
902
                printf (" **** Same is_daylight setting\n");
 
903
#endif
 
904
 
 
905
            if (zone_change->is_daylight != want_daylight
 
906
                && prev_zone_change->is_daylight == want_daylight)
 
907
                zone_change = prev_zone_change;
 
908
        }
 
909
    }
 
910
 
 
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. */
 
913
    if (is_daylight)
 
914
        *is_daylight = zone_change->is_daylight;
 
915
    return zone_change->utc_offset;
 
916
}
 
917
 
 
918
 
 
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. */
 
925
int
 
926
icaltimezone_get_utc_offset_of_utc_time (icaltimezone   *zone,
 
927
                                         struct icaltimetype    *tt,
 
928
                                         int            *is_daylight)
 
929
{
 
930
    icaltimezonechange *zone_change, tt_change, tmp_change;
 
931
    int change_num, step, change_num_to_use;
 
932
 
 
933
    if (is_daylight)
 
934
        *is_daylight = 0;
 
935
 
 
936
    /* For local times and UTC return 0. */
 
937
    if (zone == NULL || zone == &utc_timezone)
 
938
        return 0;
 
939
 
 
940
    /* Use the builtin icaltimezone if possible. */
 
941
    if (zone->builtin_timezone)
 
942
        zone = zone->builtin_timezone;
 
943
 
 
944
    /* Make sure the changes array is expanded up to the given time. */
 
945
    icaltimezone_ensure_coverage (zone, tt->year);
 
946
 
 
947
    if (!zone->changes || zone->changes->num_elements == 0)
 
948
        return 0;
 
949
 
 
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;
 
958
 
 
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);
 
962
 
 
963
    /* Sanity check. */
 
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");
 
968
 
 
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);
 
972
    step = 1;
 
973
    change_num_to_use = -1;
 
974
    for (;;) {
 
975
        /* Copy the change and adjust it to UTC. */
 
976
        tmp_change = *zone_change;
 
977
 
 
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
 
982
           backwards. */
 
983
        if (icaltimezone_compare_change_fn (&tt_change, &tmp_change) >= 0)
 
984
            change_num_to_use = change_num;
 
985
        else
 
986
            step = -1;
 
987
 
 
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
 
990
           we exit the loop. */
 
991
        if (step == -1 && change_num_to_use != -1)
 
992
            break;
 
993
 
 
994
        change_num += step;
 
995
 
 
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. */
 
998
        if (change_num < 0)
 
999
            return 0;
 
1000
 
 
1001
        if ((unsigned int)change_num >= zone->changes->num_elements)
 
1002
            break;
 
1003
 
 
1004
        zone_change = icalarray_element_at (zone->changes, change_num);
 
1005
    }
 
1006
 
 
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");
 
1010
 
 
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);
 
1014
    if (is_daylight)
 
1015
        *is_daylight = zone_change->is_daylight;
 
1016
 
 
1017
    return zone_change->utc_offset;
 
1018
}
 
1019
 
 
1020
 
 
1021
/** Returns the index of a timezone change which is close to the time
 
1022
   given in change. */
 
1023
static int
 
1024
icaltimezone_find_nearby_change         (icaltimezone   *zone,
 
1025
                                         icaltimezonechange     *change)
 
1026
{
 
1027
    icaltimezonechange *zone_change;
 
1028
    int lower, upper, middle, cmp;
 
1029
                                         
 
1030
    /* Do a simple binary search. */
 
1031
    lower = middle = 0;
 
1032
    upper = zone->changes->num_elements;
 
1033
 
 
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);
 
1038
        if (cmp == 0)
 
1039
            break;
 
1040
        else if (cmp < 0)
 
1041
            upper = middle;
 
1042
        else
 
1043
            lower = middle + 1;
 
1044
    }
 
1045
 
 
1046
    return middle;
 
1047
}
 
1048
 
 
1049
 
 
1050
 
 
1051
 
 
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. */
 
1055
static void
 
1056
icaltimezone_adjust_change              (icaltimezonechange *tt,
 
1057
                                         int             days,
 
1058
                                         int             hours,
 
1059
                                         int             minutes,
 
1060
                                         int             seconds)
 
1061
{
 
1062
    int second, minute, hour, day;
 
1063
    int minutes_overflow, hours_overflow, days_overflow;
 
1064
    int days_in_month;
 
1065
 
 
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) {
 
1071
        tt->second += 60;
 
1072
        minutes_overflow--;
 
1073
    }
 
1074
 
 
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) {
 
1080
        tt->minute += 60;
 
1081
        hours_overflow--;
 
1082
    }
 
1083
 
 
1084
    /* Add on the hours. */
 
1085
    hour = tt->hour + hours + hours_overflow;
 
1086
    tt->hour = hour % 24;
 
1087
    days_overflow = hour / 24;
 
1088
    if (tt->hour < 0) {
 
1089
        tt->hour += 24;
 
1090
        days_overflow--;
 
1091
    }
 
1092
 
 
1093
    /* Add on the days. */
 
1094
    day = tt->day + days + days_overflow;
 
1095
    if (day > 0) {
 
1096
        for (;;) {
 
1097
            days_in_month = icaltime_days_in_month (tt->month, tt->year);
 
1098
            if (day <= days_in_month)
 
1099
                break;
 
1100
 
 
1101
            tt->month++;
 
1102
            if (tt->month >= 13) {
 
1103
                tt->year++;
 
1104
                tt->month = 1;
 
1105
            }
 
1106
 
 
1107
            day -= days_in_month;
 
1108
        }
 
1109
    } else {
 
1110
        while (day <= 0) {
 
1111
            if (tt->month == 1) {
 
1112
                tt->year--;
 
1113
                tt->month = 12;
 
1114
            } else {
 
1115
                tt->month--;
 
1116
            }
 
1117
 
 
1118
            day += icaltime_days_in_month (tt->month, tt->year);
 
1119
        }
 
1120
    }
 
1121
    tt->day = day;
 
1122
}
 
1123
 
 
1124
 
 
1125
const char*
 
1126
icaltimezone_get_tzid                   (icaltimezone *zone)
 
1127
{
 
1128
    /* If this is a floating time, without a timezone, return NULL. */
 
1129
    if (!zone)
 
1130
        return NULL;
 
1131
 
 
1132
    if (!zone->tzid)
 
1133
        icaltimezone_load_builtin_timezone (zone);
 
1134
 
 
1135
    return zone->tzid;
 
1136
}
 
1137
 
 
1138
 
 
1139
const char*
 
1140
icaltimezone_get_location               (icaltimezone *zone)
 
1141
{
 
1142
    /* If this is a floating time, without a timezone, return NULL. */
 
1143
    if (!zone)
 
1144
        return NULL;
 
1145
 
 
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;
 
1149
}
 
1150
 
 
1151
 
 
1152
const char*
 
1153
icaltimezone_get_tznames                (icaltimezone *zone)
 
1154
{
 
1155
    /* If this is a floating time, without a timezone, return NULL. */
 
1156
    if (!zone)
 
1157
        return NULL;
 
1158
 
 
1159
    if (!zone->component)
 
1160
        icaltimezone_load_builtin_timezone (zone);
 
1161
 
 
1162
    return zone->tznames;
 
1163
}
 
1164
 
 
1165
 
 
1166
/** Returns the latitude of a builtin timezone. */
 
1167
double
 
1168
icaltimezone_get_latitude               (icaltimezone *zone)
 
1169
{
 
1170
    /* If this is a floating time, without a timezone, return 0. */
 
1171
    if (!zone)
 
1172
        return 0.0;
 
1173
 
 
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;
 
1177
}
 
1178
 
 
1179
 
 
1180
/** Returns the longitude of a builtin timezone. */
 
1181
double
 
1182
icaltimezone_get_longitude              (icaltimezone *zone)
 
1183
{
 
1184
    /* If this is a floating time, without a timezone, return 0. */
 
1185
    if (!zone)
 
1186
        return 0.0;
 
1187
 
 
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;
 
1191
}
 
1192
 
 
1193
 
 
1194
/** Returns the VTIMEZONE component of a timezone. */
 
1195
icalcomponent*
 
1196
icaltimezone_get_component              (icaltimezone *zone)
 
1197
{
 
1198
    /* If this is a floating time, without a timezone, return NULL. */
 
1199
    if (!zone)
 
1200
        return NULL;
 
1201
 
 
1202
    if (!zone->component)
 
1203
        icaltimezone_load_builtin_timezone (zone);
 
1204
 
 
1205
    return zone->component;
 
1206
}
 
1207
 
 
1208
 
 
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. */
 
1212
int
 
1213
icaltimezone_set_component              (icaltimezone *zone,
 
1214
                                         icalcomponent  *comp)
 
1215
{
 
1216
    icaltimezone_reset (zone);
 
1217
    return icaltimezone_get_vtimezone_properties (zone, comp);
 
1218
}
 
1219
 
 
1220
 
 
1221
icalarray*
 
1222
icaltimezone_array_new                  (void)
 
1223
{
 
1224
    return icalarray_new (sizeof (icaltimezone), 16);
 
1225
}
 
1226
 
 
1227
 
 
1228
void
 
1229
icaltimezone_array_append_from_vtimezone (icalarray     *timezones,
 
1230
                                          icalcomponent *child)
 
1231
{
 
1232
    icaltimezone zone;
 
1233
 
 
1234
    icaltimezone_init (&zone);
 
1235
    if (icaltimezone_get_vtimezone_properties (&zone, child))
 
1236
        icalarray_append (timezones, &zone);
 
1237
}
 
1238
 
 
1239
 
 
1240
void
 
1241
icaltimezone_array_free                 (icalarray      *timezones)
 
1242
{
 
1243
    icaltimezone *zone;
 
1244
    int i;
 
1245
 
 
1246
        if ( timezones )
 
1247
        {
 
1248
                for (i = 0; (unsigned int)i < timezones->num_elements; i++) {
 
1249
                zone = icalarray_element_at (timezones, i);
 
1250
                icaltimezone_free (zone, 0);
 
1251
                }
 
1252
 
 
1253
                icalarray_free (timezones);
 
1254
        }
 
1255
}
 
1256
 
 
1257
 
 
1258
/*
 
1259
 * BUILTIN TIMEZONE HANDLING
 
1260
 */
 
1261
 
 
1262
 
 
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. */
 
1267
icalarray*
 
1268
icaltimezone_get_builtin_timezones      (void)
 
1269
{
 
1270
    if (!builtin_timezones)
 
1271
        icaltimezone_init_builtin_timezones ();
 
1272
 
 
1273
    return builtin_timezones;
 
1274
}
 
1275
 
 
1276
/** Release builtin timezone memory */
 
1277
void
 
1278
icaltimezone_free_builtin_timezones(void)
 
1279
{
 
1280
        icaltimezone_array_free(builtin_timezones);
 
1281
}
 
1282
 
 
1283
 
 
1284
/** Returns a single builtin timezone, given its Olson city name. */
 
1285
icaltimezone*
 
1286
icaltimezone_get_builtin_timezone       (const char *location)
 
1287
{
 
1288
    icaltimezone *zone;
 
1289
    int lower, upper, middle, cmp;
 
1290
    const char *zone_location;
 
1291
 
 
1292
    if (!location || !location[0])
 
1293
        return NULL;
 
1294
 
 
1295
    if (!strcmp (location, "UTC"))
 
1296
        return &utc_timezone;
 
1297
 
 
1298
    if (!builtin_timezones)
 
1299
        icaltimezone_init_builtin_timezones ();
 
1300
 
 
1301
    /* Do a simple binary search. */
 
1302
    lower = middle = 0;
 
1303
    upper = builtin_timezones->num_elements;
 
1304
 
 
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);
 
1310
        if (cmp == 0)
 
1311
            return zone;
 
1312
        else if (cmp < 0)
 
1313
            upper = middle;
 
1314
        else
 
1315
            lower = middle + 1;
 
1316
    }
 
1317
 
 
1318
    return NULL;
 
1319
}
 
1320
 
 
1321
 
 
1322
/** Returns a single builtin timezone, given its TZID. */
 
1323
icaltimezone*
 
1324
icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
 
1325
{
 
1326
    int num_slashes = 0;
 
1327
    const char *p, *zone_tzid;
 
1328
    icaltimezone *zone;
 
1329
 
 
1330
    if (!tzid || !tzid[0])
 
1331
        return NULL;
 
1332
 
 
1333
    /* Check that the TZID starts with our unique prefix. */
 
1334
    if (strncmp (tzid, TZID_PREFIX, TZID_PREFIX_LEN))
 
1335
        return NULL;
 
1336
 
 
1337
    /* Get the location, which is after the 3rd '/' character. */
 
1338
    p = tzid;
 
1339
    for (p = tzid; *p; p++) {
 
1340
        if (*p == '/') {
 
1341
            num_slashes++;
 
1342
            if (num_slashes == 3)
 
1343
                break;
 
1344
        }
 
1345
    }
 
1346
 
 
1347
    if (num_slashes != 3)
 
1348
        return NULL;
 
1349
 
 
1350
    p++;
 
1351
 
 
1352
    /* Now we can use the function to get the builtin timezone from the
 
1353
       location string. */
 
1354
    zone = icaltimezone_get_builtin_timezone (p);
 
1355
    if (!zone)
 
1356
        return NULL;
 
1357
 
 
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))
 
1362
        return zone;
 
1363
    else
 
1364
        return NULL;
 
1365
}
 
1366
 
 
1367
 
 
1368
/** Returns the special UTC timezone. */
 
1369
icaltimezone*
 
1370
icaltimezone_get_utc_timezone           (void)
 
1371
{
 
1372
    if (!builtin_timezones)
 
1373
        icaltimezone_init_builtin_timezones ();
 
1374
 
 
1375
    return &utc_timezone;
 
1376
}
 
1377
 
 
1378
 
 
1379
 
 
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. */
 
1383
static void
 
1384
icaltimezone_init_builtin_timezones     (void)
 
1385
{
 
1386
    /* Initialize the special UTC timezone. */
 
1387
    utc_timezone.tzid = (char *)"UTC";
 
1388
 
 
1389
    icaltimezone_parse_zone_tab ();
 
1390
}
 
1391
 
 
1392
 
 
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. */
 
1400
static void
 
1401
icaltimezone_parse_zone_tab             (void)
 
1402
{
 
1403
    char *filename;
 
1404
    FILE *fp;
 
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;
 
1410
    icaltimezone zone;
 
1411
 
 
1412
    icalerror_assert (builtin_timezones == NULL,
 
1413
                      "Parsing zones.tab file multiple times");
 
1414
 
 
1415
    builtin_timezones = icalarray_new (sizeof (icaltimezone), 32);
 
1416
 
 
1417
    filename_len = strlen (get_zone_directory()) + strlen (ZONES_TAB_FILENAME)
 
1418
        + 2;
 
1419
 
 
1420
    filename = (char*) malloc (filename_len);
 
1421
    if (!filename) {
 
1422
        icalerror_set_errno(ICAL_NEWFAILED_ERROR);
 
1423
        return;
 
1424
    }
 
1425
 
 
1426
    snprintf (filename, filename_len, "%s/%s", get_zone_directory(),
 
1427
              ZONES_TAB_FILENAME);
 
1428
 
 
1429
    fp = fopen (filename, "r");
 
1430
    free (filename);
 
1431
    if (!fp) {
 
1432
        icalerror_set_errno(ICAL_FILE_ERROR);
 
1433
        return;
 
1434
    }
 
1435
 
 
1436
    while (fgets (buf, sizeof(buf), fp)) {
 
1437
        if (*buf == '#') continue;
 
1438
 
 
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,
 
1442
                    &latitude_seconds,
 
1443
                    &longitude_degrees, &longitude_minutes,
 
1444
                    &longitude_seconds,
 
1445
                    location) != 7) {
 
1446
            fprintf (stderr, "Invalid timezone description line: %s\n", buf);
 
1447
            continue;
 
1448
        }
 
1449
 
 
1450
        icaltimezone_init (&zone);
 
1451
        zone.location = strdup (location);
 
1452
 
 
1453
        if (latitude_degrees >= 0)
 
1454
            zone.latitude = (double) latitude_degrees
 
1455
                + (double) latitude_minutes / 60
 
1456
                + (double) latitude_seconds / 3600;
 
1457
        else
 
1458
            zone.latitude = (double) latitude_degrees
 
1459
                - (double) latitude_minutes / 60
 
1460
                - (double) latitude_seconds / 3600;
 
1461
 
 
1462
        if (longitude_degrees >= 0)
 
1463
            zone.longitude = (double) longitude_degrees
 
1464
                + (double) longitude_minutes / 60
 
1465
                + (double) longitude_seconds / 3600;
 
1466
        else
 
1467
            zone.longitude = (double) longitude_degrees
 
1468
                - (double) longitude_minutes / 60
 
1469
                - (double) longitude_seconds / 3600;
 
1470
 
 
1471
        icalarray_append (builtin_timezones, &zone);
 
1472
 
 
1473
#if 0
 
1474
        printf ("Found zone: %s %f %f\n",
 
1475
                location, zone.latitude, zone.longitude);
 
1476
#endif
 
1477
    }
 
1478
 
 
1479
    fclose (fp);
 
1480
}
 
1481
 
 
1482
 
 
1483
/** Loads the builtin VTIMEZONE data for the given timezone. */
 
1484
static void
 
1485
icaltimezone_load_builtin_timezone      (icaltimezone *zone)
 
1486
{
 
1487
    char *filename;
 
1488
    unsigned int filename_len;
 
1489
    FILE *fp;
 
1490
    icalparser *parser;
 
1491
    icalcomponent *comp, *subcomp;
 
1492
 
 
1493
            /* If the location isn't set, it isn't a builtin timezone. */
 
1494
    if (!zone->location || !zone->location[0])
 
1495
        return;
 
1496
 
 
1497
    filename_len = strlen (get_zone_directory()) + strlen (zone->location) + 6;
 
1498
 
 
1499
    filename = (char*) malloc (filename_len);
 
1500
    if (!filename) {
 
1501
        icalerror_set_errno(ICAL_NEWFAILED_ERROR);
 
1502
        return;
 
1503
    }
 
1504
 
 
1505
    snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
 
1506
              zone->location);
 
1507
 
 
1508
    fp = fopen (filename, "r");
 
1509
    free (filename);
 
1510
    if (!fp) {
 
1511
        icalerror_set_errno(ICAL_FILE_ERROR);
 
1512
        return;
 
1513
    }
 
1514
 
 
1515
        
 
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));
 
1519
        */
 
1520
 
 
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);
 
1525
        fclose (fp);
 
1526
 
 
1527
        
 
1528
        
 
1529
    /* Find the VTIMEZONE component inside the VCALENDAR. There should be 1. */
 
1530
    subcomp = icalcomponent_get_first_component (comp,
 
1531
                                                 ICAL_VTIMEZONE_COMPONENT);
 
1532
    if (!subcomp) {
 
1533
        icalerror_set_errno(ICAL_PARSE_ERROR);
 
1534
        return;
 
1535
    }
 
1536
 
 
1537
    icaltimezone_get_vtimezone_properties (zone, subcomp);
 
1538
 
 
1539
        icalcomponent_remove_component(comp,subcomp);
 
1540
 
 
1541
        icalcomponent_free(comp);
 
1542
 
 
1543
}
 
1544
 
 
1545
 
 
1546
/** Callback used from icalparser_parse() */
 
1547
static char *
 
1548
icaltimezone_load_get_line_fn           (char           *s,
 
1549
                                         size_t          size,
 
1550
                                         void           *data)
 
1551
{
 
1552
    return fgets (s, (int)size, (FILE*) data);
 
1553
}
 
1554
 
 
1555
 
 
1556
 
 
1557
 
 
1558
/*
 
1559
 * DEBUGGING
 
1560
 */
 
1561
 
 
1562
/**
 
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.)
 
1567
 * 
 
1568
 * The output format is:
 
1569
 *
 
1570
 *      Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
 
1571
 *
 
1572
 * The Date and Time fields specify the time change in UTC.
 
1573
 *
 
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.
 
1576
 */
 
1577
int
 
1578
icaltimezone_dump_changes               (icaltimezone *zone,
 
1579
                                         int             max_year,
 
1580
                                         FILE           *fp)
 
1581
{
 
1582
    static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 
1583
                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
 
1584
    icaltimezonechange *zone_change;
 
1585
    int change_num;
 
1586
    char buffer[8];
 
1587
 
 
1588
    /* Make sure the changes array is expanded up to the given time. */
 
1589
    icaltimezone_ensure_coverage (zone, max_year);
 
1590
 
 
1591
#if 0
 
1592
    printf ("Num changes: %i\n", zone->changes->num_elements);
 
1593
#endif
 
1594
 
 
1595
    change_num = 0;
 
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);
 
1598
 
 
1599
        if (zone_change->year > max_year)
 
1600
            break;
 
1601
 
 
1602
        fprintf (fp, "%s\t%2i %s %04i\t%2i:%02i:%02i",
 
1603
                zone->location,
 
1604
                zone_change->day, months[zone_change->month - 1],
 
1605
                zone_change->year,
 
1606
                zone_change->hour, zone_change->minute, zone_change->second);
 
1607
 
 
1608
        /* Wall Clock Time offset from UTC. */
 
1609
        format_utc_offset (zone_change->utc_offset, buffer);
 
1610
        fprintf (fp, "\t%s", buffer);
 
1611
 
 
1612
        fprintf (fp, "\n");
 
1613
    }
 
1614
        return 1;
 
1615
}
 
1616
 
 
1617
 
 
1618
/** This formats a UTC offset as "+HHMM" or "+HHMMSS".
 
1619
   buffer should have space for 8 characters. */
 
1620
static void
 
1621
format_utc_offset                       (int             utc_offset,
 
1622
                                         char           *buffer)
 
1623
{
 
1624
  const char *sign = "+";
 
1625
  int hours, minutes, seconds;
 
1626
 
 
1627
  if (utc_offset < 0) {
 
1628
    utc_offset = -utc_offset;
 
1629
    sign = "-";
 
1630
  }
 
1631
 
 
1632
  hours = utc_offset / 3600;
 
1633
  minutes = (utc_offset % 3600) / 60;
 
1634
  seconds = utc_offset % 60;
 
1635
 
 
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);
 
1643
  }
 
1644
 
 
1645
  if (seconds == 0)
 
1646
    snprintf (buffer, sizeof(buffer), "%s%02i%02i", sign, hours, minutes);
 
1647
  else
 
1648
    snprintf (buffer, sizeof(buffer), "%s%02i%02i%02i", sign, hours, minutes, seconds);
 
1649
}
 
1650
 
 
1651
static const char* get_zone_directory(void)
 
1652
{
 
1653
        return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
 
1654
}
 
1655
 
 
1656
void set_zone_directory(char *path)
 
1657
{
 
1658
        zone_files_directory = malloc(strlen(path)+1);
 
1659
        if ( zone_files_directory != NULL )
 
1660
        {
 
1661
                strcpy(zone_files_directory,path);
 
1662
        }
 
1663
}
 
1664
 
 
1665
void free_zone_directory(void)
 
1666
{
 
1667
        if ( zone_files_directory != NULL )
 
1668
        {
 
1669
                free(zone_files_directory);
 
1670
        }
 
1671
}