Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14393640
D16648.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D16648.id.diff
View Options
diff --git a/src/parser/calendar/data/PhutilCalendarAbsoluteDateTime.php b/src/parser/calendar/data/PhutilCalendarAbsoluteDateTime.php
--- a/src/parser/calendar/data/PhutilCalendarAbsoluteDateTime.php
+++ b/src/parser/calendar/data/PhutilCalendarAbsoluteDateTime.php
@@ -166,9 +166,15 @@
$m = $this->getMonth();
$d = $this->getDay();
- $h = $this->getHour();
- $i = $this->getMinute();
- $s = $this->getSecond();
+ if ($this->getIsAllDay()) {
+ $h = 0;
+ $i = 0;
+ $s = 0;
+ } else {
+ $h = $this->getHour();
+ $i = $this->getMinute();
+ $s = $this->getSecond();
+ }
$format = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $y, $m, $d, $h, $i, $s);
diff --git a/src/parser/calendar/data/PhutilCalendarEventNode.php b/src/parser/calendar/data/PhutilCalendarEventNode.php
--- a/src/parser/calendar/data/PhutilCalendarEventNode.php
+++ b/src/parser/calendar/data/PhutilCalendarEventNode.php
@@ -15,6 +15,9 @@
private $modifiedDateTime;
private $organizer;
private $attendees = array();
+ private $recurrenceRule;
+ private $recurrenceExceptions = array();
+ private $recurrenceDates = array();
public function setUID($uid) {
$this->uid = $uid;
@@ -125,4 +128,52 @@
return $this;
}
+ public function setRecurrenceRule(
+ PhutilCalendarRecurrenceRule $recurrence_rule) {
+ $this->recurrenceRule = $recurrence_rule;
+ return $this;
+ }
+
+ public function getRecurrenceRule() {
+ return $this->recurrenceRule;
+ }
+
+ public function setRecurrenceUntilDateTime(PhutilCalendarDateTime $date) {
+ $this->recurrenceUntilDateTime = $date;
+ return $this;
+ }
+
+ public function getRecurrenceUntilDateTime() {
+ return $this->recurrenceUntilDateTime;
+ }
+
+ public function setRecurrenceCount($recurrence_count) {
+ $this->recurrenceCount = $recurrence_count;
+ return $this;
+ }
+
+ public function getRecurrenceCount() {
+ return $this->recurrenceCount;
+ }
+
+ public function setRecurrenceExceptions(array $recurrence_exceptions) {
+ assert_instances_of($recurrence_exceptions, 'PhutilCalendarDateTime');
+ $this->recurrenceExceptions = $recurrence_exceptions;
+ return $this;
+ }
+
+ public function getRecurrenceExceptions() {
+ return $this->recurrenceExceptions;
+ }
+
+ public function setRecurrenceDates(array $recurrence_dates) {
+ assert_instances_of($recurrence_dates, 'PhutilCalendarDateTime');
+ $this->recurrenceDates = $recurrence_dates;
+ return $this;
+ }
+
+ public function getRecurrenceDates() {
+ return $this->recurrenceDates;
+ }
+
}
diff --git a/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php b/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php
--- a/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php
+++ b/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php
@@ -17,6 +17,8 @@
private $byMonth = array();
private $bySetPosition = array();
private $weekStart = self::WEEKDAY_MONDAY;
+ private $count;
+ private $until;
private $cursorSecond;
private $cursorMinute;
@@ -84,6 +86,175 @@
const WEEKINDEX_FRIDAY = 5;
const WEEKINDEX_SATURDAY = 6;
+ public function toDictionary() {
+ $parts = array();
+
+ $parts['FREQ'] = $this->getFrequency();
+
+ $interval = $this->getInterval();
+ if ($interval != 1) {
+ $parts['INTERVAL'] = $interval;
+ }
+
+ $by_second = $this->getBySecond();
+ if ($by_second) {
+ $parts['BYSECOND'] = $by_second;
+ }
+
+ $by_minute = $this->getByMinute();
+ if ($by_minute) {
+ $parts['BYMINUTE'] = $by_minute;
+ }
+
+ $by_hour = $this->getByHour();
+ if ($by_hour) {
+ $parts['BYHOUR'] = $by_hour;
+ }
+
+ $by_day = $this->getByDay();
+ if ($by_day) {
+ $parts['BYDAY'] = $by_day;
+ }
+
+ $by_month = $this->getByMonth();
+ if ($by_month) {
+ $parts['BYMONTH'] = $by_month;
+ }
+
+ $by_monthday = $this->getByMonthDay();
+ if ($by_monthday) {
+ $parts['BYMONTHDAY'] = $by_monthday;
+ }
+
+ $by_yearday = $this->getByYearDay();
+ if ($by_yearday) {
+ $parts['BYYEARDAY'] = $by_yearday;
+ }
+
+ $by_weekno = $this->getByWeekNumber();
+ if ($by_weekno) {
+ $parts['BYWEEKNO'] = $by_weekno;
+ }
+
+ $by_setpos = $this->getBySetPosition();
+ if ($by_setpos) {
+ $parts['BYSETPOS'] = $by_setpos;
+ }
+
+ $wkst = $this->getWeekStart();
+ if ($wkst != self::WEEKDAY_MONDAY) {
+ $parts['WKST'] = $wkst;
+ }
+
+ $count = $this->getCount();
+ if ($count) {
+ $parts['COUNT'] = $count;
+ }
+
+ $until = $this->getUntil();
+ if ($until) {
+ $parts['UNTIL'] = $until->getISO8601();
+ }
+
+ return $parts;
+ }
+
+ public static function newFromDictionary(array $dict) {
+ static $expect;
+ if ($expect === null) {
+ $expect = array_fuse(
+ array(
+ 'FREQ',
+ 'INTERVAL',
+ 'BYSECOND',
+ 'BYMINUTE',
+ 'BYHOUR',
+ 'BYDAY',
+ 'BYMONTHDAY',
+ 'BYYEARDAY',
+ 'BYWEEKNO',
+ 'BYSETPOS',
+ 'WKST',
+ 'UNTIL',
+ 'COUNT',
+ ));
+ }
+
+ foreach ($dict as $key => $value) {
+ if (empty($expect[$key])) {
+ throw new Exception(
+ pht(
+ 'RRULE dictionary includes unknown key "%s". Expected keys '.
+ 'are: %s.',
+ $key,
+ implode(', ', array_keys($expect))));
+ }
+ }
+
+ $rrule = id(new self())
+ ->setFrequency(idx($dict, 'FREQ'))
+ ->setInterval(idx($dict, 'INTERVAL', 1))
+ ->setBySecond(idx($dict, 'BYSECOND', array()))
+ ->setByMinute(idx($dict, 'BYMINUTE', array()))
+ ->setByHour(idx($dict, 'BYHOUR', array()))
+ ->setByDay(idx($dict, 'BYDAY', array()))
+ ->setByMonthDay(idx($dict, 'BYMONTHDAY', array()))
+ ->setByYearDay(idx($dict, 'BYYEARDAY', array()))
+ ->setByWeekNumber(idx($dict, 'BYWEEKNO', array()))
+ ->setBySetPosition(idx($dict, 'BYSETPOS', array()))
+ ->setWeekStart(idx($dict, 'WKST', self::WEEKDAY_MONDAY));
+
+ $count = idx($dict, 'COUNT');
+ if ($count) {
+ $rrule->setCount($count);
+ }
+
+ $until = idx($dict, 'UNTIL');
+ if ($until) {
+ $until = PhutilCalendarAbsoluteDateTime::newFromISO8601($until);
+ $rrule->setUntil($until);
+ }
+
+ return $rrule;
+ }
+
+ public function toRRULE() {
+ $dict = $this->toDictionary();
+
+ $parts = array();
+ foreach ($dict as $key => $value) {
+ if (is_array($value)) {
+ $value = implode(',', $value);
+ }
+ $parts[] = "{$key}={$value}";
+ }
+
+ return implode(';', $parts);
+ }
+
+ public static function newFromRRULE($rrule) {
+ $parts = explode(';', $rrule);
+
+ $dict = array();
+ foreach ($parts as $part) {
+ list($key, $value) = explode('=', $part, 2);
+ switch ($key) {
+ case 'FREQ':
+ case 'INTERVAL':
+ case 'WKST':
+ case 'COUNT':
+ case 'UNTIL';
+ break;
+ default:
+ $value = explode(',', $value);
+ break;
+ }
+ $dict[$key] = $value;
+ }
+
+ return self::newFromDictionary($dict);
+ }
+
private static function getAllWeekdayConstants() {
return array_keys(self::getWeekdayIndexMap());
}
@@ -126,6 +297,31 @@
return $this->startDateTime;
}
+ public function setCount($count) {
+ if ($count < 1) {
+ throw new Exception(
+ pht(
+ 'RRULE COUNT value "%s" is invalid: count must be at least 1.',
+ $count));
+ }
+
+ $this->count = $count;
+ return $this;
+ }
+
+ public function getCount() {
+ return $this->count;
+ }
+
+ public function setUntil(PhutilCalendarDateTime $until) {
+ $this->until = $until;
+ return $this;
+ }
+
+ public function getUntil() {
+ return $this->until;
+ }
+
public function setFrequency($frequency) {
static $map = array(
self::FREQUENCY_SECONDLY => self::SCALE_SECONDLY,
diff --git a/src/parser/calendar/ics/PhutilICSWriter.php b/src/parser/calendar/ics/PhutilICSWriter.php
--- a/src/parser/calendar/ics/PhutilICSWriter.php
+++ b/src/parser/calendar/ics/PhutilICSWriter.php
@@ -209,6 +209,27 @@
}
}
+ $rrule = $event->getRecurrenceRule();
+ if ($rrule) {
+ $properties[] = $this->newRRULEProperty(
+ 'RRULE',
+ $rrule);
+ }
+
+ $exdates = $event->getRecurrenceExceptions();
+ if ($exdates) {
+ $properties[] = $this->newDateTimesProperty(
+ 'EXDATE',
+ $exdates);
+ }
+
+ $rdates = $event->getRecurrenceDates();
+ if ($rdates) {
+ $properties[] = $this->newDateTimesProperty(
+ 'RDATE',
+ $rdates);
+ }
+
return $properties;
}
@@ -238,9 +259,17 @@
$name,
PhutilCalendarDateTime $value,
array $parameters = array()) {
- $datetime = $value->getISO8601();
- if ($value->getIsAllDay()) {
+ return $this->newDateTimesProperty($name, array($value), $parameters);
+ }
+
+ private function newDateTimesProperty(
+ $name,
+ array $values,
+ array $parameters = array()) {
+ assert_instances_of($values, 'PhutilCalendarDateTime');
+
+ if (head($values)->getIsAllDay()) {
$parameters[] = array(
'name' => 'VALUE',
'values' => array(
@@ -249,7 +278,13 @@
);
}
- return $this->newProperty($name, $datetime, $parameters);
+ $datetimes = array();
+ foreach ($values as $value) {
+ $datetimes[] = $value->getISO8601();
+ }
+ $datetimes = implode(';', $datetimes);
+
+ return $this->newProperty($name, $datetimes, $parameters);
}
private function newUserProperty(
@@ -292,6 +327,15 @@
return $this->newProperty($name, $value->getURI(), $parameters);
}
+ private function newRRULEProperty(
+ $name,
+ PhutilCalendarRecurrenceRule $rule,
+ array $parameters = array()) {
+
+ $value = $rule->toRRULE();
+ return $this->newProperty($name, $value, $parameters);
+ }
+
private function newProperty(
$name,
$value,
diff --git a/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php b/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
--- a/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
+++ b/src/parser/calendar/ics/__tests__/PhutilICSWriterTestCase.php
@@ -37,6 +37,39 @@
$this->assertICS('writer-tea-time.ics', $ics_data);
}
+ public function testICSWriterChristmas() {
+ $start = PhutilCalendarAbsoluteDateTime::newFromISO8601('20001225T000000Z');
+ $end = PhutilCalendarAbsoluteDateTime::newFromISO8601('20001226T000000Z');
+
+ $rrule = id(new PhutilCalendarRecurrenceRule())
+ ->setFrequency(PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY)
+ ->setByMonth(array(12))
+ ->setByMonthDay(array(25));
+
+ $event = id(new PhutilCalendarEventNode())
+ ->setUID('recurring-christmas')
+ ->setName('Christmas')
+ ->setDescription('Festival holiday first occurring in the year 2000.')
+ ->setStartDateTime($start)
+ ->setEndDateTime($end)
+ ->setCreatedDateTime($start)
+ ->setModifiedDateTime($start)
+ ->setRecurrenceRule($rrule)
+ ->setRecurrenceExceptions(
+ array(
+ // In 2007, Christmas was cancelled.
+ PhutilCalendarAbsoluteDateTime::newFromISO8601('20071225T000000Z'),
+ ))
+ ->setRecurrenceDates(
+ array(
+ // We had an extra early Christmas in 2009.
+ PhutilCalendarAbsoluteDateTime::newFromISO8601('20091125T000000Z'),
+ ));
+
+ $ics_data = $this->writeICSSingleEvent($event);
+ $this->assertICS('writer-recurring-christmas.ics', $ics_data);
+ }
+
public function testICSWriterAllDay() {
$event = id(new PhutilCalendarEventNode())
->setUID('christmas-day')
diff --git a/src/parser/calendar/ics/__tests__/data/writer-recurring-christmas.ics b/src/parser/calendar/ics/__tests__/data/writer-recurring-christmas.ics
new file mode 100644
--- /dev/null
+++ b/src/parser/calendar/ics/__tests__/data/writer-recurring-christmas.ics
@@ -0,0 +1,16 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Phacility//Phabricator//EN
+BEGIN:VEVENT
+UID:recurring-christmas
+CREATED:20001225T000000Z
+DTSTAMP:20001225T000000Z
+DTSTART:20001225T000000Z
+DTEND:20001226T000000Z
+SUMMARY:Christmas
+DESCRIPTION:Festival holiday first occurring in the year 2000.
+RRULE:FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25
+EXDATE:20071225T000000Z
+RDATE:20091125T000000Z
+END:VEVENT
+END:VCALENDAR
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Dec 23, 12:27 AM (12 h, 25 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6919924
Default Alt Text
D16648.id.diff (12 KB)
Attached To
Mode
D16648: Support RRULE export in ICS from libphutil
Attached
Detach File
Event Timeline
Log In to Comment