/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors : * Chenthill Palanisamy * * Copyright 2007, Novell, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * Compile with * gcc -c -Wall -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY -DICALTZ_UTIL_MAIN -DHAVE_UNISTD_H -DHAVE_ENDIAN_H -DHAVE_BYTESWAP_H $(pkg-config --cflags --libs libical) -o icaltz-util icaltz-util.c * to get an utility which will print the VTIMEZONE definition of * a certain location, for example "Europe/Berlin". * * With -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY the * icaltzutil_get_zone_directory() from libical will be used. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #ifdef ICALTZ_UTIL_MAIN #include #endif #ifdef EVOLUTION_ICAL_COMPATIBILITY # include "eds_abi_wrapper.h" # else # include #endif #ifdef HAVE_STDINT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if defined(sun) && defined(__SVR4) #include #include #else # ifdef HAVE_BYTESWAP_H # include # endif # ifdef HAVE_ENDIAN_H # include # else # ifdef HAVE_SYS_ENDIAN_H # include # ifdef bswap32 # define bswap_32 bswap32 # else # define bswap_32 swap32 # endif # endif # endif #endif #ifdef _MSC_VER #if !defined(HAVE_BYTESWAP_H) && !defined(HAVE_SYS_ENDIAN_H) && !defined(HAVE_ENDIAN_H) #define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff) #define bswap_32(x) (((x) << 24) & 0xff000000) \ | (((x) << 8) & 0xff0000) \ | (((x) >> 8) & 0xff00) \ | (((x) >> 24) & 0xff ) #define bswap_64(x) ((((x) & 0xff00000000000000ull) >> 56) \ | (((x) & 0x00ff000000000000ull) >> 40) \ | (((x) & 0x0000ff0000000000ull) >> 24) \ | (((x) & 0x000000ff00000000ull) >> 8) \ | (((x) & 0x00000000ff000000ull) << 8) \ | (((x) & 0x0000000000ff0000ull) << 24) \ | (((x) & 0x000000000000ff00ull) << 40) \ | (((x) & 0x00000000000000ffull) << 56)) #endif #include #endif #if defined(__APPLE__) #define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff) #define bswap_32 __builtin_bswap32 #define bswap_64 __builtin_bswap64 #endif #ifndef PATH_MAX #define PATH_MAX 512 #endif #ifndef F_OK #define F_OK 0 #endif #ifndef R_OK #define R_OK 4 #endif #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include "icaltz-util.h" typedef struct { char ttisgmtcnt [4]; char ttisstdcnt[4]; char leapcnt[4]; char timecnt[4]; char typecnt[4]; char charcnt[4]; } tzinfo; static int r_pos [] = {1, 2, 3, -2, -1}; #ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY static char *search_paths [] = {"/usr/share/zoneinfo","/usr/lib/zoneinfo","/etc/zoneinfo","/usr/share/lib/zoneinfo"}; static char *zdir = NULL; #endif #define NUM_SEARCH_PATHS (sizeof (search_paths)/ sizeof (search_paths [0])) #define EFREAD(buf,size,num,fs) \ if (fread (buf, size, num, fs) == 0 && ferror (fs)) {\ icalerror_set_errno (ICAL_FILE_ERROR); \ goto error; \ } \ typedef struct { long int gmtoff; unsigned char isdst; unsigned int abbr; unsigned char isstd; unsigned char isgmt; char *zname; } ttinfo; typedef struct { time_t transition; long int change; } leap; #ifndef EVOLUTION_ICAL_COMPATIBILITY extern const char *ical_tzid_prefix; #endif static int decode (const void *ptr) { #if defined(sun) && defined(__SVR4) if (sizeof (int) == 4) #ifdef _BIG_ENDIAN return *(const int *) ptr; #else return BSWAP_32 (*(const int *) ptr); #endif #else if ((BYTE_ORDER == BIG_ENDIAN) && sizeof (int) == 4) return *(const int *) ptr; else if (BYTE_ORDER == LITTLE_ENDIAN && sizeof (int) == 4) return bswap_32 (*(const int *) ptr); #endif else { const unsigned char *p = ptr; int result = *p & (1 << (CHAR_BIT - 1)) ? ~0 : 0; result = (result << 8) | *p++; result = (result << 8) | *p++; result = (result << 8) | *p++; result = (result << 8) | *p++; return result; } } static char * zname_from_stridx (char *str, long int idx) { int i = 0; char *ret; size_t size; i = idx; while (str [i] != '\0') i++; size = i - idx; str += idx; ret = (char *) malloc (size + 1); ret = strncpy (ret, str, size); ret [size] = '\0'; return ret; } static void find_transidx (time_t *transitions, ttinfo *types, int *trans_idx, long int num_trans, int *stdidx, int *dstidx) { time_t now, year_start; int i, found = 0; struct icaltimetype itime; now = time (NULL); itime = icaltime_from_timet (now, 0); itime.month = itime.day = 1; itime.hour = itime.minute = itime.second = 0; year_start = icaltime_as_timet(itime); /* Set this by default */ *stdidx = (num_trans - 1); for (i = (num_trans - 1); i >= 0; --i) if (year_start < transitions [i]) { int idx; found = 1; idx = trans_idx [i]; (types [idx].isdst) ? (*dstidx = i) : (*stdidx = i); } /* If the transition found is the last among the list, prepare to use the last two transtions. * Using this will most likely throw the DTSTART of the resulting component off by 1 or 2 days * but it would set right by the adjustment made. * NOTE: We need to use the last two transitions only because there is no data for the future * transitions. */ if (found && (*dstidx == -1)) { *dstidx = ((*stdidx) - 1); } return; } #ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY static void set_zonedir (void) { char file_path[PATH_MAX]; const char *fname = ZONES_TAB_SYSTEM_FILENAME; int i; for (i = 0;i < NUM_SEARCH_PATHS; i++) { sprintf (file_path, "%s/%s", search_paths [i], fname); if (!access (file_path, F_OK|R_OK)) { zdir = search_paths [i]; break; } } } const char * icaltzutil_get_zone_directory (void) { if (!zdir) set_zonedir (); return zdir; } #endif /* Calculate the relative position of the week in a month from a date */ static int calculate_pos (icaltimetype icaltime) { int pos; pos = (icaltime.day -1) / 7; /* Check if pos 3 is the last occurence of the week day in the month */ if (pos == 3 && ((icaltime.day + 7) > icaltime_days_in_month (icaltime.month, icaltime.year))) pos = 4; return r_pos [pos]; } static void adjust_dtstart_day_to_rrule (icalcomponent *comp, struct icalrecurrencetype rule) { time_t now, year_start; struct icaltimetype start, comp_start, iter_start, itime; icalrecur_iterator *iter; now = time (NULL); itime = icaltime_from_timet (now, 0); itime.month = itime.day = 1; itime.hour = itime.minute = itime.second = 0; year_start = icaltime_as_timet(itime); comp_start = icalcomponent_get_dtstart (comp); start = icaltime_from_timet (year_start, 0); iter = icalrecur_iterator_new (rule, start); iter_start = icalrecur_iterator_next (iter); icalrecur_iterator_free (iter); if (iter_start.day != comp_start.day) { comp_start.day = iter_start.day; icalcomponent_set_dtstart (comp, comp_start); } } icalcomponent* icaltzutil_fetch_timezone (const char *location) { int ret = 0; FILE *f; tzinfo type_cnts; unsigned int i, num_trans, num_types = 0, num_chars, num_leaps, num_isstd, num_isgmt; time_t *transitions = NULL; time_t trans; int *trans_idx = NULL, dstidx = -1, stdidx = -1, pos, sign, zidx, zp_idx; ttinfo *types = NULL; char *znames = NULL, *full_path, *tzid, *r_trans, *temp = NULL; leap *leaps = NULL; icalcomponent *tz_comp = NULL, *dst_comp = NULL, *std_comp = NULL; icalproperty *icalprop; icaltimetype dtstart, icaltime; struct icalrecurrencetype ical_recur; const char *basedir; /* if (!location) return NULL; */ basedir = icaltzutil_get_zone_directory(); if (!basedir) { icalerror_set_errno (ICAL_FILE_ERROR); return NULL; } full_path = (char *) malloc (strlen (basedir) + strlen (location) + 2); sprintf (full_path,"%s/%s",basedir, location); if ((f = fopen (full_path, "rb")) == 0) { icalerror_set_errno (ICAL_FILE_ERROR); free (full_path); return NULL; } if ((ret = fseek (f, 20, SEEK_SET)) != 0) { icalerror_set_errno (ICAL_FILE_ERROR); goto error; } EFREAD(&type_cnts, 24, 1, f); num_isgmt = decode (type_cnts.ttisgmtcnt); num_leaps = decode (type_cnts.leapcnt); num_chars = decode (type_cnts.charcnt); num_trans = decode (type_cnts.timecnt); num_isstd = decode (type_cnts.ttisstdcnt); num_types = decode (type_cnts.typecnt); transitions = calloc (num_trans, sizeof (time_t)); temp = calloc (num_trans, 4); r_trans = temp; EFREAD(r_trans, 4, num_trans, f); if (num_trans) { trans_idx = calloc (num_trans, sizeof (int)); for (i = 0; i < num_trans; i++) { trans_idx [i] = fgetc (f); transitions [i] = decode (r_trans); r_trans += 4; } } free (temp); temp = NULL; types = calloc (num_types, sizeof (ttinfo)); for (i = 0; i < num_types; i++) { unsigned char a [4]; int c; EFREAD(a, 4, 1, f); c = fgetc (f); types [i].isdst = c; if((c = fgetc (f)) < 0) { break; } types [i].abbr = c; types [i].gmtoff = decode (a); } znames = (char *) malloc (num_chars); EFREAD(znames, num_chars, 1, f); /* We got all the information which we need */ leaps = calloc (num_leaps, sizeof (leap)); for (i = 0; i < num_leaps; i++) { char c [4]; EFREAD (c, 4, 1, f); leaps [i].transition = decode (c); EFREAD (c, 4, 1, f); leaps [i].change = decode (c); } for (i = 0; i < num_isstd; ++i) { int c = getc (f); types [i].isstd = c != 0; } while (i < num_types) types [i++].isstd = 0; for (i = 0; i < num_isgmt; ++i) { int c = getc (f); types [i].isgmt = c != 0; } while (i < num_types) types [i++].isgmt = 0; /* Read all the contents now */ for (i = 0; i < num_types; i++) types [i].zname = zname_from_stridx (znames, types [i].abbr); if (num_trans != 0) find_transidx (transitions, types, trans_idx, num_trans, &stdidx, &dstidx); else stdidx = 0; tz_comp = icalcomponent_new (ICAL_VTIMEZONE_COMPONENT); /* Add tzid property */ tzid = (char *) malloc (strlen (ical_tzid_prefix) + strlen (location) + 8); sprintf (tzid, "%sTzfile/%s", ical_tzid_prefix, location); icalprop = icalproperty_new_tzid (tzid); icalcomponent_add_property (tz_comp, icalprop); free (tzid); icalprop = icalproperty_new_x (location); icalproperty_set_x_name (icalprop, "X-LIC-LOCATION"); icalcomponent_add_property (tz_comp, icalprop); if (stdidx != -1) { if (num_trans != 0) zidx = trans_idx [stdidx]; else zidx = 0; std_comp = icalcomponent_new (ICAL_XSTANDARD_COMPONENT); icalprop = icalproperty_new_tzname (types [zidx].zname); icalcomponent_add_property (std_comp, icalprop); if (dstidx != -1) zp_idx = trans_idx [stdidx-1]; else zp_idx = zidx; /* DTSTART localtime uses TZOFFSETFROM UTC offset */ if (num_trans != 0) trans = transitions [stdidx] + types [zp_idx].gmtoff; else trans = types [zp_idx].gmtoff; icaltime = icaltime_from_timet (trans, 0); dtstart = icaltime; dtstart.year = 1970; dtstart.minute = dtstart.second = 0; icalprop = icalproperty_new_dtstart (dtstart); icalcomponent_add_property (std_comp, icalprop); /* If DST changes are present use RRULE */ if (dstidx != -1) { icalrecurrencetype_clear (&ical_recur); ical_recur.freq = ICAL_YEARLY_RECURRENCE; ical_recur.by_month [0] = icaltime.month; pos = calculate_pos (icaltime); pos < 0 ? (sign = -1): (sign = 1); ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime)); icalprop = icalproperty_new_rrule (ical_recur); icalcomponent_add_property (std_comp, icalprop); adjust_dtstart_day_to_rrule (std_comp, ical_recur); } icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff); icalcomponent_add_property (std_comp, icalprop); icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff); icalcomponent_add_property (std_comp, icalprop); icalcomponent_add_component (tz_comp, std_comp); } else icalerror_set_errno (ICAL_MALFORMEDDATA_ERROR); if (dstidx != -1) { zidx = trans_idx [dstidx]; zp_idx = trans_idx [dstidx-1]; dst_comp = icalcomponent_new (ICAL_XDAYLIGHT_COMPONENT); icalprop = icalproperty_new_tzname (types [zidx].zname); icalcomponent_add_property (dst_comp, icalprop); /* DTSTART localtime uses TZOFFSETFROM UTC offset */ if (num_trans != 0) trans = transitions [dstidx] + types [zp_idx].gmtoff; else trans = types [zp_idx].gmtoff; icaltime = icaltime_from_timet (trans, 0); dtstart = icaltime; dtstart.year = 1970; dtstart.minute = dtstart.second = 0; icalprop = icalproperty_new_dtstart (dtstart); icalcomponent_add_property (dst_comp, icalprop); icalrecurrencetype_clear (&ical_recur); ical_recur.freq = ICAL_YEARLY_RECURRENCE; ical_recur.by_month [0] = icaltime.month; pos = calculate_pos (icaltime); pos < 0 ? (sign = -1): (sign = 1); ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime)); icalprop = icalproperty_new_rrule (ical_recur); icalcomponent_add_property (dst_comp, icalprop); adjust_dtstart_day_to_rrule (dst_comp, ical_recur); icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff); icalcomponent_add_property (dst_comp, icalprop); icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff); icalcomponent_add_property (dst_comp, icalprop); icalcomponent_add_component (tz_comp, dst_comp); } error: if (f) fclose (f); if (transitions) free (transitions); if (trans_idx) free (trans_idx); if (types) { for (i = 0; i < num_types; i++) if (types [i].zname) free (types [i].zname); free (types); } if (znames) free (znames); free (full_path); if (leaps) free (leaps); if (temp) free (temp); return tz_comp; } /* * What follows is copied and slightly simplified (not thread-safe!) * code from icaltimezone.c. * * This is necessary because otherwise, when libsynthesis.so calls * icaltimezone_get_component(), libical.1.so uses its own builtin * icaltzutil_fetch_timezone(). Apparently it exports that without * looking it up via the dynamic linker itself. Therefore we have to * redirect the original icaltimezone_get_component() call. */ static void icaltimezone_load_builtin_timezone (icaltimezone *zone) { icalcomponent *subcomp; const char *location; /* If the location isn't set, it isn't a builtin timezone. */ location = icaltimezone_get_location(zone); if (!location || !location[0]) return; subcomp = icaltzutil_fetch_timezone (location); if (!subcomp) { icalerror_set_errno(ICAL_PARSE_ERROR); return; } icaltimezone_set_component(zone, subcomp); } #undef icaltimezone_get_component icalcomponent *icaltimezone_get_component(icaltimezone *zone) { icalcomponent *comp; /* If this is a floating time, without a timezone, return NULL. */ if (!zone) return NULL; /* * Without this check, icaltimezone_set_component() in * icaltimezone_load_builtin_timezone() will discard the * already loaded component of builtin timezones and replace * it with the new one, so there is no leak. It's just * inefficient. * * However, this method also gets called for non-internal * timezones which were created from a VTIMEZONE and in * that case not using the existing component is wrong. * * Hack: duplicate the internal _icaltimezone struct (from * icaltimezoneimpl.h). */ struct _my_icaltimezone { char *tzid; char *location; char *tznames; double latitude; double longitude; icalcomponent *component; }; comp = ((struct _my_icaltimezone *)zone)->component; if (!comp) { icaltimezone_load_builtin_timezone (zone); comp = ((struct _my_icaltimezone *)zone)->component; } return comp; } #ifdef ICALTZ_UTIL_MAIN int main (int argc, char *argv []) { icalcomponent *comp = icaltzutil_fetch_timezone (argv [1]); const char *str = comp ? icalcomponent_as_ical_string(comp) : "no such zone"; puts(str); return 0; } #endif