From 26ae244ff062159b33c5b5d79a5bc2512a3f3235 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Tue, 14 Oct 2025 00:50:15 -0500 Subject: [PATCH] internal: Convert UTC times to local timezone in event reminder command - refs BT#23004 --- .../Command/SendEventRemindersCommand.php | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/src/CoreBundle/Command/SendEventRemindersCommand.php b/src/CoreBundle/Command/SendEventRemindersCommand.php index 2390be91329..a0729512c92 100644 --- a/src/CoreBundle/Command/SendEventRemindersCommand.php +++ b/src/CoreBundle/Command/SendEventRemindersCommand.php @@ -92,7 +92,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - $eventDetails = $this->generateEventDetails($event); + // NOTE: keep this call (without user) if you rely on it elsewhere; it does not change behavior. + // We now format per-user inside sendReminderMessage(). + $eventDetails = $this->generateEventDetails($event, null); $initialSentRemindersCount = $sentRemindersCount; @@ -193,6 +195,47 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } + /** + * Resolve the DateTimeZone to use for a given user (CLI-safe, no legacy api_* calls). + * Priority: user's timezone -> platform timezone setting -> UTC. + */ + private function resolveTimezoneForUser(?User $user): DateTimeZone + { + // User explicit timezone (if present and valid) + $tzId = $user?->getTimezone(); + if (\is_string($tzId) && $tzId !== '') { + try { + return new DateTimeZone($tzId); + } catch (\Throwable) { + // keep going + } + } + + // Platform timezone setting (equivalent to api_get_setting('platform.timezone', false, 'timezones')) + $platformTz = (string) ($this->settingsManager->getSetting('platform.timezone', false, 'timezones') ?? ''); + if ($platformTz !== '') { + try { + return new DateTimeZone($platformTz); + } catch (\Throwable) { + // keep going + } + } + + return new DateTimeZone('UTC'); + } + + /** + * Format a UTC DateTime into the user's local timezone, CLI-safe. + */ + private function formatForUser(DateTime $utc, ?User $user): string + { + $dt = (clone $utc); + $dt->setTimezone($this->resolveTimezoneForUser($user)); + + // Keep the existing format as used previously. + return $dt->format('Y-m-d H:i:s'); + } + private function sendReminderMessage(User $user, CCalendarEvent $event, int $senderId, bool $debug, SymfonyStyle $io, int &$sentRemindersCount): void { $locale = $user->getLocale() ?: 'en'; @@ -202,7 +245,9 @@ private function sendReminderMessage(User $user, CCalendarEvent $event, int $sen $this->translator->trans('Reminder for event : %s'), $event->getTitle() ); - $messageContent = implode(PHP_EOL, $this->generateEventDetails($event)); + + // IMPORTANT: build details with user's timezone applied + $messageContent = implode(PHP_EOL, $this->generateEventDetails($event, $user)); $this->messageHelper->sendMessage( $user->getId(), @@ -237,7 +282,11 @@ private function getFirstAdminId(): int : 1; } - private function generateEventDetails(CCalendarEvent $event): array + /** + * Build event details text. If $user is provided, dates are converted to user's local timezone. + * Otherwise, keep UTC (backward compatible path). + */ + private function generateEventDetails(CCalendarEvent $event, ?User $user = null): array { $details = []; $details[] = \sprintf('

%s

', $event->getTitle()); @@ -245,15 +294,23 @@ private function generateEventDetails(CCalendarEvent $event): array if ($event->isAllDay()) { $details[] = \sprintf('

%s

', $this->translator->trans('All day')); } else { + $fromStr = $user + ? $this->formatForUser($event->getStartDate(), $user) + : $event->getStartDate()->format('Y-m-d H:i:s'); + $details[] = \sprintf( '

%s

', - $this->translator->trans('From %s', ['%s' => $event->getStartDate()->format('Y-m-d H:i:s')]) + $this->translator->trans('From %s', ['%s' => $fromStr]) ); if ($event->getEndDate()) { + $untilStr = $user + ? $this->formatForUser($event->getEndDate(), $user) + : $event->getEndDate()->format('Y-m-d H:i:s'); + $details[] = \sprintf( '

%s

', - $this->translator->trans('Until %s', ['%s' => $event->getEndDate()->format('Y-m-d H:i:s')]) + $this->translator->trans('Until %s', ['%s' => $untilStr]) ); } }