summaryrefslogtreecommitdiff
path: root/src/libical/icaltimezone.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libical/icaltimezone.c')
-rw-r--r--src/libical/icaltimezone.c286
1 files changed, 192 insertions, 94 deletions
diff --git a/src/libical/icaltimezone.c b/src/libical/icaltimezone.c
index dd0e511..aa9c1d9 100644
--- a/src/libical/icaltimezone.c
+++ b/src/libical/icaltimezone.c
@@ -40,12 +40,20 @@
#include "icalerror.h"
#include "icalparser.h"
#include "icaltimezone.h"
+#include "icaltimezoneimpl.h"
#include "icaltz-util.h"
#include <sys/stat.h>
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+static pthread_mutex_t builtin_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
#ifdef WIN32
+#ifndef _WIN32_WCE
#include <mbstring.h>
+#endif
#include <windows.h>
/* Undef the similar macro from pthread.h, it doesn't check if
* gmtime() returns NULL.
@@ -54,6 +62,16 @@
/* The gmtime() in Microsoft's C library is MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+
+// MSVC lacks the POSIX macro S_ISDIR, however it's a trivial one:
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+#endif
+#endif
+
+#if defined(_MSC_VER)
+#define snprintf _snprintf
+#define strcasecmp stricmp
#endif
/** This is the toplevel directory where the timezone data is installed in. */
@@ -76,54 +94,6 @@ const char *ical_tzid_prefix = "/freeassociation.sourceforge.net/";
somewhere around 2037. */
#define ICALTIMEZONE_MAX_YEAR 2035
-struct _icaltimezone {
- char *tzid;
- /**< The unique ID of this timezone,
- e.g. "/citadel.org/Olson_20010601_1/Africa/Banjul".
- This should only be used to identify a VTIMEZONE. It is not
- meant to be displayed to the user in any form. */
-
- char *location;
- /**< The location for the timezone, e.g. "Africa/Accra" for the
- Olson database. We look for this in the "LOCATION" or
- "X-LIC-LOCATION" properties of the VTIMEZONE component. It
- isn't a standard property yet. This will be NULL if no location
- is found in the VTIMEZONE. */
-
- char *tznames;
- /**< This will be set to a combination of the TZNAME properties
- from the last STANDARD and DAYLIGHT components in the
- VTIMEZONE, e.g. "EST/EDT". If they both use the same TZNAME,
- or only one type of component is found, then only one TZNAME
- will appear, e.g. "AZOT". If no TZNAME is found this will be
- NULL. */
-
- double latitude;
- double longitude;
- /**< The coordinates of the city, in degrees. */
-
- icalcomponent *component;
- /**< The toplevel VTIMEZONE component loaded from the .ics file for this
- timezone. If we need to regenerate the changes data we need this. */
-
- icaltimezone *builtin_timezone;
- /**< If this is not NULL it points to the builtin icaltimezone
- that the above TZID refers to. This icaltimezone should be used
- instead when accessing the timezone changes data, so that the
- expanded timezone changes data is shared between calendar
- components. */
-
- int end_year;
- /**< This is the last year for which we have expanded the data to.
- If we need to calculate a date past this we need to expand the
- timezone component data from scratch. */
-
- icalarray *changes;
- /**< A dynamically-allocated array of time zone changes, sorted by the
- time of the change in local time. So we can do fast binary-searches
- to convert from local time to UTC. */
-};
-
typedef struct _icaltimezonechange icaltimezonechange;
struct _icaltimezonechange {
@@ -234,10 +204,18 @@ icaltimezone_copy (icaltimezone *originalzone)
}
memcpy (zone, originalzone, sizeof (icaltimezone));
+ if (zone->tzid != NULL)
+ zone->tzid = strdup (zone->tzid);
if (zone->location != NULL)
zone->location = strdup (zone->location);
if (zone->tznames != NULL)
zone->tznames = strdup (zone->tznames);
+ if (zone->changes != NULL)
+ zone->changes = icalarray_copy(zone->changes);
+
+ /* Let the caller set the component because then they will
+ know to be careful not to free this reference twice. */
+ zone->component = NULL;
return zone;
}
@@ -298,7 +276,7 @@ icaltimezone_get_vtimezone_properties (icaltimezone *zone,
icalcomponent *component)
{
icalproperty *prop;
- const char *tzid, *tzname;
+ const char *tzid;
prop = icalcomponent_get_first_property (component, ICAL_TZID_PROPERTY);
if (!prop)
@@ -309,17 +287,16 @@ icaltimezone_get_vtimezone_properties (icaltimezone *zone,
if (!tzid)
return 0;
- prop = icalcomponent_get_first_property (component, ICAL_TZNAME_PROPERTY);
- if (prop) {
- tzname = icalproperty_get_tzname (prop);
- zone->tznames = strdup(tzname);
- } else
- zone->tznames = NULL;
-
+ if (zone->tzid) free(zone->tzid);
zone->tzid = strdup (tzid);
+
+ if (zone->component) icalcomponent_free(zone->component);
zone->component = component;
- if ( zone->location != 0 ) free ( zone->location );
+
+ if (zone->location) free(zone->location);
zone->location = icaltimezone_get_location_from_vtimezone (component);
+
+ if (zone->tznames) free(zone->tznames);
zone->tznames = icaltimezone_get_tznames_from_vtimezone (component);
return 1;
@@ -449,7 +426,7 @@ icaltimezone_get_tznames_from_vtimezone (icalcomponent *component)
/* If both standard and daylight TZNAMEs were found, if they are the same
we return just one, else we format them like "EST/EDT". */
if (standard_tzname && daylight_tzname) {
- unsigned int standard_len, daylight_len;
+ size_t standard_len, daylight_len;
char *tznames;
if (!strcmp (standard_tzname, daylight_tzname))
@@ -482,8 +459,7 @@ icaltimezone_ensure_coverage (icaltimezone *zone,
int changes_end_year;
- if (!zone->component)
- icaltimezone_load_builtin_timezone (zone);
+ icaltimezone_load_builtin_timezone (zone);
if (icaltimezone_minimum_expansion_year == -1) {
struct icaltimetype today = icaltime_today();
@@ -552,7 +528,7 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
icalrecur_iterator* rrule_iterator;
struct icaldatetimeperiodtype rdate;
int found_dtstart = 0, found_tzoffsetto = 0, found_tzoffsetfrom = 0;
- int has_recurrence = 0;
+ int has_rdate = 0, has_rrule = 0;
/* First we check if it is a STANDARD or DAYLIGHT component, and
just return if it isn't. */
@@ -584,8 +560,10 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
found_tzoffsetfrom = 1;
break;
case ICAL_RDATE_PROPERTY:
+ has_rdate = 1;
+ break;
case ICAL_RRULE_PROPERTY:
- has_recurrence = 1;
+ has_rrule = 1;
break;
default:
/* Just ignore any other properties. */
@@ -595,6 +573,14 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY);
}
+ /* Microsoft Outlook for Mac (and possibly other versions) will create
+ timezones without a tzoffsetfrom property if it's a timezone that
+ doesn't change for DST. */
+ if (found_tzoffsetto && !found_tzoffsetfrom) {
+ change.prev_utc_offset = change.utc_offset;
+ found_tzoffsetfrom = 1;
+ }
+
/* If we didn't find a DTSTART, TZOFFSETTO and TZOFFSETFROM we have to
ignore the component. FIXME: Add an error property? */
if (!found_dtstart || !found_tzoffsetto || !found_tzoffsetfrom)
@@ -606,9 +592,9 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
dtstart.hour, dtstart.minute, dtstart.second);
#endif
- /* If the STANDARD/DAYLIGHT component has no recurrence data, we just add
+ /* If the STANDARD/DAYLIGHT component has no recurrence rule, we add
a single change for the DTSTART. */
- if (!has_recurrence) {
+ if (!has_rrule) {
change.year = dtstart.year;
change.month = dtstart.month;
change.day = dtstart.day;
@@ -627,12 +613,11 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
/* Add the change to the array. */
icalarray_append (changes, &change);
- return;
}
/* The component has recurrence data, so we expand that now. */
prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY);
- while (prop) {
+ while (prop && (has_rdate || has_rrule)) {
#if 0
printf ("Expanding property...\n");
#endif
@@ -688,9 +673,32 @@ icaltimezone_expand_vtimezone (icalcomponent *comp,
rrule.until.is_utc = 0;
}
+ /* Add the dtstart to changes, otherwise some oddly-defined VTIMEZONE
+ components can cause the first year to get skipped. */
+ change.year = dtstart.year;
+ change.month = dtstart.month;
+ change.day = dtstart.day;
+ change.hour = dtstart.hour;
+ change.minute = dtstart.minute;
+ change.second = dtstart.second;
+
+#if 0
+ printf (" Appending RRULE element (Y/M/D): %i/%02i/%02i %i:%02i:%02i\n",
+ change.year, change.month, change.day,
+ change.hour, change.minute, change.second);
+#endif
+
+ icaltimezone_adjust_change (&change, 0, 0, 0,
+ -change.prev_utc_offset);
+
+ icalarray_append (changes, &change);
+
rrule_iterator = icalrecur_iterator_new (rrule, dtstart);
- for (;;) {
+ for (;rrule_iterator;) {
occ = icalrecur_iterator_next (rrule_iterator);
+ /* Skip dtstart since we just added it */
+ if (icaltime_compare(dtstart, occ) == 0)
+ continue;
if (occ.year > end_year || icaltime_is_null_time (occ))
break;
@@ -899,9 +907,12 @@ icaltimezone_get_utc_offset (icaltimezone *zone,
change_num += step;
/* If we go past the start of the changes array, then we have no data
- for this time so we return a UTC offset of 0. */
- if (change_num < 0)
- return 0;
+ for this time so we return the prev UTC offset. */
+ if (change_num < 0) {
+ if (is_daylight)
+ *is_daylight = ! tmp_change.is_daylight;
+ return tmp_change.prev_utc_offset;
+ }
if ((unsigned int)change_num >= zone->changes->num_elements)
break;
@@ -1169,8 +1180,7 @@ icaltimezone_get_tzid (icaltimezone *zone)
if (!zone)
return NULL;
- if (!zone->tzid)
- icaltimezone_load_builtin_timezone (zone);
+ icaltimezone_load_builtin_timezone (zone);
return zone->tzid;
}
@@ -1196,8 +1206,7 @@ icaltimezone_get_tznames (icaltimezone *zone)
if (!zone)
return NULL;
- if (!zone->component)
- icaltimezone_load_builtin_timezone (zone);
+ icaltimezone_load_builtin_timezone (zone);
return zone->tznames;
}
@@ -1239,8 +1248,7 @@ icaltimezone_get_component (icaltimezone *zone)
if (!zone)
return NULL;
- if (!zone->component)
- icaltimezone_load_builtin_timezone (zone);
+ icaltimezone_load_builtin_timezone (zone);
return zone->component;
}
@@ -1354,6 +1362,7 @@ void
icaltimezone_free_builtin_timezones(void)
{
icaltimezone_array_free(builtin_timezones);
+ builtin_timezones = 0;
}
@@ -1361,8 +1370,9 @@ icaltimezone_free_builtin_timezones(void)
icaltimezone*
icaltimezone_get_builtin_timezone (const char *location)
{
+ icalcomponent *comp;
icaltimezone *zone;
- int lower;
+ unsigned int lower;
const char *zone_location;
if (!location || !location[0])
@@ -1371,7 +1381,7 @@ icaltimezone_get_builtin_timezone (const char *location)
if (!builtin_timezones)
icaltimezone_init_builtin_timezones ();
- if (!strcmp (location, "UTC"))
+ if (strcmp (location, "UTC") == 0 || strcmp (location, "GMT") == 0)
return &utc_timezone;
#if 0
@@ -1402,6 +1412,20 @@ icaltimezone_get_builtin_timezone (const char *location)
return zone;
}
+ /* Check whether file exists, but is not mentioned in zone.tab.
+ It means it's a deprecated timezone, but still available. */
+ comp = icaltzutil_fetch_timezone (location);
+ if (comp) {
+ icaltimezone tz;
+ icaltimezone_init (&tz);
+ if (icaltimezone_set_component (&tz, comp)) {
+ icalarray_append (builtin_timezones, &tz);
+ return icalarray_element_at (builtin_timezones, builtin_timezones->num_elements - 1);
+ } else {
+ icalcomponent_free (comp);
+ }
+ }
+
return NULL;
}
@@ -1462,8 +1486,7 @@ icaltimezone_get_builtin_timezone_from_offset (int offset, const char *tzname)
for (i=0; i<count; i++) {
int z_offset;
zone = icalarray_element_at (builtin_timezones, i);
- if (!zone->component)
- icaltimezone_load_builtin_timezone (zone);
+ icaltimezone_load_builtin_timezone (zone);
z_offset = get_offset(zone);
@@ -1485,12 +1508,15 @@ icaltimezone_get_builtin_timezone_from_tzid (const char *tzid)
if (!tzid || !tzid[0])
return NULL;
+ if (strcmp (tzid, "UTC") == 0 || strcmp (tzid, "GMT") == 0) {
+ return icaltimezone_get_builtin_timezone(tzid);
+ }
+
/* Check that the TZID starts with our unique prefix. */
if (strncmp (tzid, ical_tzid_prefix, strlen(ical_tzid_prefix)))
return NULL;
/* Get the location, which is after the 3rd '/' character. */
- p = tzid;
for (p = tzid; *p; p++) {
if (*p == '/') {
num_slashes++;
@@ -1541,7 +1567,14 @@ icaltimezone_init_builtin_timezones (void)
/* Initialize the special UTC timezone. */
utc_timezone.tzid = (char *)"UTC";
- icaltimezone_parse_zone_tab ();
+#ifdef HAVE_PTHREAD
+ pthread_mutex_lock(&builtin_mutex);
+#endif
+ if (!builtin_timezones)
+ icaltimezone_parse_zone_tab ();
+#ifdef HAVE_PTHREAD
+ pthread_mutex_unlock(&builtin_mutex);
+#endif
}
static int
@@ -1570,7 +1603,7 @@ parse_coord (char *coord,
}
static int
fetch_lat_long_from_string (const char *str, int *latitude_degrees, int *latitude_minutes, int *latitude_seconds,
- int *longitude_degrees, int *longitude_minutes, int *longitude_seconds, char *location)
+ int *longitude_degrees, int *longitude_minutes, int *longitude_seconds, char *location)
{
size_t len;
char *sptr, *lat, *lon, *loc, *temp;
@@ -1614,10 +1647,17 @@ fetch_lat_long_from_string (const char *str, int *latitude_degrees, int *latitu
while (*lon != '+' && *lon != '-')
lon++;
- if (parse_coord (lat, lon - lat, latitude_degrees, latitude_minutes, latitude_seconds) == 1 ||
- parse_coord (lon, strlen (lon), longitude_degrees, longitude_minutes, longitude_seconds)
- == 1)
- return 1;
+ if (parse_coord (lat, (int)(lon - lat),
+ latitude_degrees,
+ latitude_minutes,
+ latitude_seconds) == 1 ||
+ parse_coord (lon, (int)strlen(lon),
+ longitude_degrees,
+ longitude_minutes,
+ longitude_seconds) == 1) {
+ free(lat);
+ return 1;
+ }
free (lat);
@@ -1638,7 +1678,7 @@ icaltimezone_parse_zone_tab (void)
FILE *fp;
char buf[1024]; /* Used to store each line of zones.tab as it is read. */
char location[1024]; /* Stores the city name when parsing buf. */
- unsigned int filename_len;
+ size_t filename_len;
int latitude_degrees = 0, latitude_minutes = 0, latitude_seconds = 0;
int longitude_degrees = 0, longitude_minutes = 0, longitude_seconds = 0;
icaltimezone zone;
@@ -1735,7 +1775,7 @@ icaltimezone_parse_zone_tab (void)
void
icaltimezone_release_zone_tab (void)
{
- int i;
+ unsigned int i;
icalarray *mybuiltin_timezones = builtin_timezones;
if (builtin_timezones == NULL)
@@ -1756,6 +1796,17 @@ icaltimezone_load_builtin_timezone (icaltimezone *zone)
if (!zone->location || !zone->location[0])
return;
+#ifdef HAVE_PTHREAD
+ pthread_mutex_lock(&builtin_mutex);
+ if (zone->component)
+ goto out;
+#else
+#ifdef USE_BUILTIN_TZDATA
+ if (zone->component)
+ return;
+#endif
+#endif
+
#ifdef USE_BUILTIN_TZDATA
{
char *filename;
@@ -1769,7 +1820,7 @@ icaltimezone_load_builtin_timezone (icaltimezone *zone)
filename = (char*) malloc (filename_len);
if (!filename) {
icalerror_set_errno(ICAL_NEWFAILED_ERROR);
- return;
+ goto out;
}
snprintf (filename, filename_len, "%s/%s.ics", get_zone_directory(),
@@ -1779,7 +1830,7 @@ icaltimezone_load_builtin_timezone (icaltimezone *zone)
free (filename);
if (!fp) {
icalerror_set_errno(ICAL_FILE_ERROR);
- return;
+ goto out;
}
@@ -1803,7 +1854,7 @@ icaltimezone_load_builtin_timezone (icaltimezone *zone)
if (!subcomp) {
icalerror_set_errno(ICAL_PARSE_ERROR);
- return;
+ goto out;
}
icaltimezone_get_vtimezone_properties (zone, subcomp);
@@ -1812,8 +1863,13 @@ icaltimezone_load_builtin_timezone (icaltimezone *zone)
icalcomponent_remove_component(comp,subcomp);
icalcomponent_free(comp);
}
-#endif
+#endif
+ out:
+#ifdef HAVE_PTHREAD
+ pthread_mutex_unlock(&builtin_mutex);
+#endif
+ return;
}
@@ -1867,7 +1923,6 @@ icaltimezone_dump_changes (icaltimezone *zone,
printf ("Num changes: %i\n", zone->changes->num_elements);
#endif
- change_num = 0;
for (change_num = 0; (unsigned int)change_num < zone->changes->num_elements; change_num++) {
zone_change = icalarray_element_at (zone->changes, change_num);
@@ -1929,10 +1984,18 @@ static const char* get_zone_directory(void)
return zone_files_directory == NULL ? ZONEINFO_DIRECTORY : zone_files_directory;
#else
wchar_t wbuffer[1000];
+#ifndef _WIN32_WCE
char buffer[1000], zoneinfodir[1000], dirname[1000];
+#else
+ wchar_t zoneinfodir[1000], dirname[1000];
+#endif
int used_default;
static char *cache = NULL;
+#ifndef _WIN32_WCE
char *dirslash, *zislash;
+#else
+ wchar_t *dirslash, *zislash;
+#endif
struct stat st;
if (zone_files_directory)
@@ -1945,6 +2008,8 @@ static const char* get_zone_directory(void)
if (!GetModuleFileNameW (NULL, wbuffer, sizeof (wbuffer) / sizeof (wbuffer[0])))
return ZONEINFO_DIRECTORY;
+/*wince supports only unicode*/
+#ifndef _WIN32_WCE
/* Convert to system codepage */
if (!WideCharToMultiByte (CP_ACP, 0, wbuffer, -1, buffer, sizeof (buffer),
NULL, &used_default) ||
@@ -1957,6 +2022,7 @@ static const char* get_zone_directory(void)
used_default)
return ZONEINFO_DIRECTORY;
}
+#endif
/* Look for the zoneinfo directory somewhere in the path where
* the app is installed. If the path to the app is
*
@@ -1992,10 +2058,38 @@ static const char* get_zone_directory(void)
*/
/* Strip away basename of app .exe first */
+#ifndef _WIN32_WCE
dirslash = _mbsrchr (buffer, '\\');
+#else
+ dirslash = wcsrchr (wbuffer, L'\\');
+#endif
if (dirslash)
+#ifndef _WIN32_WCE
*dirslash = '\0';
+#else
+ *dirslash = L'\0';
+#endif
+#ifdef _WIN32_WCE
+ while ((dirslash = wcsrchr (wbuffer, '\\'))) {
+ /* Strip one more directory from app .exe location */
+ *dirslash = L'\0';
+
+ MultiByteToWideChar(CP_ACP,0,ZONEINFO_DIRECTORY,-1,zoneinfodir,1000);
+
+ while ((zislash = wcschr (zoneinfodir, L'/'))) {
+ *zislash = L'.';
+ wcscpy (dirname, wbuffer);
+ wcscat (dirname, "/");
+ wcscat (dirname, zislash + 1);
+ if (stat (dirname, &st) == 0 &&
+ S_ISDIR (st.st_mode)) {
+ cache = wce_wctomb (dirname);
+ return cache;
+ }
+ }
+ }
+#else
while ((dirslash = _mbsrchr (buffer, '\\'))) {
/* Strip one more directory from app .exe location */
*dirslash = '\0';
@@ -2013,12 +2107,15 @@ static const char* get_zone_directory(void)
}
}
}
+#endif
return ZONEINFO_DIRECTORY;
#endif
}
void set_zone_directory(char *path)
{
+ if (zone_files_directory)
+ free_zone_directory();
zone_files_directory = malloc(strlen(path)+1);
if ( zone_files_directory != NULL )
{
@@ -2031,6 +2128,7 @@ void free_zone_directory(void)
if ( zone_files_directory != NULL )
{
free(zone_files_directory);
+ zone_files_directory = NULL;
}
}