diff options
author | Ken Murchison <murch@fastmail.com> | 2021-03-14 16:12:53 -0400 |
---|---|---|
committer | Allen Winter <allen.winter@kdab.com> | 2021-04-11 14:27:37 -0400 |
commit | aea5834e07e45edc7d72173a4fbaaec98b170ee5 (patch) | |
tree | b414566570bfd703294dc43b53deeb0800792163 | |
parent | 8e8a0c4e4ec4c3c4cf5e472b508ede7b1786f859 (diff) | |
download | libical-git-aea5834e07e45edc7d72173a4fbaaec98b170ee5.tar.gz |
icaltz-util.c: read, parse, and use TZ string as last standard and daylight rules
-rw-r--r-- | src/libical/icaltz-util.c | 286 |
1 files changed, 251 insertions, 35 deletions
diff --git a/src/libical/icaltz-util.c b/src/libical/icaltz-util.c index c7311a3a..8ec85bf8 100644 --- a/src/libical/icaltz-util.c +++ b/src/libical/icaltz-util.c @@ -232,6 +232,125 @@ static int calculate_pos(icaltimetype icaltime) return r_pos[pos]; } +static char *parse_posix_zone(char *p, ttinfo *type) +{ + size_t size; + + /* Zone name */ + if (*p == '<') { + /* Alphanumeric, '-', or '+' */ + size = strcspn(++p, ">"); + } + else { + /* Alpha ONLY */ + size = strcspn(p, "-+0123456789,\n"); + } + + type->zname = (char *) malloc(size + 1); + strncpy(type->zname, p, size); + type->zname[size] = '\0'; + p += size; + + if (*p == '>') p++; + + if (*p == ',') return p; + + /* Zone offset: hh[:mm[:ss]] */ + type->gmtoff = strtol(p, &p, 10) * -3600; /* sign of offset is reversed */ + if (*p == ':') type->gmtoff += strtol(++p, &p, 10) * 60; + if (*p == ':') type->gmtoff += strtol(++p, &p, 10); + + return p; +} + +static char *parse_posix_rule(char *p, + struct icalrecurrencetype *recur, icaltimetype *t) +{ + int month = 0, monthday = 0, week = 0, day; + + /* Parse date */ + if (*p == 'J') { + /* The Julian day n (1 <= n <= 365). + Leap days shall not be counted. That is, in all years, + including leap years, February 28 is day 59 and March 1 is day 60. + It is impossible to refer explicitly to the occasional February 29. + */ + day = strtol(++p, &p, 10); + } + else if (*p == 'M') { + /* The d'th day (0 <= d <= 6) + of week n of month m of the year (1 <= n <= 5, 1 <= m <= 12, + where week 5 means "the last d day in month m" + which may occur in either the fourth or the fifth week). + Week 1 is the first week in which the d'th day occurs. + Day zero is Sunday. + */ + month = strtol(++p, &p, 10); + week = strtol(++p, &p, 10); + day = strtol(++p, &p, 10); + if (week == 5) week = -1; + } + else { + /* The zero-based Julian day (0 <= n <= 365). + Leap days shall be counted, and it is possible to refer to February 29. + */ + /* XXX Currently not used by any zone */ + } + + /* Parse time */ + *t = icaltime_null_time(); + t->hour = 2; /* default is 02:00 */ + + if (*p == '/') { + t->hour = strtol(++p, &p, 10); + if (*p == ':') t->minute = strtol(++p, &p, 10); + if (*p == ':') t->second = strtol(++p, &p, 10); + } + + /* Do adjustments for extended TZ strings */ + if (t->hour < 0 || t->hour > 23) { + int days_adjust = t->hour / 24; + + t->hour %= 24; + day += days_adjust; + + if (t->hour < 0) { + t->hour += 24; + day += 6; + } + if (month) { + if (week == -1) { + monthday = icaltime_days_in_month(month, -1) + days_adjust - 7; + } + else { + monthday = 1 + (week - 1) * 7 + days_adjust; + } + week = 0; + } + } + + /* Create rule */ + icalrecurrencetype_clear(recur); + recur->freq = ICAL_YEARLY_RECURRENCE; + + if (month) { + recur->by_day[0] = icalrecurrencetype_encode_day((day % 7) + 1, week); + recur->by_month[0] = month; + + if (monthday) { + unsigned i; + for (i = 0; i < 7; i++) { + recur->by_month_day[i] = monthday++; + } + } + } + else { + recur->by_year_day[0] = day; + } + + return p; +} + icalcomponent *icaltzutil_fetch_timezone(const char *location) { tzinfo header; @@ -253,12 +372,16 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) leap *leaps = NULL; char *tzid = NULL; + char footer[100], *tzstr = NULL; + int idx, prev_idx; icalcomponent *tz_comp = NULL, *comp = NULL; icalproperty *icalprop; icaltimetype icaltime; struct icalrecurrencetype standard_recur; struct icalrecurrencetype daylight_recur; + struct icalrecurrencetype final_standard_recur; + struct icalrecurrencetype final_daylight_recur; icaltimetype prev_standard_time = icaltime_null_time(); icaltimetype prev_daylight_time = icaltime_null_time(); icaltimetype prev_prev_standard_time; @@ -348,7 +471,7 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) /* read data block */ if (num_trans > 0) { - transitions = calloc(num_trans, sizeof(time_t)); + transitions = calloc(num_trans+1, sizeof(time_t)); // +1 for TZ string if (transitions == NULL) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); goto error; @@ -364,7 +487,7 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) } EFREAD(r_trans, (size_t)trans_size, num_trans, f); temp = r_trans; - trans_idx = calloc(num_trans, sizeof(int)); + trans_idx = calloc(num_trans+1, sizeof(int)); // +1 for TZ string if (trans_idx == NULL) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); goto error; @@ -380,7 +503,7 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) } r_trans = temp; - types = calloc(num_types, sizeof(ttinfo)); + types = calloc(num_types+2, sizeof(ttinfo)); // +2 for TZ string if (types == NULL) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); goto error; @@ -446,17 +569,85 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) types[i++].isgmt = 0; } - if (trans_size == 8) { - /* XXX Do we need/want to read and use the footer? */ - } - - /* Read all the contents now */ - for (i = 0; i < num_types; i++) { /* coverity[tainted_data] */ types[i].zname = zname_from_stridx(znames, types[i].abbr); } + /* Read the footer */ + if (trans_size == 8 && + (footer[0] = fgetc(f)) == '\n' && + fgets(footer+1, sizeof(footer)-1, f) && + footer[strlen(footer)-1] == '\n') { + tzstr = footer+1; + } + if (tzstr) { + /* Parse the TZ string: + stdoffset[dst[offset][,start[/time],end[/time]]] + */ + ttinfo *std_type = &types[num_types++]; + ttinfo *dst_type = &types[num_types++]; + char *p = tzstr; + + /* Parse standard zone */ + p = parse_posix_zone(p, std_type); + if (*p == '\n') { + /* No DST, so ignore the TZ string */ + tzstr = NULL; + } + else { + /* Parse DST zone */ + dst_type->isdst = 1; + dst_type->gmtoff = std_type->gmtoff + 3600; /* default is +1hr */ + p = parse_posix_zone(p, dst_type); + + if (*p != ',') { + /* No rule, so ignore the TZ string */ + tzstr = NULL; + } + else { + struct icaltimetype std_trans, dst_trans, last_trans; + icalrecur_iterator *iter; + + /* Parse std->dst rule */ + p = parse_posix_rule(++p /* skip ',' */, + &final_daylight_recur, &dst_trans); + + /* Parse dst->std rule */ + p = parse_posix_rule(++p /* skip ',' */, + &final_standard_recur, &std_trans); + + last_trans = icaltime_from_timet_with_zone(transitions[num_trans-1], 0, NULL); + if (types[trans_idx[num_trans-1]].isdst) { + /* Add next dst->std transition */ + std_trans.year = last_trans.year; + std_trans.month = last_trans.month; + std_trans.day = last_trans.day; + iter = icalrecur_iterator_new(final_standard_recur, std_trans); + std_trans = icalrecur_iterator_next(iter); + icaltime_adjust(&std_trans, 0, 0, 0, -dst_type->gmtoff); + transitions[num_trans] = icaltime_as_timet(std_trans); + trans_idx[num_trans++] = num_types-2; + icalrecur_iterator_free(iter); + } + else { + /* Add next std->dst transition */ + dst_trans.year = last_trans.year; + dst_trans.month = last_trans.month; + dst_trans.day = last_trans.day; + iter = icalrecur_iterator_new(final_daylight_recur, dst_trans); + dst_trans = icalrecur_iterator_next(iter); + icaltime_adjust(&dst_trans, 0, 0, 0, -std_type->gmtoff); + transitions[num_trans] = icaltime_as_timet(dst_trans); + trans_idx[num_trans++] = num_types-1; + icalrecur_iterator_free(iter); + } + } + } + } + + /* Build the VTIMEZONE now */ + tz_comp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); /* Add tzid property */ @@ -478,6 +669,7 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) idx = trans_idx[0]; for (i = 1; i < num_trans; i++) { + int last_trans = 0; int by_day; int is_new_comp = 0; time_t start; @@ -488,9 +680,14 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) start = transitions[i] + types[prev_idx].gmtoff; icaltime = icaltime_from_timet_with_zone(start, 0, NULL); - pos = calculate_pos(icaltime); - pos < 0 ? (sign = -1): (sign = 1); - by_day = sign * ((abs(pos) * 8) + icaltime_day_of_week(icaltime)); + if (tzstr && (i >= num_trans - 2)) { + last_trans = 1; + } + else { + pos = calculate_pos(icaltime); + pos < 0 ? (sign = -1): (sign = 1); + by_day = sign * ((abs(pos) * 8) + icaltime_day_of_week(icaltime)); + } // Figure out if the rule has changed since the previous year // If it has, update the recurrence rule of the current component and create a new component @@ -500,7 +697,8 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) // Check if the pattern for daylight has changed // If it has, create a new component and update UNTIL // of previous component's RRULE - if (daylight_recur.by_month[0] != icaltime.month || + if (last_trans || + daylight_recur.by_month[0] != icaltime.month || daylight_recur.by_day[0] != by_day || types[prev_idx].gmtoff != prev_daylight_gmtoff || prev_daylight_time.hour != icaltime.hour || @@ -536,22 +734,30 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) } comp = cur_daylight_comp; - recur = &daylight_recur; - - if (icaltime_is_null_time(prev_daylight_time)) { - prev_prev_daylight_time = icaltime; - } else { - prev_prev_daylight_time = prev_daylight_time; + if (last_trans) { + /* This rule will take us into the future */ + recur = &final_daylight_recur; + prev_daylight_time = icaltime_from_day_of_year(1, 9999); } + else { + recur = &daylight_recur; + + if (icaltime_is_null_time(prev_daylight_time)) { + prev_prev_daylight_time = icaltime; + } else { + prev_prev_daylight_time = prev_daylight_time; + } - prev_daylight_time = icaltime; - prev_daylight_gmtoff = types[prev_idx].gmtoff; + prev_daylight_time = icaltime; + prev_daylight_gmtoff = types[prev_idx].gmtoff; + } } else { if (cur_standard_comp) { // Check if the pattern for standard has changed // If it has, create a new component and update UNTIL // of the previous component's RRULE - if (standard_recur.by_month[0] != icaltime.month || + if (last_trans || + standard_recur.by_month[0] != icaltime.month || standard_recur.by_day[0] != by_day || types[prev_idx].gmtoff != prev_standard_gmtoff || prev_standard_time.hour != icaltime.hour || @@ -585,16 +791,23 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) } comp = cur_standard_comp; - recur = &standard_recur; - - if (icaltime_is_null_time(prev_standard_time)) { - prev_prev_standard_time = icaltime; - } else { - prev_prev_standard_time = prev_standard_time; + if (last_trans) { + /* This rule will take us into the future */ + recur = &final_standard_recur; + prev_standard_time = icaltime_from_day_of_year(1, 9999); } + else { + recur = &standard_recur; + + if (icaltime_is_null_time(prev_standard_time)) { + prev_prev_standard_time = icaltime; + } else { + prev_prev_standard_time = prev_standard_time; + } - prev_standard_time = icaltime; - prev_standard_gmtoff = types[prev_idx].gmtoff; + prev_standard_time = icaltime; + prev_standard_gmtoff = types[prev_idx].gmtoff; + } } if (is_new_comp) { @@ -607,11 +820,14 @@ icalcomponent *icaltzutil_fetch_timezone(const char *location) icalprop = icalproperty_new_tzoffsetto(types[idx].gmtoff); icalcomponent_add_property(comp, icalprop); - // Determine the recurrence rule for the current set of changes - icalrecurrencetype_clear(recur); - recur->freq = ICAL_YEARLY_RECURRENCE; - recur->by_month[0] = icaltime.month; - recur->by_day[0] = by_day; + if (!last_trans) { + // Determine the recurrence rule for the current set of changes + icalrecurrencetype_clear(recur); + recur->freq = ICAL_YEARLY_RECURRENCE; + recur->by_month[0] = icaltime.month; + recur->by_day[0] = by_day; + } + icalprop = icalproperty_new_rrule(*recur); icalcomponent_add_property(comp, icalprop); |