@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Migrate Calendar away from stored-epoch fields

Summary:
Ref T10747. This deprecates "dateFrom", "dateTo", "allDayDateFrom", "allDayDateTo", and "recurrenceEndDate".

They are replaced with "utc*Epoch" fields (for querying) and CalendarDateTime objects (for start, end, until). These objects can represent the full range of dates and times expressible in ICS format, allowing us to import a wider range of ICS events.

Test Plan:
Ran migrations, viewed/edited Calendar, didn't catch anything catastrophcially broken.

This likely needs some followups, I'll keep it local for a bit until I'm confident I didn't break anything too catastrophically. I'm retaining the old data for now so we can likely fix things if it turns out there is some sort of issue.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16664

+168 -141
+1 -50
resources/sql/autopatches/20160715.event.03.allday.php
··· 1 1 <?php 2 2 3 - $table = new PhabricatorCalendarEvent(); 4 - $conn = $table->establishConnection('w'); 5 - 6 - // Previously, "All Day" events were stored with a start and end date set to 7 - // the earliest possible start and end seconds for the corresponding days. We 8 - // now store all day events with their "date" epochs as UTC, separate from 9 - // individual event times. 10 - $zone_min = new DateTimeZone('Pacific/Midway'); 11 - $zone_max = new DateTimeZone('Pacific/Kiritimati'); 12 - $zone_utc = new DateTimeZone('UTC'); 13 - 14 - foreach (new LiskMigrationIterator($table) as $event) { 15 - // If this event has already migrated, skip it. 16 - if ($event->getAllDayDateFrom()) { 17 - continue; 18 - } 19 - 20 - $is_all_day = $event->getIsAllDay(); 21 - 22 - $epoch_min = $event->getDateFrom(); 23 - $epoch_max = $event->getDateTo(); 24 - 25 - $date_min = new DateTime('@'.$epoch_min); 26 - $date_max = new DateTime('@'.$epoch_max); 27 - 28 - if ($is_all_day) { 29 - $date_min->setTimeZone($zone_min); 30 - $date_min->modify('+2 days'); 31 - $date_max->setTimeZone($zone_max); 32 - $date_max->modify('-2 days'); 33 - } else { 34 - $date_min->setTimeZone($zone_utc); 35 - $date_max->setTimeZone($zone_utc); 36 - } 37 - 38 - $string_min = $date_min->format('Y-m-d'); 39 - $string_max = $date_max->format('Y-m-d 23:59:00'); 40 - 41 - $allday_min = id(new DateTime($string_min, $zone_utc))->format('U'); 42 - $allday_max = id(new DateTime($string_max, $zone_utc))->format('U'); 43 - 44 - queryfx( 45 - $conn, 46 - 'UPDATE %T SET allDayDateFrom = %d, allDayDateTo = %d 47 - WHERE id = %d', 48 - $table->getTableName(), 49 - $allday_min, 50 - $allday_max, 51 - $event->getID()); 52 - } 3 + // This migration was replaced by "20161004.cal.01.noepoch.php".
+125
resources/sql/autopatches/20161004.cal.01.noepoch.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorCalendarEvent(); 4 + $conn = $table->establishConnection('w'); 5 + $table_name = 'calendar_event'; 6 + 7 + // Long ago, "All Day" events were stored with a start and end date set to 8 + // the earliest possible start and end seconds for the corresponding days. We 9 + // then moved to store all day events with their "date" epochs as UTC, separate 10 + // from individual event times. Both systems were later replaced with use of 11 + // CalendarDateTime. 12 + $zone_min = new DateTimeZone('Pacific/Midway'); 13 + $zone_max = new DateTimeZone('Pacific/Kiritimati'); 14 + $zone_utc = new DateTimeZone('UTC'); 15 + 16 + foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) { 17 + $parameters = phutil_json_decode($row['parameters']); 18 + if (isset($parameters['startDateTime'])) { 19 + // This event has already been migrated. 20 + continue; 21 + } 22 + 23 + $is_all_day = $row['isAllDay']; 24 + 25 + if (empty($row['allDayDateFrom'])) { 26 + // No "allDayDateFrom" means this is an old event which was never migrated 27 + // by the earlier "20160715.event.03.allday.php" migration. The dateFrom 28 + // and dateTo will be minimum and maximum earthly seconds for the event. We 29 + // convert them to UTC if they were in extreme timezones. 30 + $epoch_min = $row['dateFrom']; 31 + $epoch_max = $row['dateTo']; 32 + 33 + if ($is_all_day) { 34 + $date_min = new DateTime('@'.$epoch_min); 35 + $date_max = new DateTime('@'.$epoch_max); 36 + 37 + $date_min->setTimeZone($zone_min); 38 + $date_min->modify('+2 days'); 39 + $date_max->setTimeZone($zone_max); 40 + $date_max->modify('-2 days'); 41 + 42 + $string_min = $date_min->format('Y-m-d'); 43 + $string_max = $date_max->format('Y-m-d 23:59:00'); 44 + 45 + $utc_min = id(new DateTime($string_min, $zone_utc))->format('U'); 46 + $utc_max = id(new DateTime($string_max, $zone_utc))->format('U'); 47 + } else { 48 + $utc_min = $epoch_min; 49 + $utc_max = $epoch_max; 50 + } 51 + } else { 52 + // This is an event which was migrated already. We can pick the correct 53 + // epoch timestamps based on the "isAllDay" flag. 54 + if ($is_all_day) { 55 + $utc_min = $row['allDayDateFrom']; 56 + $utc_max = $row['allDayDateTo']; 57 + } else { 58 + $utc_min = $row['dateFrom']; 59 + $utc_max = $row['dateTo']; 60 + } 61 + } 62 + 63 + $utc_until = $row['recurrenceEndDate']; 64 + 65 + $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_min); 66 + if ($is_all_day) { 67 + $start_datetime->setIsAllDay(true); 68 + } 69 + 70 + $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_max); 71 + if ($is_all_day) { 72 + $end_datetime->setIsAllDay(true); 73 + } 74 + 75 + if ($utc_until) { 76 + $until_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_until); 77 + } else { 78 + $until_datetime = null; 79 + } 80 + 81 + $parameters['startDateTime'] = $start_datetime->toDictionary(); 82 + $parameters['endDateTime'] = $end_datetime->toDictionary(); 83 + if ($until_datetime) { 84 + $parameters['untilDateTime'] = $until_datetime->toDictionary(); 85 + } 86 + 87 + queryfx( 88 + $conn, 89 + 'UPDATE %T SET parameters = %s WHERE id = %d', 90 + $table_name, 91 + phutil_json_encode($parameters), 92 + $row['id']); 93 + } 94 + 95 + // Generate UTC epochs for all events. We can't readily do this one at a 96 + // time because instance UTC epochs rely on having the parent event. 97 + $viewer = PhabricatorUser::getOmnipotentUser(); 98 + 99 + $all_events = id(new PhabricatorCalendarEventQuery()) 100 + ->setViewer($viewer) 101 + ->execute(); 102 + foreach ($all_events as $event) { 103 + if ($event->getUTCInitialEpoch()) { 104 + // Already migrated. 105 + continue; 106 + } 107 + 108 + try { 109 + $event->updateUTCEpochs(); 110 + } catch (Exception $ex) { 111 + continue; 112 + } 113 + 114 + queryfx( 115 + $conn, 116 + 'UPDATE %T SET 117 + utcInitialEpoch = %d, 118 + utcUntilEpoch = %nd, 119 + utcInstanceEpoch = %nd WHERE id = %d', 120 + $table_name, 121 + $event->getUTCInitialEpoch(), 122 + $event->getUTCUntilEpoch(), 123 + $event->getUTCInstanceEpoch(), 124 + $event->getID()); 125 + }
+2 -2
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 352 352 if ($this->rangeBegin) { 353 353 $where[] = qsprintf( 354 354 $conn, 355 - 'event.dateTo >= %d OR event.isRecurring = 1', 355 + '(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)', 356 356 $this->rangeBegin - phutil_units('16 hours in seconds')); 357 357 } 358 358 359 359 if ($this->rangeEnd) { 360 360 $where[] = qsprintf( 361 361 $conn, 362 - 'event.dateFrom <= %d', 362 + 'event.utcInitialEpoch <= %d', 363 363 $this->rangeEnd + phutil_units('16 hours in seconds')); 364 364 } 365 365
+40 -69
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 17 17 18 18 protected $name; 19 19 protected $hostPHID; 20 - protected $dateFrom; 21 - protected $dateTo; 22 - protected $allDayDateFrom; 23 - protected $allDayDateTo; 24 20 protected $description; 25 21 protected $isCancelled; 26 22 protected $isAllDay; ··· 30 26 31 27 protected $isRecurring = 0; 32 28 protected $recurrenceFrequency = array(); 33 - protected $recurrenceEndDate; 34 29 35 30 private $isGhostEvent = false; 36 31 protected $instanceOfEventPHID; ··· 51 46 52 47 private $viewerTimezone; 53 48 49 + // TODO: DEPRECATED. Remove once we're sure the migrations worked. 50 + protected $allDayDateFrom; 51 + protected $allDayDateTo; 52 + protected $dateFrom; 53 + protected $dateTo; 54 + protected $recurrenceEndDate; 55 + 54 56 // Frequency Constants 55 57 const FREQUENCY_DAILY = 'daily'; 56 58 const FREQUENCY_WEEKLY = 'weekly'; ··· 70 72 71 73 $now = PhabricatorTime::getNow(); 72 74 73 - $start = new DateTime('@'.$now); 74 - $start->setTimeZone($actor->getTimeZone()); 75 - 76 - $start->setTime($start->format('H'), 0, 0); 77 - $start->modify('+1 hour'); 78 - $end = id(clone $start)->modify('+1 hour'); 79 - 80 - $epoch_min = $start->format('U'); 81 - $epoch_max = $end->format('U'); 82 - 83 - $now_date = new DateTime('@'.$now); 84 - $now_min = id(clone $now_date)->setTime(0, 0)->format('U'); 85 - $now_max = id(clone $now_date)->setTime(23, 59)->format('U'); 86 - 87 75 $default_icon = 'fa-calendar'; 88 76 89 77 $datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch( ··· 106 94 ->setEditPolicy($edit_policy) 107 95 ->setSpacePHID($actor->getDefaultSpacePHID()) 108 96 ->attachInvitees(array()) 109 - ->setDateFrom($epoch_min) 110 - ->setDateTo($epoch_max) 111 - ->setAllDayDateFrom($now_min) 112 - ->setAllDayDateTo($now_max) 97 + ->setDateFrom(0) 98 + ->setDateTo(0) 99 + ->setAllDayDateFrom(0) 100 + ->setAllDayDateTo(0) 113 101 ->setStartDateTime($datetime_start) 114 102 ->setEndDateTime($datetime_end) 115 103 ->applyViewerTimezone($actor); ··· 130 118 ->setSequenceIndex($sequence) 131 119 ->setIsRecurring(true) 132 120 ->setRecurrenceFrequency($this->getRecurrenceFrequency()) 133 - ->attachParentEvent($this); 121 + ->attachParentEvent($this) 122 + ->setAllDayDateFrom(0) 123 + ->setAllDayDateTo(0) 124 + ->setDateFrom(0) 125 + ->setDateTo(0); 134 126 135 127 return $child->copyFromParent($actor); 136 128 } ··· 187 179 $duration = $parent->getDuration(); 188 180 $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); 189 181 182 + $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 183 + $epochs['dateFrom'], 184 + $parent->newStartDateTime()->getTimezone()); 185 + $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 186 + $epochs['dateTo'], 187 + $parent->newEndDateTime()->getTimezone()); 188 + 190 189 $this 191 - ->setDateFrom($epochs['dateFrom']) 192 - ->setDateTo($epochs['dateTo']) 193 - ->setAllDayDateFrom($epochs['allDayDateFrom']) 194 - ->setAllDayDateTo($epochs['allDayDateTo']); 190 + ->setStartDateTime($start_datetime) 191 + ->setEndDateTime($end_datetime); 195 192 196 193 return $this; 197 194 } ··· 213 210 $frequency = $this->getFrequencyUnit(); 214 211 $modify_key = '+'.$sequence.' '.$frequency; 215 212 216 - $date = $this->getDateFrom(); 217 - $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); 213 + $date_time = $this->newStartDateTime() 214 + ->setViewerTimezone($viewer->getTimezoneIdentifier()) 215 + ->newPHPDateTime(); 216 + 218 217 $date_time->modify($modify_key); 219 218 $date = $date_time->format('U'); 220 219 221 - $end_date = $this->getRecurrenceEndDate(); 220 + $end_date = $this->getUntilDateTimeEpoch(); 222 221 if ($end_date && $date > $end_date) { 223 222 throw new Exception( 224 223 pht( ··· 227 226 $sequence)); 228 227 } 229 228 230 - $utc = new DateTimeZone('UTC'); 231 - 232 - $allday_from = $this->getAllDayDateFrom(); 233 - $allday_date = new DateTime('@'.$allday_from, $utc); 234 - $allday_date->setTimeZone($utc); 235 - $allday_date->modify($modify_key); 236 - 237 - $allday_min = $allday_date->format('U'); 238 - $allday_duration = ($this->getAllDayDateTo() - $allday_from); 239 - 240 229 return array( 241 230 'dateFrom' => $date, 242 231 'dateTo' => $date + $duration, 243 - 'allDayDateFrom' => $allday_min, 244 - 'allDayDateTo' => $allday_min + $allday_duration, 245 232 ); 246 233 } 247 234 ··· 280 267 return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch()); 281 268 } 282 269 283 - public function getDateEpochForTimezone( 284 - $epoch, 285 - $src_zone, 286 - $format, 287 - $adjust, 288 - $dst_zone) { 289 - 290 - $src = new DateTime('@'.$epoch); 291 - $src->setTimeZone($src_zone); 292 - 293 - if (strlen($adjust)) { 294 - $adjust = ' '.$adjust; 295 - } 296 - 297 - $dst = new DateTime($src->format($format).$adjust, $dst_zone); 298 - return $dst->format('U'); 299 - } 300 - 301 270 public function updateUTCEpochs() { 302 271 // The "intitial" epoch is the start time of the event, in UTC. 303 272 $start_date = $this->newStartDateTime() ··· 314 283 $until_epoch = $end_date->getEpoch(); 315 284 } else { 316 285 $until_epoch = null; 317 - $until_date = $this->newUntilDateTime() 318 - ->setViewerTimezone('UTC'); 286 + $until_date = $this->newUntilDateTime(); 319 287 if ($until_date) { 288 + $until_date->setViewerTimezone('UTC'); 320 289 $duration = $this->newDuration(); 321 290 $until_epoch = id(new PhutilCalendarRelativeDateTime()) 322 291 ->setOrigin($until_date) ··· 377 346 self::CONFIG_AUX_PHID => true, 378 347 self::CONFIG_COLUMN_SCHEMA => array( 379 348 'name' => 'text', 380 - 'dateFrom' => 'epoch', 381 - 'dateTo' => 'epoch', 382 - 'allDayDateFrom' => 'epoch', 383 - 'allDayDateTo' => 'epoch', 384 349 'description' => 'text', 385 350 'isCancelled' => 'bool', 386 351 'isAllDay' => 'bool', 387 352 'icon' => 'text32', 388 353 'mailKey' => 'bytes20', 389 354 'isRecurring' => 'bool', 390 - 'recurrenceEndDate' => 'epoch?', 391 355 'instanceOfEventPHID' => 'phid?', 392 356 'sequenceIndex' => 'uint32?', 393 357 'isStub' => 'bool', 394 358 'utcInitialEpoch' => 'epoch', 395 359 'utcUntilEpoch' => 'epoch?', 396 360 'utcInstanceEpoch' => 'epoch?', 361 + 362 + // TODO: DEPRECATED. 363 + 'allDayDateFrom' => 'epoch', 364 + 'allDayDateTo' => 'epoch', 365 + 'dateFrom' => 'epoch', 366 + 'dateTo' => 'epoch', 367 + 'recurrenceEndDate' => 'epoch?', 397 368 ), 398 369 self::CONFIG_KEY_SCHEMA => array( 399 370 'key_date' => array( ··· 814 785 return null; 815 786 } 816 787 817 - $epochs = $this->getParent()->getSequenceIndexEpochs( 788 + $epochs = $this->getParentEvent()->getSequenceIndexEpochs( 818 789 new PhabricatorUser(), 819 790 $this->getSequenceIndex(), 820 791 $this->getDuration());
-10
src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
··· 13 13 public function applyInternalEffects($object, $value) { 14 14 $actor = $this->getActor(); 15 15 16 - // TODO: DEPRECATED. 17 - $object->setDateTo($value); 18 - $object->setAllDayDateTo( 19 - $object->getDateEpochForTimezone( 20 - $value, 21 - $actor->getTimeZone(), 22 - 'Y-m-d 23:59:00', 23 - null, 24 - new DateTimeZone('UTC'))); 25 - 26 16 $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 27 17 $value, 28 18 $actor->getTimezoneIdentifier());
-10
src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
··· 13 13 public function applyInternalEffects($object, $value) { 14 14 $actor = $this->getActor(); 15 15 16 - // TODO: DEPRECATED. 17 - $object->setDateFrom($value); 18 - $object->setAllDayDateFrom( 19 - $object->getDateEpochForTimezone( 20 - $value, 21 - $actor->getTimeZone(), 22 - 'Y-m-d', 23 - null, 24 - new DateTimeZone('UTC'))); 25 - 26 16 $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 27 17 $value, 28 18 $actor->getTimezoneIdentifier());