Page MenuHomePhabricator

D16800.id.diff
No OneTemporary

D16800.id.diff

diff --git a/src/parser/calendar/ics/PhutilICSParser.php b/src/parser/calendar/ics/PhutilICSParser.php
--- a/src/parser/calendar/ics/PhutilICSParser.php
+++ b/src/parser/calendar/ics/PhutilICSParser.php
@@ -28,12 +28,13 @@
const PARSE_EMPTY_DATETIME = 'empty-datetime';
const PARSE_MANY_DATETIME = 'many-datetime';
const PARSE_BAD_DATETIME = 'bad-datetime';
- const PARSE_BAD_TZID = 'bad-tzid';
const PARSE_EMPTY_DURATION = 'empty-duration';
const PARSE_MANY_DURATION = 'many-duration';
const PARSE_BAD_DURATION = 'bad-duration';
const WARN_TZID_UTC = 'warn-tzid-utc';
+ const WARN_TZID_GUESS = 'warn-tzid-guess';
+ const WARN_TZID_IGNORED = 'warn-tzid-ignored';
public function parseICSData($data) {
$this->stack = array();
@@ -617,6 +618,10 @@
return $this;
}
+ public function getWarnings() {
+ return $this->warnings;
+ }
+
private function didParseEventProperty(
PhutilCalendarEventNode $node,
$name,
@@ -736,15 +741,7 @@
}
$tzid = 'UTC';
} else if ($tzid !== null) {
- $map = DateTimeZone::listIdentifiers();
- $map = array_fuse($map);
- if (empty($map[$tzid])) {
- $this->raiseParseFailure(
- self::PARSE_BAD_TZID,
- pht(
- 'Timezone "%s" is not a recognized timezone.',
- $tzid));
- }
+ $tzid = $this->guessTimezone($tzid);
}
try {
@@ -835,5 +832,67 @@
return idx(head($value), 'value');
}
+ private function guessTimezone($tzid) {
+ $map = DateTimeZone::listIdentifiers();
+ $map = array_fuse($map);
+ if (isset($map[$tzid])) {
+ // This is a real timezone we recognize, so just use it as provided.
+ return $tzid;
+ }
+
+ // Look for something that looks like "UTC+3" or "GMT -05.00". If we find
+ // anything
+ $offset_pattern =
+ '/'.
+ '(?:UTC|GMT)'.
+ '\s*'.
+ '(?P<sign>[+-])'.
+ '\s*'.
+ '(?P<h>\d+)'.
+ '(?:'.
+ '[:.](?P<m>\d+)'.
+ ')?'.
+ '/i';
+
+ $matches = null;
+ if (preg_match($offset_pattern, $tzid, $matches)) {
+ $hours = (int)$matches['h'];
+ $minutes = (int)idx($matches, 'm');
+ $offset = ($hours * 60 * 60) + ($minutes * 60);
+
+ if (idx($matches, 'sign') == '-') {
+ $offset = -$offset;
+ }
+
+ // NOTE: We could possibly do better than this, by using the event start
+ // time to guess a timezone. However, that won't work for recurring
+ // events and would require us to do this work after finishing initial
+ // parsing. Since these unusual offset-based timezones appear to be rare,
+ // the benefit may not be worth the complexity.
+ $now = new DateTime('@'.time());
+
+ foreach ($map as $identifier) {
+ $zone = new DateTimeZone($identifier);
+ if ($zone->getOffset($now) == $offset) {
+ $this->raiseWarning(
+ self::WARN_TZID_GUESS,
+ pht(
+ 'TZID "%s" is unknown, guessing "%s" based on pattern "%s".',
+ $tzid,
+ $identifier,
+ $matches[0]));
+ return $identifier;
+ }
+ }
+ }
+
+ $this->raiseWarning(
+ self::WARN_TZID_IGNORED,
+ pht(
+ 'TZID "%s" is unknown, using UTC instead.',
+ $tzid));
+
+ return 'UTC';
+ }
}
diff --git a/src/parser/calendar/ics/__tests__/PhutilICSParserTestCase.php b/src/parser/calendar/ics/__tests__/PhutilICSParserTestCase.php
--- a/src/parser/calendar/ics/__tests__/PhutilICSParserTestCase.php
+++ b/src/parser/calendar/ics/__tests__/PhutilICSParserTestCase.php
@@ -113,6 +113,16 @@
$event->getEndDateTime()->getEpoch());
}
+ public function testICSOddTimezone() {
+ $event = $this->parseICSSingleEvent('zimbra-timezone.ics');
+
+ $start = $event->getStartDateTime();
+
+ $this->assertEqual(
+ '20170303T140000Z',
+ $start->getISO8601());
+ }
+
public function testICSFloatingTime() {
// This tests "floating" event times, which have no absolute time and are
// supposed to be interpreted using the viewer's timezone. It also uses
@@ -234,8 +244,6 @@
PhutilICSParser::PARSE_MANY_DATETIME,
'err-bad-datetime.ics' =>
PhutilICSParser::PARSE_BAD_DATETIME,
- 'err-bad-tzid.ics' =>
- PhutilICSParser::PARSE_BAD_TZID,
'err-empty-duration.ics' =>
PhutilICSParser::PARSE_EMPTY_DURATION,
'err-many-duration.ics' =>
diff --git a/src/parser/calendar/ics/__tests__/data/err-bad-tzid.ics b/src/parser/calendar/ics/__tests__/data/err-bad-tzid.ics
deleted file mode 100644
--- a/src/parser/calendar/ics/__tests__/data/err-bad-tzid.ics
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART;TZID=quack:20130101
-END:VEVENT
-END:VCALENDAR
diff --git a/src/parser/calendar/ics/__tests__/data/zimbra-timezone.ics b/src/parser/calendar/ics/__tests__/data/zimbra-timezone.ics
new file mode 100644
--- /dev/null
+++ b/src/parser/calendar/ics/__tests__/data/zimbra-timezone.ics
@@ -0,0 +1,12 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20161104T220244Z
+UID:zimbra-timezone
+SUMMARY:Zimbra Timezone
+DTSTART;TZID="(GMT-05.00) Auto-Detected":20170303T090000
+DTSTAMP:20161104T220244Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 20, 5:47 PM (2 d, 9 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7697975
Default Alt Text
D16800.id.diff (5 KB)

Event Timeline