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>
This commit is contained in:
parent
32783496e6
commit
d8c1c48e78
@ -1219,7 +1219,7 @@ int get_item_min(time_t);
|
|||||||
struct tm date2tm(struct date, unsigned, unsigned);
|
struct tm date2tm(struct date, unsigned, unsigned);
|
||||||
time_t date2sec(struct date, unsigned, unsigned);
|
time_t date2sec(struct date, unsigned, unsigned);
|
||||||
struct date sec2date(time_t);
|
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(struct date *, struct date *);
|
||||||
int date_cmp_day(time_t, time_t);
|
int date_cmp_day(time_t, time_t);
|
||||||
char *date_sec2date_str(time_t, const char *);
|
char *date_sec2date_str(time_t, const char *);
|
||||||
|
118
src/ical.c
118
src/ical.c
@ -578,6 +578,32 @@ ical_chk_header(FILE * fd, char *buf, char *lstore, unsigned *lineno,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
* Return event type from a DTSTART/DTEND/EXDATE property.
|
||||||
*/
|
*/
|
||||||
@ -601,23 +627,23 @@ static ical_vevent_e ical_get_type(char *c_line)
|
|||||||
* where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'.
|
* where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'.
|
||||||
* The time and 'T' separator are optional (in the case of an day-long event).
|
* 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
|
* The type argument is either APPOINTMENT or EVENT, and the time format must
|
||||||
* agree.
|
* match (either DATE-TIME or DATE). The time zone identifier is ignored in an
|
||||||
*
|
* EVENT or in an APPOINTMENT with UTC time.
|
||||||
* The timezone is not yet handled by calcurse.
|
|
||||||
*/
|
*/
|
||||||
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;
|
const int INVALID = 0, DATE = 3, DATETIME = 6, DATETIMEZ = 7;
|
||||||
struct date date;
|
struct date date;
|
||||||
unsigned hour, min, sec;
|
unsigned hour, min, sec;
|
||||||
char c;
|
char c, UTC[] = "";
|
||||||
int format;
|
int format;
|
||||||
|
|
||||||
EXIT_IF(type == UNDEFINED, "event type not set");
|
EXIT_IF(type == UNDEFINED, "event type not set");
|
||||||
|
|
||||||
format = sscanf(datestr, "%04u%02u%02uT%02u%02u%02u%c",
|
format = sscanf(datestr, "%04u%02u%02uT%02u%02u%02u%c",
|
||||||
&date.yyyy, &date.mm, &date.dd, &hour, &min, &sec, &c);
|
&date.yyyy, &date.mm, &date.dd, &hour, &min, &sec, &c);
|
||||||
|
|
||||||
if (format == DATE && strlen(datestr) > 8)
|
if (format == DATE && strlen(datestr) > 8)
|
||||||
format = INVALID;
|
format = INVALID;
|
||||||
if (format == DATETIMEZ && c != 'Z')
|
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)
|
if (format == DATE && type == EVENT)
|
||||||
return date2sec(date, 0, 0);
|
return date2sec(date, 0, 0);
|
||||||
else if (format == DATETIME && type == APPOINTMENT)
|
else if (format == DATETIME && type == APPOINTMENT)
|
||||||
return date2sec(date, hour, min);
|
return tzdate2sec(date, hour, min, tzid);
|
||||||
else if (format == DATETIMEZ && type == APPOINTMENT)
|
else if (format == DATETIMEZ && type == APPOINTMENT)
|
||||||
return utcdate2sec(date, hour, min);
|
return tzdate2sec(date, hour, min, UTC);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -869,7 +895,7 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
|
|||||||
* specified, counts as the first occurrence.
|
* specified, counts as the first occurrence.
|
||||||
*/
|
*/
|
||||||
if ((p = strstr(rrulestr, "UNTIL")) != NULL) {
|
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)) {
|
if (!(rpt->until)) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("invalid until format."));
|
_("invalid until format."));
|
||||||
@ -915,47 +941,47 @@ static int
|
|||||||
ical_read_exdate(llist_t * exc, FILE * log, char *exstr, unsigned *noskipped,
|
ical_read_exdate(llist_t * exc, FILE * log, char *exstr, unsigned *noskipped,
|
||||||
const int itemline, ical_vevent_e type)
|
const int itemline, ical_vevent_e type)
|
||||||
{
|
{
|
||||||
char *p, *q;
|
char *p, *q, *tzid = NULL;
|
||||||
time_t t;
|
time_t t;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
/* See DTSTART. */
|
|
||||||
if (type == UNDEFINED) {
|
if (type == UNDEFINED) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("need DTSTART to determine event type."));
|
_("need DTSTART to determine event type."));
|
||||||
(*noskipped)++;
|
goto cleanup;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (type != ical_get_type(exstr)) {
|
if (type != ical_get_type(exstr)) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("invalid exception date value type."));
|
_("invalid exception date value type."));
|
||||||
(*noskipped)++;
|
goto cleanup;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
p = ical_get_value(exstr);
|
p = ical_get_value(exstr);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("malformed exceptions line."));
|
_("malformed exceptions line."));
|
||||||
(*noskipped)++;
|
goto cleanup;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
tzid = ical_get_tzid(exstr);
|
||||||
/* Count the exceptions and replace commas by zeroes */
|
/* Count the exceptions and replace commas by zeroes */
|
||||||
for (q = p, n = 1; (q = strchr(q, ',')); *q = '\0', q++, n++)
|
for (q = p, n = 1; (q = strchr(q, ',')); *q = '\0', q++, n++)
|
||||||
;
|
;
|
||||||
while (n) {
|
while (n) {
|
||||||
if (!(t = ical_datetime2time_t(p, type))) {
|
if (!(t = ical_datetime2time_t(p, tzid, type))) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("invalid exception."));
|
_("invalid exception."));
|
||||||
(*noskipped)++;
|
goto cleanup;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
ical_add_exc(exc, t);
|
ical_add_exc(exc, t);
|
||||||
p = strchr(p, '\0') + 1;
|
p = strchr(p, '\0') + 1;
|
||||||
n--;
|
n--;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
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);
|
const int ITEMLINE = *lineno - !feof(fdi);
|
||||||
ical_vevent_e vevent_type;
|
ical_vevent_e vevent_type;
|
||||||
ical_property_e property;
|
ical_property_e property;
|
||||||
char *p, *note = NULL, *comment;
|
char *p, *note = NULL, *tmp, *tzid;
|
||||||
const char *SEPARATOR = "-- \n";
|
const char *SEPARATOR = "-- \n";
|
||||||
struct string s;
|
struct string s;
|
||||||
struct {
|
struct {
|
||||||
llist_t exc;
|
llist_t exc;
|
||||||
ical_rpt_t *rpt;
|
ical_rpt_t *rpt;
|
||||||
char *mesg, *desc, *loc, *comm, *stat, *note;
|
char *mesg, *desc, *loc, *comm, *stat, *imp, *note;
|
||||||
long start, end, dur;
|
long start, end, dur;
|
||||||
int has_alarm;
|
int has_alarm;
|
||||||
} vevent;
|
} vevent;
|
||||||
@ -1129,6 +1155,11 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
vevent.stat);
|
vevent.stat);
|
||||||
mem_free(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));
|
vevent.note = generate_note(string_buf(&s));
|
||||||
mem_free(s.buf);
|
mem_free(s.buf);
|
||||||
}
|
}
|
||||||
@ -1167,6 +1198,18 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
* event.
|
* event.
|
||||||
*/
|
*/
|
||||||
vevent_type = ical_get_type(buf);
|
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);
|
p = ical_get_value(buf);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
@ -1174,7 +1217,9 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
goto skip;
|
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) {
|
if (!vevent.start) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
_("invalid or malformed event "
|
_("invalid or malformed event "
|
||||||
@ -1194,6 +1239,7 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
_("invalid end time value type."));
|
_("invalid end time value type."));
|
||||||
goto skip;
|
goto skip;
|
||||||
}
|
}
|
||||||
|
tzid = ical_get_tzid(buf);
|
||||||
p = ical_get_value(buf);
|
p = ical_get_value(buf);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
@ -1201,7 +1247,9 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
goto skip;
|
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) {
|
if (!vevent.end) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
_("malformed event end time."));
|
_("malformed event end time."));
|
||||||
@ -1257,7 +1305,8 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
ICAL_VEVENT, ITEMLINE, log);
|
ICAL_VEVENT, ITEMLINE, log);
|
||||||
if (!note)
|
if (!note)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
separator = (property != DESCRIPTION);
|
if (!separator)
|
||||||
|
separator = (property != DESCRIPTION);
|
||||||
has_note = 1;
|
has_note = 1;
|
||||||
}
|
}
|
||||||
switch (property) {
|
switch (property) {
|
||||||
@ -1280,10 +1329,10 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
case COMMENT:
|
case COMMENT:
|
||||||
/* There may be more than one. */
|
/* There may be more than one. */
|
||||||
if (vevent.comm) {
|
if (vevent.comm) {
|
||||||
asprintf(&comment, "%sComment: %s",
|
asprintf(&tmp, "%sComment: %s",
|
||||||
vevent.comm, note);
|
vevent.comm, note);
|
||||||
mem_free(vevent.comm);
|
mem_free(vevent.comm);
|
||||||
vevent.comm = comment;
|
vevent.comm = tmp;
|
||||||
} else
|
} else
|
||||||
vevent.comm = note;
|
vevent.comm = note;
|
||||||
break;
|
break;
|
||||||
@ -1322,6 +1371,8 @@ cleanup:
|
|||||||
mem_free(vevent.comm);
|
mem_free(vevent.comm);
|
||||||
if (vevent.stat)
|
if (vevent.stat)
|
||||||
mem_free(vevent.stat);
|
mem_free(vevent.stat);
|
||||||
|
if (vevent.imp)
|
||||||
|
mem_free(vevent.imp);
|
||||||
if (vevent.mesg)
|
if (vevent.mesg)
|
||||||
mem_free(vevent.mesg);
|
mem_free(vevent.mesg);
|
||||||
if (vevent.rpt)
|
if (vevent.rpt)
|
||||||
@ -1335,7 +1386,7 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped,
|
|||||||
{
|
{
|
||||||
const int ITEMLINE = *lineno - !feof(fdi);
|
const int ITEMLINE = *lineno - !feof(fdi);
|
||||||
ical_property_e property;
|
ical_property_e property;
|
||||||
char *note = NULL, *comment;
|
char *note = NULL, *tmp;
|
||||||
const char *SEPARATOR = "-- \n";
|
const char *SEPARATOR = "-- \n";
|
||||||
struct string s;
|
struct string s;
|
||||||
struct {
|
struct {
|
||||||
@ -1430,7 +1481,8 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped,
|
|||||||
ICAL_VTODO, ITEMLINE, log);
|
ICAL_VTODO, ITEMLINE, log);
|
||||||
if (!note)
|
if (!note)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
separator = (property != DESCRIPTION);
|
if (!separator)
|
||||||
|
separator = (property != DESCRIPTION);
|
||||||
has_note = 1;
|
has_note = 1;
|
||||||
}
|
}
|
||||||
switch (property) {
|
switch (property) {
|
||||||
@ -1453,10 +1505,10 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped,
|
|||||||
case COMMENT:
|
case COMMENT:
|
||||||
/* There may be more than one. */
|
/* There may be more than one. */
|
||||||
if (vtodo.comm) {
|
if (vtodo.comm) {
|
||||||
asprintf(&comment, "%sComment: %s",
|
asprintf(&tmp, "%sComment: %s",
|
||||||
vtodo.comm, note);
|
vtodo.comm, note);
|
||||||
mem_free(vtodo.comm);
|
mem_free(vtodo.comm);
|
||||||
vtodo.comm = comment;
|
vtodo.comm = tmp;
|
||||||
} else
|
} else
|
||||||
vtodo.comm = note;
|
vtodo.comm = note;
|
||||||
break;
|
break;
|
||||||
|
23
src/utils.c
23
src/utils.c
@ -423,22 +423,25 @@ struct date sec2date(time_t t)
|
|||||||
return d;
|
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;
|
time_t t;
|
||||||
|
|
||||||
tz = getenv("TZ");
|
if (!tznew)
|
||||||
if (tz)
|
return date2sec(day, hour, min);
|
||||||
tz = mem_strdup(tz);
|
|
||||||
setenv("TZ", "", 1);
|
|
||||||
tzset();
|
|
||||||
|
|
||||||
|
tzold = getenv("TZ");
|
||||||
|
if (tzold)
|
||||||
|
tzold = mem_strdup(tzold);
|
||||||
|
|
||||||
|
setenv("TZ", tznew, 1);
|
||||||
|
tzset();
|
||||||
t = date2sec(day, hour, min);
|
t = date2sec(day, hour, min);
|
||||||
|
|
||||||
if (tz) {
|
if (tzold) {
|
||||||
setenv("TZ", tz, 1);
|
setenv("TZ", tzold, 1);
|
||||||
mem_free(tz);
|
mem_free(tzold);
|
||||||
} else {
|
} else {
|
||||||
unsetenv("TZ");
|
unsetenv("TZ");
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,9 @@ SUMMARY:UTC
|
|||||||
DTSTART:20150223T110000Z
|
DTSTART:20150223T110000Z
|
||||||
DURATION:PT1H
|
DURATION:PT1H
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
SUMMARY:CET
|
||||||
|
DTSTART;TZID=CET:20150223T110000
|
||||||
|
DURATION:PT1H
|
||||||
|
END:VEVENT
|
||||||
END:VCALENDAR
|
END:VCALENDAR
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
BEGIN:VCALENDAR
|
BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
BEGIN:VEVENT
|
BEGIN:VEVENT
|
||||||
DTSTART;TZID="(UTC+01:00) Amsterdam, Berlin, Bern, Rom, Stockholm, Wien":19800101T000100
|
DTSTART:19800101T000100
|
||||||
DURATION:P1DT9H17M0S
|
DURATION;TESTPARAM="Quoted string with colon(:), semicolon(;) and comma(,)":P1DT9H17M0S
|
||||||
SUMMARY:Calibrator's
|
SUMMARY:Calibrator's
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
BEGIN:VTODO
|
BEGIN:VTODO
|
||||||
|
@ -8,16 +8,20 @@ if [ "$1" = 'actual' ]; then
|
|||||||
TZ="America/New_York" "$CALCURSE" -D "$PWD/.calcurse" \
|
TZ="America/New_York" "$CALCURSE" -D "$PWD/.calcurse" \
|
||||||
-i "$DATA_DIR/ical-007.ical"
|
-i "$DATA_DIR/ical-007.ical"
|
||||||
"$CALCURSE" -D "$PWD/.calcurse" -s02/23/2015
|
"$CALCURSE" -D "$PWD/.calcurse" -s02/23/2015
|
||||||
|
cat "$PWD/.calcurse/notes/"*
|
||||||
rm -rf .calcurse || exit 1
|
rm -rf .calcurse || exit 1
|
||||||
elif [ "$1" = 'expected' ]; then
|
elif [ "$1" = 'expected' ]; then
|
||||||
cat <<EOD
|
cat <<EOD
|
||||||
Import process report: 0013 lines read
|
Import process report: 0018 lines read
|
||||||
2 apps / 0 events / 0 todos / 0 skipped
|
3 apps / 0 events / 0 todos / 0 skipped
|
||||||
02/23/15:
|
02/23/15:
|
||||||
|
- 05:00 -> 06:00
|
||||||
|
CET
|
||||||
- 06:00 -> 07:00
|
- 06:00 -> 07:00
|
||||||
UTC
|
UTC
|
||||||
- 11:00 -> 12:00
|
- 11:00 -> 12:00
|
||||||
Local time
|
Local time
|
||||||
|
Import: TZID=CET
|
||||||
EOD
|
EOD
|
||||||
else
|
else
|
||||||
./run-test "$0"
|
./run-test "$0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user