From 6e11823ec46ddff04c318034325da9631cd8e654 Mon Sep 17 00:00:00 2001 From: Allen Winter Date: Mon, 15 Jun 2015 17:20:58 -0400 Subject: BuildTime+RunTime preference for exact vs. inter-operable timezones ISSUE: #95 --- CMakeLists.txt | 14 +++ Install.txt | 29 +++++ config.h.cmake | 3 + src/libical/ical_file.cmake | 1 + src/libical/icaltime.c | 2 +- src/libical/icaltimezone.h | 1 - src/libical/icaltz-util.c | 298 ++++++++++++++++++++++++++++++++++++-------- src/libical/icaltz-util.h | 6 + src/test/CMakeLists.txt | 1 - src/test/timezones.c | 7 ++ 10 files changed, 310 insertions(+), 52 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cecbe631..df9c4dd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,13 @@ # Set to build using a 32bit time_t (ignored unless building with MSVC on Windows) # Default=false (use the default size of time_t) # +# -DUSE_INTEROPERABLE_VTIMEZONES=[true|false] +# Set to use inter-operable rather than exact vtimezones. +# Default=false (build exact vtimezones) +# Notes: +# Change the behavior at runtime using the icaltzutil_set_exact_vtimezones_support() function. +# Query the behavior at runtime using the icaltzutil_get_exact_vtimezones_support() function. +# cmake_minimum_required(VERSION 2.8.9) #first line, to shutup a cygwin warning project(libical C CXX) @@ -196,6 +203,13 @@ if(WIN32 OR WINCE) endif() endif() +option(USE_INTEROPERABLE_VTIMEZONES "use interoperable rather than exact vtimezones." False) +if(USE_INTEROPERABLE_VTIMEZONES) + set(USE_INTEROPERABLE_VTIMEZONES 1) +else() + set(USE_INTEROPERABLE_VTIMEZONES 0) +endif() + include(ConfigureChecks.cmake) add_definitions(-DHAVE_CONFIG_H) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/Install.txt b/Install.txt index 90ba2eb2..c6df8d93 100644 --- a/Install.txt +++ b/Install.txt @@ -99,3 +99,32 @@ Then you can set the C and C++ compilers at CMake time, like so: This C library can be built with bindings for these other languages: * C++. By default the buildsystem will create and install the C++ bindings API. Turn this off by passing -DWITH_CXX_BINDINGS=False option to CMake. + +* There are Java, Perl, PHP and Python bindings but they are old and haven't + been tested in a very long time. Volunteers wanted. + +== Tweaking the Library Behavior == +Use these CMake options to adjust the library behavior as follows: + * ICAL_ERRORS_ARE_FATALL=[true|false] + Set to make icalerror_* calls abort instead of internally signaling an error. + Default=false + + * NO_WARN_DEPRECATED=[true|false] + Set if you DO NOT WANT to see deprecated messages. + Default=true + + * ICAL_ALLOW_EMPTY_PROPERTIES=[true|false] + Set to prevent empty properties from being replaced with X-LIC-ERROR properties. + Default=false + + * USE_BUILTIN_TZDATA=[true|false] + Set to build using our own (instead of the system's) timezone data. + Default=false (use the system timezone data on non-Windows systems) + ALWAYS true on Windows systems + + * USE_INTEROPERABLE_VTIMEZONES=[true|false] + Set to use inter-operable rather than exact VTIMEZONEs. + Default=false (build exact VTIMEZONEs) + Notes: + Change the behavior at runtime using the icaltzutil_set_exact_vtimezones_support() function. + Query the behavior at runtime using the icaltzutil_get_exact_vtimezones_support() function. diff --git a/config.h.cmake b/config.h.cmake index 80d32455..80fd57b9 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -187,6 +187,9 @@ /* whether we should bring our own TZ-Data */ #cmakedefine USE_BUILTIN_TZDATA +/* whether we should use operable vtimezones */ +#cmakedefine USE_INTEROPERABLE_VTIMEZONES + /* Define to empty if `const' does not conform to ANSI C. */ #cmakedefine const diff --git a/src/libical/ical_file.cmake b/src/libical/ical_file.cmake index e055645f..380bac49 100644 --- a/src/libical/ical_file.cmake +++ b/src/libical/ical_file.cmake @@ -19,6 +19,7 @@ set(COMBINEDHEADERSICAL ${TOPS}/src/libical/pvl.h ${TOPS}/src/libical/icalcomponent.h ${TOPS}/src/libical/icaltimezone.h + ${TOPS}/src/libical/icaltz-util.h ${TOPS}/src/libical/icalparser.h ${TOPS}/src/libical/icalmemory.h ${TOPS}/src/libical/icalerror.h diff --git a/src/libical/icaltime.c b/src/libical/icaltime.c index a1e9d4fa..1be155c8 100644 --- a/src/libical/icaltime.c +++ b/src/libical/icaltime.c @@ -72,7 +72,7 @@ static int icaltime_leap_days(int y1, int y2) { --y1; --y2; - return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400); + return (y2 / 4 - y1 / 4) - (y2 / 100 - y1 / 100) + (y2 / 400 - y1 / 400); } /* diff --git a/src/libical/icaltimezone.h b/src/libical/icaltimezone.h index 1574f0c2..58c2dd42 100644 --- a/src/libical/icaltimezone.h +++ b/src/libical/icaltimezone.h @@ -154,7 +154,6 @@ LIBICAL_ICAL_EXPORT char *icaltimezone_get_location_from_vtimezone(icalcomponent LIBICAL_ICAL_EXPORT char *icaltimezone_get_tznames_from_vtimezone(icalcomponent *component); - /* * @par Handling the default location the timezone files */ diff --git a/src/libical/icaltz-util.c b/src/libical/icaltz-util.c index e9df4cf3..39996b26 100644 --- a/src/libical/icaltz-util.c +++ b/src/libical/icaltz-util.c @@ -52,7 +52,7 @@ #if defined(_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_16(x) (((x) << 8) & 0xff00) | (((x) >> 8) & 0xff) #define bswap_32(x) \ (((x) << 24) & 0xff000000) | \ @@ -74,7 +74,7 @@ #endif #if defined(__APPLE__) || defined(__MINGW32__) -#define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff) +#define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8) & 0xff) #define bswap_32 __builtin_bswap32 #define bswap_64 __builtin_bswap64 #endif @@ -99,7 +99,7 @@ static char *search_paths[] = { }; #define EFREAD(buf,size,num,fs) \ -if (fread(buf, size, num, fs) < num && ferror (fs)) { \ +if (fread(buf, size, num, fs) < num && ferror(fs)) { \ icalerror_set_errno(ICAL_FILE_ERROR); \ goto error; \ } @@ -160,7 +160,7 @@ static char *zname_from_stridx(char *str, long idx) i++; } - size = (size_t) (i - idx); + size = (size_t)(i - idx); str += idx; ret = (char *)malloc(size + 1); ret = strncpy(ret, str, size); @@ -193,12 +193,110 @@ const char *icaltzutil_get_zone_directory(void) return zdir; } +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; +} + +static int calculate_pos(icaltimetype icaltime) +{ + static int r_pos[] = {1, 2, 3, -2, -1}; + int pos; + + pos = (icaltime.day - 1) / 7; + + /* Check if pos 3 is the last occurrence 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]; +} + +#if defined(USE_INTEROPERABLE_VTIMEZONES) +static int _s_use_exact_timezones = 0; +#else +static int _s_use_exact_timezones = 1; +#endif + +void icaltzutil_set_exact_vtimezones_support(int on) +{ + _s_use_exact_timezones = (on != 0); +} + +int icaltzutil_get_exact_vtimezones_support(void) +{ + return _s_use_exact_timezones; +} + +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) { tzinfo type_cnts; size_t i, num_trans, num_chars, num_leaps, num_isstd, num_isgmt; size_t num_types = 0; size_t size; + time_t trans; + int dstidx = -1, stdidx = -1, pos, sign, zidx, zp_idx; + icalcomponent *std_comp = NULL; const char *zonedir; FILE *f = NULL; @@ -213,9 +311,10 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) time_t start, end; int idx, prev_idx; - icalcomponent *tz_comp = NULL, *comp = NULL; + icalcomponent *tz_comp = NULL, *comp = NULL, *dst_comp; icalproperty *icalprop; - icaltimetype dtstart; + icaltimetype dtstart, icaltime; + struct icalrecurrencetype ical_recur; if (icaltimezone_get_builtin_tzdata()) { goto error; @@ -246,12 +345,12 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) EFREAD(&type_cnts, 24, 1, f); - num_isgmt = (size_t) decode(type_cnts.ttisgmtcnt); - num_leaps = (size_t) decode(type_cnts.leapcnt); - num_chars = (size_t) decode(type_cnts.charcnt); - num_trans = (size_t) decode(type_cnts.timecnt); - num_isstd = (size_t) decode(type_cnts.ttisstdcnt); - num_types = (size_t) decode(type_cnts.typecnt); + num_isgmt = (size_t)decode(type_cnts.ttisgmtcnt); + num_leaps = (size_t)decode(type_cnts.leapcnt); + num_chars = (size_t)decode(type_cnts.charcnt); + num_trans = (size_t)decode(type_cnts.timecnt); + num_isstd = (size_t)decode(type_cnts.ttisstdcnt); + num_types = (size_t)decode(type_cnts.typecnt); transitions = calloc(num_trans, sizeof(time_t)); if (transitions == NULL) { @@ -317,7 +416,7 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) char c[4]; EFREAD(c, 4, 1, f); - leaps[i].transition = (time_t) decode(c); + leaps[i].transition = (time_t)decode(c); EFREAD(c, 4, 1, f); leaps[i].change = decode(c); @@ -325,7 +424,6 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) for (i = 0; i < num_isstd; ++i) { int c = getc(f); - types[i].isstd = c != 0; } @@ -349,6 +447,14 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) types[i].zname = zname_from_stridx(znames, (long)types[i].abbr); } + if (!_s_use_exact_timezones) { + if (num_trans != 0) { + find_transidx(transitions, types, trans_idx, (long int)num_trans, &stdidx, &dstidx); + } else { + stdidx = 0; + } + } + tz_comp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); /* Add tzid property */ @@ -366,53 +472,147 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) icalproperty_set_x_name(icalprop, "X-LIC-LOCATION"); icalcomponent_add_property(tz_comp, icalprop); - prev_idx = 0; - if (num_trans == 0) { - prev_idx = idx = 0; - } else { - idx = trans_idx[0]; - } - start = 0; - for (i = 1; i < num_trans; i++, start = end) { - prev_idx = idx; - idx = trans_idx[i]; - end = transitions[i] + types[prev_idx].gmtoff; - /* don't bother starting until the epoch */ - if (0 > end) - continue; - - if (types[prev_idx].isdst) { + if (!_s_use_exact_timezones) { + 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 = (time_t)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 = (time_t)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); + } + } else { /*exact vtimezones*/ + prev_idx = 0; + if (num_trans == 0) { + prev_idx = idx = 0; + } else { + idx = trans_idx[0]; + } + start = 0; + for (i = 1; i < num_trans; i++, start = end) { + prev_idx = idx; + idx = trans_idx[i]; + end = transitions[i] + types[prev_idx].gmtoff; + /* don't bother starting until the epoch */ + if (0 > end) + continue; + + if (types[prev_idx].isdst) { + comp = icalcomponent_new(ICAL_XDAYLIGHT_COMPONENT); + } else { + comp = icalcomponent_new(ICAL_XSTANDARD_COMPONENT); + } + icalprop = icalproperty_new_tzname(types[prev_idx].zname); + icalcomponent_add_property(comp, icalprop); + dtstart = icaltime_from_timet(start, 0); + icalprop = icalproperty_new_dtstart(dtstart); + icalcomponent_add_property(comp, icalprop); + icalprop = icalproperty_new_tzoffsetfrom(types[idx].gmtoff); + icalcomponent_add_property(comp, icalprop); + icalprop = icalproperty_new_tzoffsetto(types[prev_idx].gmtoff); + icalcomponent_add_property(comp, icalprop); + icalcomponent_add_component(tz_comp, comp); + } + /* finally, add a last zone with no end date */ + if (types[idx].isdst) { comp = icalcomponent_new(ICAL_XDAYLIGHT_COMPONENT); } else { comp = icalcomponent_new(ICAL_XSTANDARD_COMPONENT); } - icalprop = icalproperty_new_tzname(types[prev_idx].zname); + icalprop = icalproperty_new_tzname(types[idx].zname); icalcomponent_add_property(comp, icalprop); dtstart = icaltime_from_timet(start, 0); icalprop = icalproperty_new_dtstart(dtstart); icalcomponent_add_property(comp, icalprop); - icalprop = icalproperty_new_tzoffsetfrom(types[idx].gmtoff); + icalprop = icalproperty_new_tzoffsetfrom(types[prev_idx].gmtoff); icalcomponent_add_property(comp, icalprop); - icalprop = icalproperty_new_tzoffsetto(types[prev_idx].gmtoff); + icalprop = icalproperty_new_tzoffsetto(types[idx].gmtoff); icalcomponent_add_property(comp, icalprop); icalcomponent_add_component(tz_comp, comp); } - /* finally, add a last zone with no end date */ - if (types[idx].isdst) { - comp = icalcomponent_new(ICAL_XDAYLIGHT_COMPONENT); - } else { - comp = icalcomponent_new(ICAL_XSTANDARD_COMPONENT); - } - icalprop = icalproperty_new_tzname(types[idx].zname); - icalcomponent_add_property(comp, icalprop); - dtstart = icaltime_from_timet(start, 0); - icalprop = icalproperty_new_dtstart(dtstart); - icalcomponent_add_property(comp, icalprop); - icalprop = icalproperty_new_tzoffsetfrom(types[prev_idx].gmtoff); - icalcomponent_add_property(comp, icalprop); - icalprop = icalproperty_new_tzoffsetto(types[idx].gmtoff); - icalcomponent_add_property(comp, icalprop); - icalcomponent_add_component(tz_comp, comp); error: if (f) diff --git a/src/libical/icaltz-util.h b/src/libical/icaltz-util.h index 2a9b3d89..e3981d5e 100644 --- a/src/libical/icaltz-util.h +++ b/src/libical/icaltz-util.h @@ -35,4 +35,10 @@ LIBICAL_ICAL_EXPORT const char *icaltzutil_get_zone_directory(void); LIBICAL_ICAL_EXPORT icalcomponent *icaltzutil_fetch_timezone(const char *location); +/* set @p on to 0 if inter-operable vtimezones are desired; else exact timezones are in-effect */ +LIBICAL_ICAL_EXPORT void icaltzutil_set_exact_vtimezones_support(int on); + +/* return 1 if exact vtimezones are in-effect; else inter-operable vtimezones are in-effect */ +LIBICAL_ICAL_EXPORT int icaltzutil_get_exact_vtimezones_support(void); + #endif diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 352c8c06..981695f4 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -156,7 +156,6 @@ if(NOT USE_BUILTIN_TZDATA) testme(timezones "${timezones_SRCS}") endif() - ########### next target ############### set(builtin_timezones_SRCS builtin_timezones.c) diff --git a/src/test/timezones.c b/src/test/timezones.c index 37d68630..b9362420 100644 --- a/src/test/timezones.c +++ b/src/test/timezones.c @@ -153,6 +153,13 @@ int main() percent_failed = total_failed * 100 / (total_failed + total_okay); printf(" *** Summary: %lu zones tested, %u days failed, %u okay => %u%% failed ***\n", (unsigned long)timezones->num_elements, total_failed, total_okay, percent_failed); + + if (!icaltzutil_get_exact_vtimezones_support()) { + if (!percent_failed) { + ret = 0; + printf(" *** Expect some small error rate with inter-operable vtimezones *** \n"); + } + } } return ret; -- cgit v1.2.1