Skip to content

Commit

Permalink
Merge pull request #2972 from nextcloud/enh/timezone
Browse files Browse the repository at this point in the history
Move and shift dates respecting client's timezone
  • Loading branch information
dartcafe authored Jul 2, 2023
2 parents 4b73eca + dd326d7 commit 6317bc1
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 21 deletions.
2 changes: 1 addition & 1 deletion lib/Controller/OptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,6 @@ public function shift(int $pollId, int $step, string $unit): JSONResponse {
* @NoAdminRequired
*/
public function findCalendarEvents(int $optionId, string $tz): JSONResponse {
return $this->response(fn () => ['events' => $this->calendarService->getEvents($optionId, $tz)]);
return $this->response(fn () => ['events' => $this->calendarService->getEvents($optionId)]);
}
}
4 changes: 4 additions & 0 deletions lib/Db/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ public function getPollOptionText(): string {
return htmlspecialchars_decode($this->pollOptionText);
}

public function updatePollOptionText(): void {
$this->setPollOptionText($this->getPollOptionText());
}

public function getPollOptionTextEnd(): string {
if ($this->getTimestamp()) {
return date('c', $this->getTimestamp() + $this->getDuration());
Expand Down
10 changes: 9 additions & 1 deletion lib/Db/OptionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ public function __construct(
}

public function update(Entity $entity): Entity {
$entity->updatePollOptionText();
if ($entity->getTimestamp() > 0) {
$entity->setOrder($entity->getTimestamp());
}
$entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp()));
return parent::update($entity);
}

