diff --git a/.env.example b/.env.example
index b7c73320d..792fe0262 100644
--- a/.env.example
+++ b/.env.example
@@ -4,6 +4,8 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_DOMAIN=localhost
+APP_TIMEZONE=UTC
+DISPLAY_TIMEZONE=America/Sao_Paulo
NIGHTWATCH_TOKEN=
LOG_CHANNEL=stack
diff --git a/app-modules/bot-discord/src/Events/GreetingsEvent.php b/app-modules/bot-discord/src/Events/GreetingsEvent.php
index d95f3f72d..6cb53645c 100644
--- a/app-modules/bot-discord/src/Events/GreetingsEvent.php
+++ b/app-modules/bot-discord/src/Events/GreetingsEvent.php
@@ -27,7 +27,7 @@ public function handle(Message $message, Discord $discord): void
return;
}
- $hour = now()->hour;
+ $hour = now()->timezone(config('app.display_timezone'))->hour;
$payload = mb_strtolower($message->content);
$response = match (true) {
diff --git a/app-modules/he4rt/resources/views/components/dashboard/heatmap.blade.php b/app-modules/he4rt/resources/views/components/dashboard/heatmap.blade.php
index 3b6f76382..ec68053e2 100644
--- a/app-modules/he4rt/resources/views/components/dashboard/heatmap.blade.php
+++ b/app-modules/he4rt/resources/views/components/dashboard/heatmap.blade.php
@@ -152,8 +152,8 @@
};
// Now indicator
- $nowRow = $highlightNow && $isWeek ? (int) now()->format('N') - 1 : -1;
- $nowCol = $highlightNow && $isWeek ? (int) now()->format('G') : -1;
+ $nowRow = $highlightNow && $isWeek ? (int) now(config('app.display_timezone'))->format('N') - 1 : -1;
+ $nowCol = $highlightNow && $isWeek ? (int) now(config('app.display_timezone'))->format('G') : -1;
// Auto insight
$autoHeadline = $insightHeadline;
diff --git a/app-modules/he4rt/resources/views/components/schedule-card.blade.php b/app-modules/he4rt/resources/views/components/schedule-card.blade.php
index f2a8870e5..ad68b1bb5 100644
--- a/app-modules/he4rt/resources/views/components/schedule-card.blade.php
+++ b/app-modules/he4rt/resources/views/components/schedule-card.blade.php
@@ -1,15 +1,12 @@
-@props([
- 'startsAt',
- 'endsAt',
- 'title',
- 'icon',
- 'speakers',
-])
+@props (['startsAt', 'endsAt', 'title', 'icon', 'speakers'])
@php
/** @var \Carbon\Carbon $startsAt */
/** @var \Carbon\Carbon $endsAt */
+ $startsAt = $startsAt->timezone(config('app.display_timezone'));
+ $endsAt = $endsAt->timezone(config('app.display_timezone'));
+
$config = match (true) {
$endsAt->isPast() => [
'color' => 'text-green-300',
@@ -51,7 +48,7 @@
@if ($speakers)
- // {{ $speakers->map(fn ($speaker) => $speaker->name)->implode(', ') }} \\
+ // {{ $speakers->map(fn($speaker) => $speaker->name)->implode(', ') }} \\
@endif
diff --git a/app-modules/integration-discord/src/ETL/Console/BackfillVoiceLogsCommand.php b/app-modules/integration-discord/src/ETL/Console/BackfillVoiceLogsCommand.php
index 771e1a18e..40715104c 100644
--- a/app-modules/integration-discord/src/ETL/Console/BackfillVoiceLogsCommand.php
+++ b/app-modules/integration-discord/src/ETL/Console/BackfillVoiceLogsCommand.php
@@ -143,7 +143,7 @@ public function handle(
foreach ($messages as $message) {
$timestamp = CarbonImmutable::parse($message['timestamp']);
- $oldestTimestamp = $timestamp->format('Y-m-d H:i');
+ $oldestTimestamp = $timestamp->timezone(config('app.display_timezone'))->format('Y-m-d H:i');
if ($timestamp->isBefore($since)) {
$reachedSince = true;
@@ -181,7 +181,7 @@ public function handle(
$this->alreadyExistsCount++;
$logger->line(sprintf(
'%s <@%s> %s #%s [EXISTS]',
- $timestamp->format('m/d H:i'),
+ $timestamp->timezone(config('app.display_timezone'))->format('m/d H:i'),
$voiceDto->userDiscordId,
$voiceDto->action,
$channelName,
@@ -201,7 +201,7 @@ public function handle(
$logger->line(sprintf(
'%s <@%s> %s #%s [NEW]',
- $timestamp->format('m/d H:i'),
+ $timestamp->timezone(config('app.display_timezone'))->format('m/d H:i'),
$voiceDto->userDiscordId,
$voiceDto->action,
$channelName,
diff --git a/app-modules/panel-admin/resources/views/moderation/appeal-queue/appeal-detail.blade.php b/app-modules/panel-admin/resources/views/moderation/appeal-queue/appeal-detail.blade.php
index 5933e9079..25a3d2586 100644
--- a/app-modules/panel-admin/resources/views/moderation/appeal-queue/appeal-detail.blade.php
+++ b/app-modules/panel-admin/resources/views/moderation/appeal-queue/appeal-detail.blade.php
@@ -86,7 +86,11 @@ class="mb-4 flex items-start gap-2.5 rounded-lg border border-red-300/40 bg-red-
}}
- {{ __('panel-admin::moderation.appeal_queue.detail.sla_deadline') }}: {{ $appeal->sla_deadline->format('M d, Y H:i') }}
+ {{ __('panel-admin::moderation.appeal_queue.detail.sla_deadline') }}: {{
+ $appeal->sla_deadline
+ ->timezone(config('app.display_timezone'))
+ ->format('M d, Y H:i')
+ }}
@@ -381,7 +385,11 @@ class="mb-3 flex items-center gap-2 text-[11px] font-semibold tracking-wider tex
'text-zinc-700 dark:text-zinc-300' => !$isOverdue
])
>
- {{ $appeal->sla_deadline?->format('M d, Y H:i') ?? '—' }}
+ {{
+ $appeal->sla_deadline
+ ?->timezone(config('app.display_timezone'))
+ ->format('M d, Y H:i') ?? '—'
+ }}
@if ($appeal->sla_deadline && !$appeal->status->isResolved())
*/
public function get(): Collection
{
- $start = Date::now('America/Sao_Paulo')->subDays($this->rangeDays)->startOfDay()->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::now($tz)->subDays($this->rangeDays)->startOfDay()->utc();
return DB::table('messages')
- ->selectRaw("(sent_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo')::date AS day")
+ ->selectRaw('(sent_at AT TIME ZONE ?)::date AS day', [$tz])
->selectRaw('COUNT(*) AS total_messages')
->selectRaw('COUNT(DISTINCT external_identity_id) AS unique_users')
->where('sent_at', '>=', $start)
diff --git a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php
index 732afb570..aff6633e4 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php
@@ -16,11 +16,12 @@ public function __construct(
/** @return array
*/
public function get(): array
{
- $start = Date::now('America/Sao_Paulo')->subDays($this->rangeDays)->startOfDay()->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::now($tz)->subDays($this->rangeDays)->startOfDay()->utc();
return DB::table('messages')
- ->selectRaw("EXTRACT(DOW FROM sent_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo')::int AS dow")
- ->selectRaw("EXTRACT(HOUR FROM sent_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo')::int AS hour")
+ ->selectRaw('EXTRACT(DOW FROM sent_at AT TIME ZONE ?)::int AS dow', [$tz])
+ ->selectRaw('EXTRACT(HOUR FROM sent_at AT TIME ZONE ?)::int AS hour', [$tz])
->selectRaw('COUNT(*) AS total')
->where('sent_at', '>=', $start)
->whereNotNull('sent_at')
diff --git a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php
index 053c02400..fbd16c240 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php
@@ -182,7 +182,7 @@ private function queryVoiceStats(array $subdivisions): array
private function subdivisions(): array
{
return once(function (): array {
- $now = Date::now('America/Sao_Paulo');
+ $now = Date::now(config('app.display_timezone'));
[$blockCount, $blockSize, $unit] = match (true) {
$this->rangeDays >= 90 => [3, 30, 'days'],
diff --git a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php
index e503b4584..f4791aa7a 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php
@@ -17,7 +17,8 @@ public function __construct(
/** @return Collection */
public function get(): Collection
{
- $start = Date::now('America/Sao_Paulo')->subDays($this->rangeDays)->startOfDay()->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::now($tz)->subDays($this->rangeDays)->startOfDay()->utc();
return DB::table('messages')
->select('channel_id')
diff --git a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php
index c25b1d2b3..d7b770976 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php
@@ -16,11 +16,12 @@ public function __construct(
/** @return array */
public function get(): array
{
- $start = Date::now('America/Sao_Paulo')->subDays($this->rangeDays)->startOfDay()->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::now($tz)->subDays($this->rangeDays)->startOfDay()->utc();
return DB::table('voice_messages')
- ->selectRaw("EXTRACT(DOW FROM occurred_at AT TIME ZONE 'America/Sao_Paulo')::int AS dow")
- ->selectRaw("EXTRACT(HOUR FROM occurred_at AT TIME ZONE 'America/Sao_Paulo')::int AS hour")
+ ->selectRaw('EXTRACT(DOW FROM occurred_at AT TIME ZONE ?)::int AS dow', [$tz])
+ ->selectRaw('EXTRACT(HOUR FROM occurred_at AT TIME ZONE ?)::int AS hour', [$tz])
->selectRaw('COUNT(*) AS total')
->where('occurred_at', '>=', $start)
->whereNotNull('occurred_at')
diff --git a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php
index f1a854be3..9cf832b5d 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php
@@ -17,10 +17,11 @@ public function __construct(
/** @return Collection */
public function get(): Collection
{
- $start = Date::now('America/Sao_Paulo')->subDays($this->rangeDays)->startOfDay()->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::now($tz)->subDays($this->rangeDays)->startOfDay()->utc();
return DB::table('voice_messages')
- ->selectRaw("(occurred_at AT TIME ZONE 'America/Sao_Paulo')::date AS day")
+ ->selectRaw('(occurred_at AT TIME ZONE ?)::date AS day', [$tz])
->selectRaw('COUNT(*) AS total_joins')
->where('occurred_at', '>=', $start)
->whereNotNull('occurred_at')
diff --git a/app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php b/app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
index 816ff04f9..123a5a0e0 100644
--- a/app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
+++ b/app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
@@ -55,8 +55,9 @@ public function loadParticipants(): void
return;
}
- $start = Date::parse($this->startDate, 'America/Sao_Paulo')->utc();
- $end = Date::parse($this->endDate, 'America/Sao_Paulo')->utc();
+ $tz = config('app.display_timezone');
+ $start = Date::parse($this->startDate, $tz)->utc();
+ $end = Date::parse($this->endDate, $tz)->utc();
$messageStats = Message::query()
->where('channel_id', $this->channelId)
diff --git a/app-modules/panel-admin/src/Moderation/Livewire/ModerationDashboardLivewire.php b/app-modules/panel-admin/src/Moderation/Livewire/ModerationDashboardLivewire.php
index 60753438c..560a2baae 100644
--- a/app-modules/panel-admin/src/Moderation/Livewire/ModerationDashboardLivewire.php
+++ b/app-modules/panel-admin/src/Moderation/Livewire/ModerationDashboardLivewire.php
@@ -346,10 +346,14 @@ public function repeatOffenders(): Collection
#[Computed]
public function activityHeatmap(): array
{
+ $tz = config('app.display_timezone');
+
$dbData = DB::table('moderation_actions')
->where('created_at', '>=', now()->subDays(30))
- ->selectRaw('EXTRACT(DOW FROM created_at) as dow, EXTRACT(HOUR FROM created_at) as hour, count(*) as total')
- ->groupByRaw('EXTRACT(DOW FROM created_at), EXTRACT(HOUR FROM created_at)')
+ ->selectRaw('EXTRACT(DOW FROM created_at AT TIME ZONE ?)::int AS dow', [$tz])
+ ->selectRaw('EXTRACT(HOUR FROM created_at AT TIME ZONE ?)::int AS hour', [$tz])
+ ->selectRaw('COUNT(*) AS total')
+ ->groupBy('dow', 'hour')
->get();
$grid = array_fill(0, 7, array_fill(0, 24, 0));
diff --git a/config/app.php b/config/app.php
index c7c3d80b7..5310b707c 100644
--- a/config/app.php
+++ b/config/app.php
@@ -69,7 +69,9 @@
|
*/
- 'timezone' => env('APP_TIMEZONE', 'America/Sao_Paulo'),
+ 'timezone' => env('APP_TIMEZONE', 'UTC'),
+
+ 'display_timezone' => env('DISPLAY_TIMEZONE', 'America/Sao_Paulo'),
/*
|--------------------------------------------------------------------------
diff --git a/database/migrations/2026_05_20_160249_alter_messages_sent_at_to_timestamptz.php b/database/migrations/2026_05_20_160249_alter_messages_sent_at_to_timestamptz.php
new file mode 100644
index 000000000..c5a07a33a
--- /dev/null
+++ b/database/migrations/2026_05_20_160249_alter_messages_sent_at_to_timestamptz.php
@@ -0,0 +1,14 @@
+
@foreach ($supportedProviders as $provider)
@php
- $connectedProvider = $userProviders->filter(fn (ExternalIdentity $connection) => $connection->provider == $provider->value)->first();
+ $connectedProvider = $userProviders
+ ->filter(fn(ExternalIdentity $connection) => $connection->provider == $provider->value)
+ ->first();
@endphp
-
@@ -30,13 +28,15 @@
Connected
@endif
-
- {{ $provider->getDescription() }}
-
+
{{ $provider->getDescription() }}
@if ($connectedProvider)
Connected at
- {{ $connectedProvider->updated_at->format('d/m/Y H:i:s') }}
+ {{
+ $connectedProvider->updated_at
+ ->timezone(config('app.display_timezone'))
+ ->format('d/m/Y H:i:s')
+ }}
@else
Nessa autenticação iremos pedir acesso à:
@foreach ($provider->getScopes() as $scope)
@@ -48,7 +48,7 @@
{{ $connectedProvider ? 'Disconnect' : 'Connect' }}
diff --git a/routes/console.php b/routes/console.php
index b439472c3..d610bd88b 100644
--- a/routes/console.php
+++ b/routes/console.php
@@ -23,4 +23,5 @@
Schedule::command('backup:monitor')
->when(fn () => config('backup.enabled'))
->daily()
- ->at('09:00');
+ ->at('09:00')
+ ->timezone(config('app.display_timezone'));