1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-19 16:58:48 +02:00

Introduce Calendar "UTC Epoch" columns for query windowing

Summary:
Ref T10747. Currently, Calendar events are mostly epoch-based and cheat a little bit for all-day events.

This already felt a little flimsy, and can't reasonably accommodate the full range of `.ics` events, which include "floating" events (e.g., occurs at 3PM regardless of timezone, like "Tea Time").

As a secondary issue, we identify instances of a recurring event by instance number (1, 2, 3, etc.). This can't accommodate the full range of `.ics` events, which include arbitrary additional "RDATE" events (e.g., recurrs every week, and also on these specific extra days).

However, we do need to store some epoch information so we can do query windowing: when the user looks at "October 2016", we want to select the smallest number of events that we can from the database initially, before refining them down to generate instances. We can't reasonably query the actual dates no matter how we store them because this depends on computing things like UNTIL, COUNT, initial dates, whether events are recurring or not, timezones, etc.

Instead, when we save an event compute the earliest second it occurs on in UTC and the latest second it occurs on in UTC. We can then query for a small superset of possible events in "October 2016" for any viewer pretty easily.

Also, start laying the groundwork for using fewer epochs in the rest of the code, and for reducing the role of sequence indexes (I plan to keep some sequences indexes around, probably, since they're nice in the UI, but not all child events will have indexes since there's no index for an RDATE event).

This doesn't migrate existing events yet or actually read these new columns -- that will come later once the new code is a little more solid.

Test Plan:
  - Ran `bin/storage upgrade`.
  - Created a new event.
  - Saved an existing event.
  - Viewed database, saw sensible-looking "UTC Epoch" values.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16652
This commit is contained in:
epriestley 2016-10-03 08:32:06 -07:00
parent 98f22d9523
commit 0ce7eacaf1
2 changed files with 129 additions and 5 deletions

View file

@ -0,0 +1,8 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD utcInitialEpoch INT UNSIGNED NOT NULL;
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD utcUntilEpoch INT UNSIGNED;
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
ADD utcInstanceEpoch INT UNSIGNED;

View file

@ -41,11 +41,16 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $spacePHID;
protected $utcInitialEpoch;
protected $utcUntilEpoch;
protected $utcInstanceEpoch;
private $parentEvent = self::ATTACHABLE;
private $invitees = self::ATTACHABLE;
private $viewerDateFrom;
private $viewerDateTo;
private $viewerTimezone;
// Frequency Constants
const FREQUENCY_DAILY = 'daily';
@ -298,6 +303,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$zone);
}
$this->viewerTimezone = $viewer->getTimezoneIdentifier();
return $this;
}
@ -323,11 +330,62 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $dst->format('U');
}
public function updateUTCEpochs() {
// The "intitial" epoch is the start time of the event, in UTC.
$start_date = $this->newStartDateTime()
->setViewerTimezone('UTC');
$start_epoch = $start_date->getEpoch();
$this->setUTCInitialEpoch($start_epoch);
// The "until" epoch is the last UTC epoch on which any instance of this
// event occurs. For infinitely recurring events, it is `null`.
if (!$this->getIsRecurring()) {
$end_date = $this->newEndDateTime()
->setViewerTimezone('UTC');
$until_epoch = $end_date->getEpoch();
} else {
$until_epoch = null;
$until_date = $this->newUntilDateTime()
->setViewerTimezone('UTC');
if ($until_date) {
$duration = $this->newDuration();
$until_epoch = id(new PhutilCalendarRelativeDateTime())
->setOrigin($until_date)
->setDuration($duration)
->getEpoch();
}
}
$this->setUTCUntilEpoch($until_epoch);
// The "instance" epoch is a property of instances of recurring events.
// It's the original UTC epoch on which the instance started. Usually that
// is the same as the start date, but they may be different if the instance
// has been edited.
// The ICS format uses this value (original start time) to identify event
// instances, and must do so because it allows additional arbitrary
// instances to be added (with "RDATE").
$instance_epoch = null;
$instance_date = $this->newInstanceDateTime();
if ($instance_date) {
$instance_epoch = $instance_date
->setViewerTimezone('UTC')
->getEpoch();
}
$this->setUTCInstanceEpoch($instance_epoch);
return $this;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$this->updateUTCEpochs();
return parent::save();
}
@ -363,6 +421,9 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'instanceOfEventPHID' => 'phid?',
'sequenceIndex' => 'uint32?',
'isStub' => 'bool',
'utcInitialEpoch' => 'epoch',
'utcUntilEpoch' => 'epoch?',
'utcInstanceEpoch' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_date' => array(
@ -372,6 +433,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'columns' => array('instanceOfEventPHID', 'sequenceIndex'),
'unique' => true,
),
'key_epoch' => array(
'columns' => array('utcInitialEpoch', 'utcUntilEpoch'),
),
'key_rdate' => array(
'columns' => array('instanceOfEventPHID', 'utcInstanceEpoch'),
'unique' => true,
),
),
self::CONFIG_SERIALIZATION => array(
'recurrenceFrequency' => self::SERIALIZATION_JSON,
@ -641,11 +709,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$modified = $this->getDateModified();
$modified = PhutilCalendarAbsoluteDateTime::newFromEpoch($modified);
$date_start = $this->getDateFrom();
$date_start = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_start);
$date_end = $this->getDateTo();
$date_end = PhutilCalendarAbsoluteDateTime::newFromEpoch($date_end);
$date_start = $this->newStartDateTime();
$date_end = $this->newEndDateTime();
if ($this->getIsAllDay()) {
$date_start->setIsAllDay(true);
@ -719,6 +784,57 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $node;
}
public function newStartDateTime() {
$epoch = $this->getDateFrom();
return $this->newDateTimeFromEpoch($epoch);
}
public function newEndDateTime() {
$epoch = $this->getDateTo();
return $this->newDateTimeFromEpoch($epoch);
}
public function newUntilDateTime() {
$epoch = $this->getRecurrenceEndDate();
if (!$epoch) {
return null;
}
return $this->newDateTimeFromEpoch($epoch);
}
public function newDuration() {
return id(new PhutilCalendarDuration())
->setSeconds($this->getDuration());
}
public function newInstanceDateTime() {
if (!$this->getIsRecurring()) {
return null;
}
$epochs = $this->getParent()->getSequenceIndexEpochs(
new PhabricatorUser(),
$this->getSequenceIndex(),
$this->getDuration());
$epoch = $epochs['dateFrom'];
return $this->newDateTimeFromEpoch($epoch);
}
private function newDateTimeFromEpoch($epoch) {
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($epoch);
$viewer_timezone = $this->viewerTimezone;
if ($viewer_timezone) {
$datetime->setViewerTimezone($viewer_timezone);
}
if ($this->getIsAllDay()) {
$datetime->setIsAllDay(true);
}
return $datetime;
}
/* -( Markup Interface )--------------------------------------------------- */