public function insert(Entity $entity): Entity {
$entity->updatePollOptionText();
if ($entity->getTimestamp() > 0) {
$entity->setOrder($entity->getTimestamp());
}
$entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp()));
return parent::insert($entity);
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Middleware/RequestAttributesMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class RequestAttributesMiddleware extends Middleware {
private const CLIENT_ID_KEY = 'Nc-Polls-Client-Id';
private const TIME_ZONE_KEY = 'Nc-Polls-Client-Time-Zone';

public function __construct(
protected IRequest $request,
Expand All @@ -17,6 +18,7 @@ public function __construct(

public function beforeController($controller, $methodName): void {
$clientId = $this->request->getHeader(self::CLIENT_ID_KEY);
$clientTimeZone = $this->request->getHeader(self::TIME_ZONE_KEY);

if (!$clientId) {
$clientId = $this->session->getId();
Expand All @@ -25,5 +27,8 @@ public function beforeController($controller, $methodName): void {
if ($clientId) {
$this->session->set('ncPollsClientId', $clientId);
}
if ($clientTimeZone) {
$this->session->set('ncPollsClientTimeZone', $clientTimeZone);
}
}
}
6 changes: 4 additions & 2 deletions lib/Service/CalendarService.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use OCA\Polls\Model\User\CurrentUser;
use OCP\Calendar\ICalendar;
use OCP\Calendar\IManager as CalendarManager;
use OCP\ISession;

class CalendarService {
/** @var ICalendar[] */
Expand All @@ -43,6 +44,7 @@ class CalendarService {

public function __construct(
private CalendarManager $calendarManager,
private ISession $session,
private PreferencesService $preferencesService,
private OptionMapper $optionMapper,
private CurrentUser $currentUser,
Expand Down Expand Up @@ -112,8 +114,8 @@ private function searchEventsByTimeRange(DateTimeImmutable $from, DateTimeImmuta
*
* @psalm-return list<CalendarEvent|null>
*/
public function getEvents(int $optionId, string $tz): array {
$timezone = new DateTimeZone($tz);
public function getEvents(int $optionId): array {
$timezone = new DateTimeZone($this->session->get('ncPollsClientTimeZone'));
$timerange = $this->getTimerange($optionId, $timezone);

$events = [];
Expand Down
54 changes: 37 additions & 17 deletions lib/Service/OptionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace OCA\Polls\Service;

use DateTime;
use DateTimeZone;
use OCA\Polls\Db\Option;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\Poll;
Expand All @@ -43,6 +44,7 @@
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ISession;
use Psr\Log\LoggerInterface;

class OptionService {
Expand All @@ -60,6 +62,8 @@ public function __construct(
private Option $option,
private OptionMapper $optionMapper,
private VoteMapper $voteMapper,
private ISession $session,
private SystemService $systemService,
) {
$this->options = [];
$this->votes = [];
Expand Down Expand Up @@ -246,6 +250,26 @@ public function confirm(int $optionId): Option {
return $this->option;
}

private function getModifiedDateOption(Option $option, DateTimeZone $timeZone, int $step, string $unit) {
$from = (new DateTime())
->setTimestamp($option->getTimestamp())
->setTimezone($timeZone)
->modify($step . ' ' . $unit);
$to = (new DateTime())
->setTimestamp($option->getTimestamp() + $option->getDuration())
->setTimezone($timeZone)
->modify($step . ' ' . $unit);
return [
'from' => $from,
'to' => $to,
'duration' => $to->getTimestamp() - $from->getTimestamp(),
];
}

private function cloneOption() {
return clone $this->option;
}

/**
* Make a sequence of date poll options
*
Expand All @@ -256,6 +280,7 @@ public function confirm(int $optionId): Option {
public function sequence(int $optionId, int $step, string $unit, int $amount): array {
$this->option = $this->optionMapper->find($optionId);
$this->acl->setPollId($this->option->getPollId(), Acl::PERMISSION_POLL_EDIT);
$timezone = new DateTimeZone($this->session->get('ncPollsClientTimeZone'));

if ($this->acl->getPoll()->getType() !== Poll::TYPE_DATE) {
throw new InvalidPollTypeException('Sequences are only available in date polls');
Expand All @@ -265,25 +290,19 @@ public function sequence(int $optionId, int $step, string $unit, int $amount): a
return $this->optionMapper->findByPoll($this->acl->getPollId());
}

$baseDate = new DateTime;
$baseDate->setTimestamp($this->option->getTimestamp());

for ($i = 0; $i < $amount; $i++) {
for ($i = 1; $i < ($amount + 1) ; $i++) {
$clonedOption = new Option();
$clonedOption->setPollId($this->acl->getPollId());
$clonedOption->setDuration($this->option->getDuration());
$clonedOption->setPollId($this->option->getPollId());
$clonedOption->setConfirmed(0);
$clonedOption->setTimestamp($baseDate->modify($step . ' ' . $unit)->getTimestamp());
$clonedOption->setOrder($clonedOption->getTimestamp());
$clonedOption->setPollOptionText($baseDate->format('c'));

$newDates = $this->getModifiedDateOption($this->option, $timezone, ($step * $i), $unit);
$clonedOption->setTimestamp($newDates['from']->getTimestamp());
$clonedOption->setDuration($newDates['duration']);

try {
$this->optionMapper->insert($clonedOption);
} catch (Exception $e) {
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
$this->logger->warning('skip adding ' . $baseDate->format('c') . 'for pollId' . $this->option->getPollId() . '. Option already exists.');
}
throw $e;
$this->logger->warning('skip adding ' . $newDates['from']->format('c') . 'for pollId ' . $this->option->getPollId() . '. Option already exists.');
}
}

Expand All @@ -301,6 +320,7 @@ public function sequence(int $optionId, int $step, string $unit, int $amount): a
*/
public function shift(int $pollId, int $step, string $unit): array {
$this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT);
$timezone = new DateTimeZone($this->session->get('ncPollsClientTimeZone'));

if ($this->acl->getPoll()->getType() !== Poll::TYPE_DATE) {
throw new InvalidPollTypeException('Shifting is only available in date polls');
Expand All @@ -309,15 +329,15 @@ public function shift(int $pollId, int $step, string $unit): array {
$this->options = $this->optionMapper->findByPoll($this->acl->getPollId());

if ($step > 0) {
// start from last item if moving option into the future
// avoid UniqueConstraintViolationException
// start from last item
$this->options = array_reverse($this->options);
}

$shiftedDate = new DateTime;
foreach ($this->options as $option) {
$shiftedDate->setTimestamp($option->getTimestamp());
$option->setTimestamp($shiftedDate->modify($step . ' ' . $unit)->getTimestamp());
$newDates = $this->getModifiedDateOption($option, $timezone, $step, $unit);
$option->setTimestamp($newDates['from']->getTimestamp());
$option->setDuration($newDates['duration']);
$this->optionMapper->update($option);
}

Expand Down
1 change: 1 addition & 0 deletions src/js/Api/HttpApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const axiosConfig = {
headers: {
Accept: 'application/json',
'Nc-Polls-Client-Id': clientSessionId,
'Nc-Polls-Client-Time-Zone': Intl.DateTimeFormat().resolvedOptions().timeZone,
},
}

Expand Down

0 comments on commit 6317bc1

Please sign in to comment.