Commit 428cfc85 authored by Robert Rollins's avatar Robert Rollins

Issue #2207173: RRULEs with non-UTC UNTIL values are now supported.

According to the iCal spec, the UNTIL value of an RRULE must be specified in
UTC. But certain iCal feed creation tools don't respect this rule, and
instead specify the UNTIL value in local time. Since all the other code that
Date iCal utilizes to parse RRULEs expects the UNTIL value to be in UTC,
this go badly when it's not.

In order to get around this issue, I've added an option to the iCal Parser
settings, which allows users to tell Date iCal that they expect the UNTIL
values in their feeds' RRULEs to not be in UTC. With that option enabled,
Date iCal will treat the UNTIL date as being in the same timezone as the
event's DTSTART.
parent 6e96f984
...@@ -42,17 +42,33 @@ function _date_ical_get_repeat_dates($field_name, $repeat_data, $item, $source) ...@@ -42,17 +42,33 @@ function _date_ical_get_repeat_dates($field_name, $repeat_data, $item, $source)
$start_datetime = date_format($start, DATE_FORMAT_DATETIME); $start_datetime = date_format($start, DATE_FORMAT_DATETIME);
if (!empty($rrule_values['UNTIL']['datetime'])) { if (!empty($rrule_values['UNTIL']['datetime'])) {
// TODO: The spec says that UNTIL must be specified in UTC, but I think // The spec says that UNTIL must be in UTC, but not all feed creators
// this may not be followd by all feed creators. It may be a good idea to // follow that rule. If the user specified that he wanted to overcome this
// support optionally assuming the date has the same TZID as DTSTART. // problem, use $timezone for the $final_repeat, and then convert the UNTIL
$end = date_ical_date($rrule_values['UNTIL'], $timezone); // in the unparsed RRULE to UTC.
$end_datetime = date_format($end, DATE_FORMAT_DATETIME); if (!empty($source->importer->config['parser']['config']['until_not_utc'])) {
// Change the parsed UNTIL from UTC to $timezone, so that the
// date_ical_date() won't convert it.
$rrule_values['UNTIL']['tz'] = $timezone;
// Convert the unparsed UNTIL to UTC, since the Date code will use it.
// It's currently got a Z on it, but only because iCalcreator blindly
// added it. Since the user set "until_not_utc", we know it's not UTC.
$matches = array();
preg_match('/^(.*?)UNTIL=(.*?)Z(.*?)$/', $repeat_data['RRULE'], $matches);
$until_date = new DateObject($matches[2], $timezone);
$until_date->setTimezone(new DateTimeZone('UTC'));
$matches[2] = $until_date->format('Ymd\THis');
$repeat_data['RRULE'] = "{$matches[1]}UNTIL={$matches[2]}Z{$matches[3]}";
}
$final_repeat = date_ical_date($rrule_values['UNTIL'], $timezone);
$final_repeat_datetime = date_format($final_repeat, DATE_FORMAT_DATETIME);
} }
elseif (!empty($rrule_values['COUNT'])) { elseif (!empty($rrule_values['COUNT'])) {
$end_datetime = NULL; $final_repeat_datetime = NULL;
} }
else { else {
// No UNTIL and no COUNT? // No UNTIL and no COUNT? This is an illegal RRULE.
return array(); return array();
} }
...@@ -69,10 +85,11 @@ function _date_ical_get_repeat_dates($field_name, $repeat_data, $item, $source) ...@@ -69,10 +85,11 @@ function _date_ical_get_repeat_dates($field_name, $repeat_data, $item, $source)
$date = date_ical_date($rdate, $timezone); $date = date_ical_date($rdate, $timezone);
$additions[] = date_format($date, 'Y-m-d'); $additions[] = date_format($date, 'Y-m-d');
} }
// TODO: EXRULE.
// TODO: EXRULEs.
$date_repeat_compatible_rrule = "{$repeat_data['RRULE']}\n{$repeat_data['RDATE']}\n{$repeat_data['EXDATE']}"; $date_repeat_compatible_rrule = "{$repeat_data['RRULE']}\n{$repeat_data['RDATE']}\n{$repeat_data['EXDATE']}";
$calculated_dates = date_repeat_calc($date_repeat_compatible_rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions); $calculated_dates = date_repeat_calc($date_repeat_compatible_rrule, $start_datetime, $final_repeat_datetime, $exceptions, $timezone, $additions);
$repeat_dates = array(); $repeat_dates = array();
foreach ($calculated_dates as $delta => $date) { foreach ($calculated_dates as $delta => $date) {
// date_repeat_calc always returns DATE_DATETIME dates, which is // date_repeat_calc always returns DATE_DATETIME dates, which is
...@@ -114,7 +131,7 @@ function _date_ical_parse_repeat_rule($repeat_rule_string) { ...@@ -114,7 +131,7 @@ function _date_ical_parse_repeat_rule($repeat_rule_string) {
continue; continue;
} }
// These keys never have multiple values. // The following keys never have multiple values.
if (in_array($key, array('UNTIL', 'FREQ', 'INTERVAL', 'COUNT', 'WKST'))) { if (in_array($key, array('UNTIL', 'FREQ', 'INTERVAL', 'COUNT', 'WKST'))) {
if ($key == 'UNTIL') { if ($key == 'UNTIL') {
// This is a function from the date_api module, not date_ical. // This is a function from the date_api module, not date_ical.
......
...@@ -64,7 +64,7 @@ class DateiCalFeedsParser extends FeedsParser { ...@@ -64,7 +64,7 @@ class DateiCalFeedsParser extends FeedsParser {
// the subsequent batch starts on the first unparsed component. // the subsequent batch starts on the first unparsed component.
$state->pointer = $parser->getLastComponentParsed() + 1; $state->pointer = $parser->getLastComponentParsed() + 1;
$state->progress($state->total, $state->pointer); $state->progress($state->total, $state->pointer);
return new FeedsParserResult($rows); return new FeedsParserResult($rows);
} }
...@@ -74,6 +74,7 @@ class DateiCalFeedsParser extends FeedsParser { ...@@ -74,6 +74,7 @@ class DateiCalFeedsParser extends FeedsParser {
public function sourceDefaults() { public function sourceDefaults() {
return array( return array(
'indefinite_count' => $this->config['indefinite_count'], 'indefinite_count' => $this->config['indefinite_count'],
'until_not_utc' => $this->config['until_not_utc'],
); );
} }
...@@ -83,6 +84,7 @@ class DateiCalFeedsParser extends FeedsParser { ...@@ -83,6 +84,7 @@ class DateiCalFeedsParser extends FeedsParser {
public function configDefaults() { public function configDefaults() {
return array( return array(
'indefinite_count' => '52', 'indefinite_count' => '52',
'until_not_utc' => FALSE,
); );
} }
...@@ -103,6 +105,14 @@ class DateiCalFeedsParser extends FeedsParser { ...@@ -103,6 +105,14 @@ class DateiCalFeedsParser extends FeedsParser {
'#description' => t('Indefinitely repeating events are not supported. The repeat count will instead be set to this number.'), '#description' => t('Indefinitely repeating events are not supported. The repeat count will instead be set to this number.'),
'#default_value' => $this->config['indefinite_count'], '#default_value' => $this->config['indefinite_count'],
); );
$form['until_not_utc'] = array(
'#title' => t('RRULE UNTILs are not in UTC'),
'#type' => 'checkbox',
'#description' => t('Enable this setting if you\'re seeing reccuring events lose or gain repeat(s) at the end. ' .
'The iCal spec requires that the UNTIL value in an RRULE be specified in UTC, but some iCal feed creators fail to do this ' .
'(the UNTIL values in the feed\'s RRULEs don\'t end is "Z"). This causes the UNTIL value to be off by several hours.'),
'#default_value' => $this->config['until_not_utc'],
);
return $form; return $form;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment