@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.

Prepare event dates for EditEngine/API

Summary:
Ref T9275. Currently, the "Start Date", "End Date", and "Recurrence End Date" transcations take a complex value (AphrontFormDateControlValue) and reduce it to an epoch.

Do this a little earlier, since the API will be much more usable if it just passes in epoch timestamps.

Events also have some logic where they rewrite the from date and to date on the actual object for all day events, then undo the changes later. Specifically, if you have an all-day event on "July 24th", the exact start and end times vary based on who is looking at it. Instead of overwriting the persistent `dateFrom` and `dateTo` properties, add separate `viewer` properties to make it easier to keep this stuff straight.

Since this means all-day events get stored in UTC, we need to query/fetch (and then discard) slightly more events. This is perfectly and much simpler to do.

The one weird "UTC" hack in here will get nuked when this moves to EditEngine properly.

Test Plan: Edited times for normal events and all-day events.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9275

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

+131 -147
+1 -2
src/applications/calendar/controller/PhabricatorCalendarEventDragController.php
··· 29 29 30 30 $xactions = array(); 31 31 32 - $duration = $event->getDateTo() - $event->getDateFrom(); 32 + $duration = $event->getDuration(); 33 33 34 34 $start = $request->getInt('start'); 35 35 $start_value = id(AphrontFormDateControlValue::newFromEpoch( ··· 49 49 $xactions[] = id(new PhabricatorCalendarEventTransaction()) 50 50 ->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_END_DATE) 51 51 ->setNewValue($end_value); 52 - 53 52 54 53 $editor = id(new PhabricatorCalendarEventEditor()) 55 54 ->setActor($viewer)
+15 -6
src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
··· 91 91 92 92 $end_value = AphrontFormDateControlValue::newFromEpoch( 93 93 $viewer, 94 - $event->getDateTo()); 94 + $event->getViewerDateTo()); 95 95 $start_value = AphrontFormDateControlValue::newFromEpoch( 96 96 $viewer, 97 - $event->getDateFrom()); 97 + $event->getViewerDateFrom()); 98 98 $recurrence_end_date_value = id(clone $end_value) 99 99 ->setOptional(true); 100 100 ··· 137 137 $view_policy = $event->getViewPolicy(); 138 138 $space = $event->getSpacePHID(); 139 139 140 + 140 141 if ($request->isFormPost()) { 142 + $is_all_day = $request->getStr('isAllDay'); 143 + 144 + if ($is_all_day) { 145 + // TODO: This is a very gross temporary hack to get this working 146 + // reasonably: if this is an all day event, force the viewer's 147 + // timezone to UTC so the date controls get interpreted as UTC. 148 + $viewer->overrideTimezoneIdentifier('UTC'); 149 + } 150 + 141 151 $xactions = array(); 142 152 $name = $request->getStr('name'); 143 153 ··· 159 169 $space = $request->getStr('spacePHID'); 160 170 $is_recurring = $request->getStr('isRecurring') ? 1 : 0; 161 171 $frequency = $request->getStr('frequency'); 162 - $is_all_day = $request->getStr('isAllDay'); 163 172 $icon = $request->getStr('icon'); 164 173 165 174 $invitees = $request->getArr('invitees'); ··· 192 201 $xactions[] = id(new PhabricatorCalendarEventTransaction()) 193 202 ->setTransactionType( 194 203 PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE) 195 - ->setNewValue($recurrence_end_date_value); 204 + ->setNewValue($recurrence_end_date_value->getEpoch()); 196 205 } 197 206 } 198 207 ··· 210 219 $xactions[] = id(new PhabricatorCalendarEventTransaction()) 211 220 ->setTransactionType( 212 221 PhabricatorCalendarEventTransaction::TYPE_START_DATE) 213 - ->setNewValue($start_value); 222 + ->setNewValue($start_value->getEpoch()); 214 223 215 224 $xactions[] = id(new PhabricatorCalendarEventTransaction()) 216 225 ->setTransactionType( 217 226 PhabricatorCalendarEventTransaction::TYPE_END_DATE) 218 - ->setNewValue($end_value); 227 + ->setNewValue($end_value->getEpoch()); 219 228 } 220 229 221 230
+7 -7
src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
··· 198 198 ->setUser($viewer); 199 199 200 200 if ($event->getIsAllDay()) { 201 - $date_start = phabricator_date($event->getDateFrom(), $viewer); 202 - $date_end = phabricator_date($event->getDateTo(), $viewer); 201 + $date_start = phabricator_date($event->getViewerDateFrom(), $viewer); 202 + $date_end = phabricator_date($event->getViewerDateTo(), $viewer); 203 203 204 204 if ($date_start == $date_end) { 205 205 $properties->addProperty( 206 206 pht('Time'), 207 - phabricator_date($event->getDateFrom(), $viewer)); 207 + phabricator_date($event->getViewerDateFrom(), $viewer)); 208 208 } else { 209 209 $properties->addProperty( 210 210 pht('Starts'), 211 - phabricator_date($event->getDateFrom(), $viewer)); 211 + phabricator_date($event->getViewerDateFrom(), $viewer)); 212 212 $properties->addProperty( 213 213 pht('Ends'), 214 - phabricator_date($event->getDateTo(), $viewer)); 214 + phabricator_date($event->getViewerDateTo(), $viewer)); 215 215 } 216 216 } else { 217 217 $properties->addProperty( 218 218 pht('Starts'), 219 - phabricator_datetime($event->getDateFrom(), $viewer)); 219 + phabricator_datetime($event->getViewerDateFrom(), $viewer)); 220 220 221 221 $properties->addProperty( 222 222 pht('Ends'), 223 - phabricator_datetime($event->getDateTo(), $viewer)); 223 + phabricator_datetime($event->getViewerDateTo(), $viewer)); 224 224 } 225 225 226 226 if ($event->getIsRecurring()) {
+6 -19
src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
··· 22 22 array $xactions) { 23 23 24 24 $actor = $this->requireActor(); 25 - $object->removeViewerTimezone($actor); 26 - 27 25 if ($object->getIsStub()) { 28 26 $this->materializeStub($object); 29 27 } ··· 151 149 case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: 152 150 case PhabricatorCalendarEventTransaction::TYPE_START_DATE: 153 151 case PhabricatorCalendarEventTransaction::TYPE_END_DATE: 154 - return $xaction->getNewValue()->getEpoch(); 152 + return $xaction->getNewValue(); 155 153 } 156 154 157 155 return parent::getCustomTransactionNewValue($object, $xaction); ··· 308 306 } 309 307 310 308 if ($phids) { 309 + $object->applyViewerTimezone($this->getActor()); 310 + 311 311 $user = new PhabricatorUser(); 312 312 $conn_w = $user->establishConnection('w'); 313 313 queryfx( ··· 344 344 345 345 foreach ($xactions as $xaction) { 346 346 if ($xaction->getTransactionType() == $start_date_xaction) { 347 - $start_date = $xaction->getNewValue()->getEpoch(); 347 + $start_date = $xaction->getNewValue(); 348 348 } else if ($xaction->getTransactionType() == $end_date_xaction) { 349 - $end_date = $xaction->getNewValue()->getEpoch(); 349 + $end_date = $xaction->getNewValue(); 350 350 } else if ($xaction->getTransactionType() == $recurrence_end_xaction) { 351 351 $recurrence_end = $xaction->getNewValue(); 352 352 } else if ($xaction->getTransactionType() == $is_recurrence_xaction) { 353 353 $is_recurring = $xaction->getNewValue(); 354 354 } 355 355 } 356 + 356 357 if ($start_date > $end_date) { 357 358 $type = PhabricatorCalendarEventTransaction::TYPE_END_DATE; 358 359 $errors[] = new PhabricatorApplicationTransactionValidationError( ··· 397 398 398 399 $error->setIsMissingFieldError(true); 399 400 $errors[] = $error; 400 - } 401 - break; 402 - case PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE: 403 - case PhabricatorCalendarEventTransaction::TYPE_START_DATE: 404 - case PhabricatorCalendarEventTransaction::TYPE_END_DATE: 405 - foreach ($xactions as $xaction) { 406 - $date_value = $xaction->getNewValue(); 407 - if (!$date_value->isValid()) { 408 - $errors[] = new PhabricatorApplicationTransactionValidationError( 409 - $type, 410 - pht('Invalid'), 411 - pht('Invalid date.'), 412 - $xaction); 413 - } 414 401 } 415 402 break; 416 403 }
+17 -12
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 90 90 protected function getPagingValueMap($cursor, array $keys) { 91 91 $event = $this->loadCursorObject($cursor); 92 92 return array( 93 - 'start' => $event->getDateFrom(), 93 + 'start' => $event->getViewerDateFrom(), 94 94 'id' => $event->getID(), 95 95 ); 96 96 } ··· 121 121 foreach ($events as $key => $event) { 122 122 $sequence_start = 0; 123 123 $sequence_end = null; 124 - $duration = $event->getDateTo() - $event->getDateFrom(); 124 + $duration = $event->getDuration(); 125 125 $end = null; 126 126 127 127 $instance_of = $event->getInstanceOfEventPHID(); ··· 137 137 $frequency = $event->getFrequencyUnit(); 138 138 $modify_key = '+1 '.$frequency; 139 139 140 - if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) { 140 + if (($this->rangeBegin !== null) && 141 + ($this->rangeBegin > $event->getViewerDateFrom())) { 141 142 $max_date = $this->rangeBegin - $duration; 142 - $date = $event->getDateFrom(); 143 + $date = $event->getViewerDateFrom(); 143 144 $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); 144 145 145 146 while ($date < $max_date) { ··· 151 152 152 153 $start = $this->rangeBegin; 153 154 } else { 154 - $start = $event->getDateFrom() - $duration; 155 + $start = $event->getViewerDateFrom() - $duration; 155 156 } 156 157 157 158 $date = $start; ··· 199 200 200 201 if ($raw_limit) { 201 202 if (count($events) >= $raw_limit) { 202 - $events = msort($events, 'getDateFrom'); 203 + $events = msort($events, 'getViewerDateFrom'); 203 204 $events = array_slice($events, 0, $raw_limit, true); 204 - $enforced_end = last($events)->getDateFrom(); 205 + $enforced_end = last($events)->getViewerDateFrom(); 205 206 } 206 207 } 207 208 } ··· 303 304 $this->phids); 304 305 } 305 306 307 + // NOTE: The date ranges we query for are larger than the requested ranges 308 + // because we need to catch all-day events. We'll refine this range later 309 + // after adjusting the visible range of events we load. 310 + 306 311 if ($this->rangeBegin) { 307 312 $where[] = qsprintf( 308 313 $conn, 309 314 'event.dateTo >= %d OR event.isRecurring = 1', 310 - $this->rangeBegin); 315 + $this->rangeBegin - phutil_units('16 hours in seconds')); 311 316 } 312 317 313 318 if ($this->rangeEnd) { 314 319 $where[] = qsprintf( 315 320 $conn, 316 321 'event.dateFrom <= %d', 317 - $this->rangeEnd); 322 + $this->rangeEnd + phutil_units('16 hours in seconds')); 318 323 } 319 324 320 325 if ($this->inviteePHIDs !== null) { ··· 399 404 $viewer = $this->getViewer(); 400 405 401 406 foreach ($events as $key => $event) { 402 - $event_start = $event->getDateFrom(); 403 - $event_end = $event->getDateTo(); 407 + $event_start = $event->getViewerDateFrom(); 408 + $event_end = $event->getViewerDateTo(); 404 409 405 410 if ($range_start && $event_end < $range_start) { 406 411 unset($events[$key]); ··· 466 471 } 467 472 } 468 473 469 - $events = msort($events, 'getDateFrom'); 474 + $events = msort($events, 'getViewerDateFrom'); 470 475 471 476 return $events; 472 477 }
+17 -14
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 311 311 $item->addAttribute($attending); 312 312 } 313 313 314 - if (strlen($event->getDuration()) > 0) { 314 + if ($event->getDuration()) { 315 315 $duration = pht( 316 316 'Duration: %s', 317 - $event->getDuration()); 318 - 317 + $event->getDisplayDuration()); 319 318 $item->addIcon('none', $duration); 320 319 } 321 320 ··· 370 369 $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); 371 370 372 371 $event = new AphrontCalendarEventView(); 373 - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); 372 + $event->setEpochRange( 373 + $status->getViewerDateFrom(), 374 + $status->getViewerDateTo()); 374 375 $event->setIsAllDay($status->getIsAllDay()); 375 376 $event->setIcon($status->getIcon()); 376 377 ··· 434 435 $event = new AphrontCalendarEventView(); 435 436 $event->setCanEdit($can_edit); 436 437 $event->setEventID($status->getID()); 437 - $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); 438 + $event->setEpochRange( 439 + $status->getViewerDateFrom(), 440 + $status->getViewerDateTo()); 438 441 $event->setIsAllDay($status->getIsAllDay()); 439 442 $event->setIcon($status->getIcon()); 440 443 $event->setViewerIsInvited($viewer_is_invited); ··· 553 556 $viewer = $this->requireViewer(); 554 557 555 558 $from_datetime = PhabricatorTime::getDateTimeFromEpoch( 556 - $event->getDateFrom(), 559 + $event->getViewerDateFrom(), 557 560 $viewer); 558 561 $to_datetime = PhabricatorTime::getDateTimeFromEpoch( 559 - $event->getDateTo(), 562 + $event->getViewerDateTo(), 560 563 $viewer); 561 564 562 565 $from_date_formatted = $from_datetime->format('Y m d'); ··· 566 569 if ($from_date_formatted == $to_date_formatted) { 567 570 return pht( 568 571 '%s, All Day', 569 - phabricator_date($event->getDateFrom(), $viewer)); 572 + phabricator_date($event->getViewerDateFrom(), $viewer)); 570 573 } else { 571 574 return pht( 572 575 '%s - %s, All Day', 573 - phabricator_date($event->getDateFrom(), $viewer), 574 - phabricator_date($event->getDateTo(), $viewer)); 576 + phabricator_date($event->getViewerDateFrom(), $viewer), 577 + phabricator_date($event->getViewerDateTo(), $viewer)); 575 578 } 576 579 } else if ($from_date_formatted == $to_date_formatted) { 577 580 return pht( 578 581 '%s - %s', 579 - phabricator_datetime($event->getDateFrom(), $viewer), 580 - phabricator_time($event->getDateTo(), $viewer)); 582 + phabricator_datetime($event->getViewerDateFrom(), $viewer), 583 + phabricator_time($event->getViewerDateTo(), $viewer)); 581 584 } else { 582 585 return pht( 583 586 '%s - %s', 584 - phabricator_datetime($event->getDateFrom(), $viewer), 585 - phabricator_datetime($event->getDateTo(), $viewer)); 587 + phabricator_datetime($event->getViewerDateFrom(), $viewer), 588 + phabricator_datetime($event->getViewerDateTo(), $viewer)); 586 589 } 587 590 } 588 591 }
+38 -67
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 41 41 42 42 private $parentEvent = self::ATTACHABLE; 43 43 private $invitees = self::ATTACHABLE; 44 - private $appliedViewer; 44 + 45 + private $viewerDateFrom; 46 + private $viewerDateTo; 45 47 46 48 // Frequency Constants 47 49 const FREQUENCY_DAILY = 'daily'; ··· 157 159 $date_time->modify($modify_key); 158 160 $date = $date_time->format('U'); 159 161 160 - $duration = $parent->getDateTo() - $parent->getDateFrom(); 162 + $duration = $this->getDuration(); 161 163 162 164 $this 163 165 ->setDateFrom($date) ··· 192 194 return $ghost; 193 195 } 194 196 195 - public function applyViewerTimezone(PhabricatorUser $viewer) { 196 - if ($this->appliedViewer) { 197 - throw new Exception(pht('Viewer timezone is already applied!')); 197 + public function getViewerDateFrom() { 198 + if ($this->viewerDateFrom === null) { 199 + throw new PhutilInvalidStateException('applyViewerTimezone'); 198 200 } 199 201 200 - $this->appliedViewer = $viewer; 202 + return $this->viewerDateFrom; 203 + } 201 204 202 - if (!$this->getIsAllDay()) { 203 - return $this; 205 + public function getViewerDateTo() { 206 + if ($this->viewerDateTo === null) { 207 + throw new PhutilInvalidStateException('applyViewerTimezone'); 204 208 } 205 209 206 - $zone = $viewer->getTimeZone(); 210 + return $this->viewerDateTo; 211 + } 207 212 213 + public function applyViewerTimezone(PhabricatorUser $viewer) { 214 + if (!$this->getIsAllDay()) { 215 + $this->viewerDateFrom = $this->getDateFrom(); 216 + $this->viewerDateTo = $this->getDateTo(); 217 + } else { 218 + $zone = $viewer->getTimeZone(); 208 219 209 - $this->setDateFrom( 210 - $this->getDateEpochForTimeZone( 220 + $this->viewerDateFrom = $this->getDateEpochForTimeZone( 211 221 $this->getDateFrom(), 212 - new DateTimeZone('Pacific/Kiritimati'), 222 + new DateTimeZone('UTC'), 213 223 'Y-m-d', 214 224 null, 215 - $zone)); 225 + $zone); 216 226 217 - $this->setDateTo( 218 - $this->getDateEpochForTimeZone( 227 + $this->viewerDateTo = $this->getDateEpochForTimeZone( 219 228 $this->getDateTo(), 220 - new DateTimeZone('Pacific/Midway'), 229 + new DateTimeZone('UTC'), 221 230 'Y-m-d 23:59:00', 222 - '-1 day', 223 - $zone)); 231 + null, 232 + $zone); 233 + } 224 234 225 235 return $this; 226 236 } 227 237 228 - 229 - public function removeViewerTimezone(PhabricatorUser $viewer) { 230 - if (!$this->appliedViewer) { 231 - throw new Exception(pht('Viewer timezone is not applied!')); 232 - } 233 - 234 - if ($viewer->getPHID() != $this->appliedViewer->getPHID()) { 235 - throw new Exception(pht('Removed viewer must match applied viewer!')); 236 - } 237 - 238 - $this->appliedViewer = null; 239 - 240 - if (!$this->getIsAllDay()) { 241 - return $this; 242 - } 243 - 244 - $zone = $viewer->getTimeZone(); 245 - 246 - $this->setDateFrom( 247 - $this->getDateEpochForTimeZone( 248 - $this->getDateFrom(), 249 - $zone, 250 - 'Y-m-d', 251 - null, 252 - new DateTimeZone('Pacific/Kiritimati'))); 253 - 254 - $this->setDateTo( 255 - $this->getDateEpochForTimeZone( 256 - $this->getDateTo(), 257 - $zone, 258 - 'Y-m-d', 259 - '+1 day', 260 - new DateTimeZone('Pacific/Midway'))); 261 - 262 - return $this; 238 + public function getDuration() { 239 + return $this->getDateTo() - $this->getDateFrom(); 263 240 } 264 241 265 242 private function getDateEpochForTimeZone( ··· 281 258 } 282 259 283 260 public function save() { 284 - if ($this->appliedViewer) { 285 - throw new Exception( 286 - pht( 287 - 'Can not save event with viewer timezone still applied!')); 288 - } 289 - 290 261 if (!$this->mailKey) { 291 262 $this->mailKey = Filesystem::readRandomCharacters(20); 292 263 } ··· 298 269 * Get the event start epoch for evaluating invitee availability. 299 270 * 300 271 * When assessing availability, we pretend events start earlier than they 301 - * really. This allows us to mark users away for the entire duration of a 272 + * really do. This allows us to mark users away for the entire duration of a 302 273 * series of back-to-back meetings, even if they don't strictly overlap. 303 274 * 304 275 * @return int Event start date for availability caches. 305 276 */ 306 277 public function getDateFromForCache() { 307 - return ($this->getDateFrom() - phutil_units('15 minutes in seconds')); 278 + return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds')); 308 279 } 309 280 310 281 protected function getConfiguration() { ··· 456 427 return false; 457 428 } 458 429 459 - public function getDuration() { 460 - $seconds = $this->dateTo - $this->dateFrom; 430 + public function getDisplayDuration() { 431 + $seconds = $this->getDuration(); 461 432 $minutes = round($seconds / 60, 1); 462 433 $hours = round($minutes / 60, 3); 463 434 $days = round($hours / 24, 2); ··· 470 441 round($days, 1)); 471 442 } else if ($hours >= 1) { 472 443 return pht( 473 - '%s hour(s)', 474 - round($hours, 1)); 444 + '%s hour(s)', 445 + round($hours, 1)); 475 446 } else if ($minutes >= 1) { 476 447 return pht( 477 - '%s minute(s)', 478 - round($minutes, 0)); 448 + '%s minute(s)', 449 + round($minutes, 0)); 479 450 } 480 451 } 481 452
+22 -19
src/applications/people/controller/PhabricatorPeopleProfileViewController.php
··· 189 189 $range_start = $midnight->format('U'); 190 190 $range_end = $week_end->format('U'); 191 191 192 - $query = id(new PhabricatorCalendarEventQuery()) 192 + $events = id(new PhabricatorCalendarEventQuery()) 193 193 ->setViewer($viewer) 194 194 ->withDateRange($range_start, $range_end) 195 195 ->withInvitedPHIDs(array($user->getPHID())) 196 - ->withIsCancelled(false); 196 + ->withIsCancelled(false) 197 + ->execute(); 197 198 198 - $statuses = $query->execute(); 199 - $phids = mpull($statuses, 'getUserPHID'); 200 - $events = array(); 201 - 202 - foreach ($statuses as $status) { 203 - $viewer_is_invited = $status->getIsUserInvited($user->getPHID()); 199 + $event_views = array(); 200 + foreach ($events as $event) { 201 + $viewer_is_invited = $event->getIsUserInvited($viewer->getPHID()); 204 202 205 203 $can_edit = PhabricatorPolicyFilter::hasCapability( 206 204 $viewer, 207 - $status, 205 + $event, 208 206 PhabricatorPolicyCapability::CAN_EDIT); 209 207 210 - $event = id(new AphrontCalendarEventView()) 208 + $epoch_min = $event->getViewerDateFrom(); 209 + $epoch_max = $event->getViewerDateTo(); 210 + 211 + $event_view = id(new AphrontCalendarEventView()) 211 212 ->setCanEdit($can_edit) 212 - ->setEventID($status->getID()) 213 - ->setEpochRange($status->getDateFrom(), $status->getDateTo()) 214 - ->setIsAllDay($status->getIsAllDay()) 215 - ->setIcon($status->getIcon()) 213 + ->setEventID($event->getID()) 214 + ->setEpochRange($epoch_min, $epoch_max) 215 + ->setIsAllDay($event->getIsAllDay()) 216 + ->setIcon($event->getIcon()) 216 217 ->setViewerIsInvited($viewer_is_invited) 217 - ->setName($status->getName()) 218 - ->setURI($status->getURI()); 219 - $events[] = $event; 218 + ->setName($event->getName()) 219 + ->setURI($event->getURI()); 220 + 221 + $event_views[] = $event_view; 220 222 } 221 223 222 - $events = msort($events, 'getEpochStart'); 224 + $event_views = msort($event_views, 'getEpochStart'); 225 + 223 226 $day_view = id(new PHUICalendarWeekView()) 224 227 ->setViewer($viewer) 225 228 ->setView('week') 226 - ->setEvents($events) 229 + ->setEvents($event_views) 227 230 ->setWeekLength(3) 228 231 ->render(); 229 232
+8 -1
src/applications/people/query/PhabricatorPeopleQuery.php
··· 413 413 foreach ($rebuild as $phid => $user) { 414 414 $events = idx($map, $phid, array()); 415 415 416 + // We loaded events with the omnipotent user, but want to shift them 417 + // into the user's timezone before building the cache because they will 418 + // be unavailable during their own local day. 419 + foreach ($events as $event) { 420 + $event->applyViewerTimezone($user); 421 + } 422 + 416 423 $cursor = $min_range; 417 424 if ($events) { 418 425 // Find the next time when the user has no meetings. If we move forward ··· 420 427 while (true) { 421 428 foreach ($events as $event) { 422 429 $from = $event->getDateFromForCache(); 423 - $to = $event->getDateTo(); 430 + $to = $event->getViewerDateTo(); 424 431 if (($from <= $cursor) && ($to > $cursor)) { 425 432 $cursor = $to; 426 433 continue 2;