diff options
author | Lars Henriksen <LarsHenriksen@get2net.dk> | 2020-05-25 20:52:22 +0200 |
---|---|---|
committer | Lukas Fleischer <lfleischer@calcurse.org> | 2020-06-13 10:56:15 -0400 |
commit | d8c1c48e78bd6cdfbee922c6bda4022c7d7cb9dd (patch) | |
tree | c4a27777909eb68a4e293e1579090262c04361e2 | |
parent | 32783496e6a002f6408ad21d1436e61442173536 (diff) | |
download | calcurse-d8c1c48e78bd6cdfbee922c6bda4022c7d7cb9dd.tar.gz calcurse-d8c1c48e78bd6cdfbee922c6bda4022c7d7cb9dd.zip |
Support import of time zones (RFC 5545)
The property parameter time zone identifier (TZID) is recognized in DTSTART,
DTEND and EXDATE and the DATE-TIME value converted to a local time. The time
zone identifier is logged in the note file.
Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
-rw-r--r-- | src/calcurse.h | 2 | ||||
-rw-r--r-- | src/ical.c | 118 | ||||
-rw-r--r-- | src/utils.c | 23 | ||||
-rw-r--r-- | test/data/ical-007.ical | 5 | ||||
-rw-r--r-- | test/data/ical-008.ical | 4 | ||||
-rwxr-xr-x | test/ical-007.sh | 8 |
6 files changed, 112 insertions, 48 deletions
diff --git a/src/calcurse.h b/src/calcurse.h index 25c63a0..1f28c37 100644 --- a/src/calcurse.h +++ b/src/calcurse.h @@ -1219,7 +1219,7 @@ int get_item_min(time_t); struct tm date2tm(struct date, unsigned, unsigned); time_t date2sec(struct date, unsigned, unsigned); struct date sec2date(time_t); -time_t utcdate2sec(struct date, unsigned, unsigned); +time_t tzdate2sec(struct date, unsigned, unsigned, char *); int date_cmp(struct date *, struct date *); int date_cmp_day(time_t, time_t); char *date_sec2date_str(time_t, const char *); @@ -579,6 +579,32 @@ ical_chk_header(FILE * fd, char *buf, char *lstore, unsigned *lineno, } /* + * Return the TZID property parameter value from a DTSTART/DTEND/EXDATE property + * in an allocated string. The value may be any text string not containing the + * characters '"', ';', ':' and ',' (RFC 5545, sections 3.2.19 and 3.1). + */ +static char *ical_get_tzid(char *p) +{ + const char param[] = ";TZID="; + char *q; + int s; + + if (!(p = strstr(p, param))) + return NULL; + p += sizeof(param) - 1; + if (*p == '"') + return NULL; + + q = strpbrk(p, ":;"); + s = q - p + 1; + q = mem_malloc(s); + strncpy(q, p, s); + q[s - 1] = '\0'; + + return q; +} + +/* * Return event type from a DTSTART/DTEND/EXDATE property. */ static ical_vevent_e ical_get_type(char *c_line) @@ -601,23 +627,23 @@ static ical_vevent_e ical_get_type(char *c_line) * where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'. * The time and 'T' separator are optional (in the case of an day-long event). * - * The type argument is either APPOINTMENT or EVENT and the time format must - * agree. - * - * The timezone is not yet handled by calcurse. + * The type argument is either APPOINTMENT or EVENT, and the time format must + * match (either DATE-TIME or DATE). The time zone identifier is ignored in an + * EVENT or in an APPOINTMENT with UTC time. */ -static time_t ical_datetime2time_t(char *datestr, ical_vevent_e type) +static time_t ical_datetime2time_t(char *datestr, char *tzid, ical_vevent_e type) { const int INVALID = 0, DATE = 3, DATETIME = 6, DATETIMEZ = 7; struct date date; unsigned hour, min, sec; - char c; + char c, UTC[] = ""; int format; EXIT_IF(type == UNDEFINED, "event type not set"); format = sscanf(datestr, "%04u%02u%02uT%02u%02u%02u%c", &date.yyyy, &date.mm, &date.dd, &hour, &min, &sec, &c); + if (format == DATE && strlen(datestr) > 8) format = INVALID; if (format == DATETIMEZ && c != 'Z') @@ -626,9 +652,9 @@ static time_t ical_datetime2time_t(char *datestr, ical_vevent_e type) if (format == DATE && type == EVENT) return date2sec(date, 0, 0); else if (format == DATETIME && type == APPOINTMENT) - return date2sec(date, hour, min); + return tzdate2sec(date, hour, min, tzid); else if (format == DATETIMEZ && type == APPOINTMENT) - return utcdate2sec(date, hour, min); + return tzdate2sec(date, hour, min, UTC); return 0; } @@ -869,7 +895,7 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, * specified, counts as the first occurrence. */ if ((p = strstr(rrulestr, "UNTIL")) != NULL) { - rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, type); + rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, NULL, type); if (!(rpt->until)) { ical_log(log, ICAL_VEVENT, itemline, _("invalid until format.")); @@ -915,47 +941,47 @@ static int ical_read_exdate(llist_t * exc, FILE * log, char *exstr, unsigned *noskipped, const int itemline, ical_vevent_e type) { - char *p, *q; + char *p, *q, *tzid = NULL; time_t t; int n; - /* See DTSTART. */ if (type == UNDEFINED) { ical_log(log, ICAL_VEVENT, itemline, _("need DTSTART to determine event type.")); - (*noskipped)++; - return 0; + goto cleanup; } if (type != ical_get_type(exstr)) { ical_log(log, ICAL_VEVENT, itemline, _("invalid exception date value type.")); - (*noskipped)++; - return 0; + goto cleanup; } p = ical_get_value(exstr); if (!p) { ical_log(log, ICAL_VEVENT, itemline, _("malformed exceptions line.")); - (*noskipped)++; - return 0; + goto cleanup; } - + tzid = ical_get_tzid(exstr); /* Count the exceptions and replace commas by zeroes */ for (q = p, n = 1; (q = strchr(q, ',')); *q = '\0', q++, n++) ; while (n) { - if (!(t = ical_datetime2time_t(p, type))) { + if (!(t = ical_datetime2time_t(p, tzid, type))) { ical_log(log, ICAL_VEVENT, itemline, _("invalid exception.")); - (*noskipped)++; - return 0; + goto cleanup; } ical_add_exc(exc, t); p = strchr(p, '\0') + 1; n--; } - return 1; + +cleanup: + (*noskipped)++; + if (tzid) + mem_free(tzid); + return 0; } /* @@ -1050,13 +1076,13 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, const int ITEMLINE = *lineno - !feof(fdi); ical_vevent_e vevent_type; ical_property_e property; - char *p, *note = NULL, *comment; + char *p, *note = NULL, *tmp, *tzid; const char *SEPARATOR = "-- \n"; struct string s; struct { llist_t exc; ical_rpt_t *rpt; - char *mesg, *desc, *loc, *comm, *stat, *note; + char *mesg, *desc, *loc, *comm, *stat, *imp, *note; long start, end, dur; int has_alarm; } vevent; @@ -1129,6 +1155,11 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, vevent.stat); mem_free(vevent.stat); } + if (vevent.imp) { + string_catf(&s, ("Import: %s\n"), + vevent.imp); + mem_free(vevent.imp); + } vevent.note = generate_note(string_buf(&s)); mem_free(s.buf); } @@ -1167,6 +1198,18 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, * event. */ vevent_type = ical_get_type(buf); + if ((tzid = ical_get_tzid(buf)) && + vevent_type == APPOINTMENT) { + /* Add note on TZID. */ + if (vevent.imp) { + asprintf(&tmp, "%s, TZID=%s", + vevent.imp, tzid); + mem_free(vevent.imp); + vevent.imp = tmp; + } else + asprintf(&vevent.imp, "TZID=%s", tzid); + has_note = separator = 1; + } p = ical_get_value(buf); if (!p) { ical_log(log, ICAL_VEVENT, ITEMLINE, @@ -1174,7 +1217,9 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, goto skip; } - vevent.start = ical_datetime2time_t(p, vevent_type); + vevent.start = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) + mem_free(tzid); if (!vevent.start) { ical_log(log, ICAL_VEVENT, ITEMLINE, _("invalid or malformed event " @@ -1194,6 +1239,7 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, _("invalid end time value type.")); goto skip; } + tzid = ical_get_tzid(buf); p = ical_get_value(buf); if (!p) { ical_log(log, ICAL_VEVENT, ITEMLINE, @@ -1201,7 +1247,9 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, goto skip; } - vevent.end = ical_datetime2time_t(p, vevent_type); + vevent.end = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) + mem_free(tzid); if (!vevent.end) { ical_log(log, ICAL_VEVENT, ITEMLINE, _("malformed event end time.")); @@ -1257,7 +1305,8 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, ICAL_VEVENT, ITEMLINE, log); if (!note) goto cleanup; - separator = (property != DESCRIPTION); + if (!separator) + separator = (property != DESCRIPTION); has_note = 1; } switch (property) { @@ -1280,10 +1329,10 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, case COMMENT: /* There may be more than one. */ if (vevent.comm) { - asprintf(&comment, "%sComment: %s", + asprintf(&tmp, "%sComment: %s", vevent.comm, note); mem_free(vevent.comm); - vevent.comm = comment; + vevent.comm = tmp; } else vevent.comm = note; break; @@ -1322,6 +1371,8 @@ cleanup: mem_free(vevent.comm); if (vevent.stat) mem_free(vevent.stat); + if (vevent.imp) + mem_free(vevent.imp); if (vevent.mesg) mem_free(vevent.mesg); if (vevent.rpt) @@ -1335,7 +1386,7 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, { const int ITEMLINE = *lineno - !feof(fdi); ical_property_e property; - char *note = NULL, *comment; + char *note = NULL, *tmp; const char *SEPARATOR = "-- \n"; struct string s; struct { @@ -1430,7 +1481,8 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, ICAL_VTODO, ITEMLINE, log); if (!note) goto cleanup; - separator = (property != DESCRIPTION); + if (!separator) + separator = (property != DESCRIPTION); has_note = 1; } switch (property) { @@ -1453,10 +1505,10 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, case COMMENT: /* There may be more than one. */ if (vtodo.comm) { - asprintf(&comment, "%sComment: %s", + asprintf(&tmp, "%sComment: %s", vtodo.comm, note); mem_free(vtodo.comm); - vtodo.comm = comment; + vtodo.comm = tmp; } else vtodo.comm = note; break; diff --git a/src/utils.c b/src/utils.c index 6f849ea..23b9d89 100644 --- a/src/utils.c +++ b/src/utils.c @@ -423,22 +423,25 @@ struct date sec2date(time_t t) return d; } -time_t utcdate2sec(struct date day, unsigned hour, unsigned min) +time_t tzdate2sec(struct date day, unsigned hour, unsigned min, char *tznew) { - char *tz; + char *tzold; time_t t; - tz = getenv("TZ"); - if (tz) - tz = mem_strdup(tz); - setenv("TZ", "", 1); - tzset(); + if (!tznew) + return date2sec(day, hour, min); + + tzold = getenv("TZ"); + if (tzold) + tzold = mem_strdup(tzold); + setenv("TZ", tznew, 1); + tzset(); t = date2sec(day, hour, min); - if (tz) { - setenv("TZ", tz, 1); - mem_free(tz); + if (tzold) { + setenv("TZ", tzold, 1); + mem_free(tzold); } else { unsetenv("TZ"); } diff --git a/test/data/ical-007.ical b/test/data/ical-007.ical index e46c3fb..a7dfdd5 100644 --- a/test/data/ical-007.ical +++ b/test/data/ical-007.ical @@ -10,4 +10,9 @@ SUMMARY:UTC DTSTART:20150223T110000Z DURATION:PT1H END:VEVENT +BEGIN:VEVENT +SUMMARY:CET +DTSTART;TZID=CET:20150223T110000 +DURATION:PT1H +END:VEVENT END:VCALENDAR diff --git a/test/data/ical-008.ical b/test/data/ical-008.ical index 51625d5..7789734 100644 --- a/test/data/ical-008.ical +++ b/test/data/ical-008.ical @@ -1,8 +1,8 @@ BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT -DTSTART;TZID="(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien":19800101T000100 -DURATION:P1DT9H17M0S +DTSTART:19800101T000100 +DURATION;TESTPARAM="Quoted string with colon(:), semicolon(;) and comma(,)":P1DT9H17M0S SUMMARY:Calibrator's END:VEVENT BEGIN:VTODO diff --git a/test/ical-007.sh b/test/ical-007.sh index 0a5ad2a..da5002c 100755 --- a/test/ical-007.sh +++ b/test/ical-007.sh @@ -8,16 +8,20 @@ if [ "$1" = 'actual' ]; then TZ="America/New_York" "$CALCURSE" -D "$PWD/.calcurse" \ -i "$DATA_DIR/ical-007.ical" "$CALCURSE" -D "$PWD/.calcurse" -s02/23/2015 + cat "$PWD/.calcurse/notes/"* rm -rf .calcurse || exit 1 elif [ "$1" = 'expected' ]; then cat <<EOD -Import process report: 0013 lines read -2 apps / 0 events / 0 todos / 0 skipped +Import process report: 0018 lines read +3 apps / 0 events / 0 todos / 0 skipped 02/23/15: + - 05:00 -> 06:00 + CET - 06:00 -> 07:00 UTC - 11:00 -> 12:00 Local time +Import: TZID=CET EOD else ./run-test "$0" |