summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen Murchison <murch@fastmail.com>2021-03-14 16:12:53 -0400
committerAllen Winter <allen.winter@kdab.com>2021-04-11 14:27:37 -0400
commitaea5834e07e45edc7d72173a4fbaaec98b170ee5 (patch)
treeb414566570bfd703294dc43b53deeb0800792163
parent8e8a0c4e4ec4c3c4cf5e472b508ede7b1786f859 (diff)
downloadlibical-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.c286
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);