From 5feaa8a36a1177f2254e6655a25a8992a80d1b07 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Thu, 18 Jun 2026 12:40:19 +0000 Subject: [PATCH] Zeitzone Europe/Berlin: unnoetige UTC-Konvertierung entfernt Beim Speichern/Veroeffentlichen waren Termine 2 Stunden versetzt: die App-Zeitzone stand auf UTC, Termine wurden nach UTC umgerechnet, aber published_at roh angezeigt. - config/app.php: timezone UTC -> Europe/Berlin - PressRelease: DISPLAY_TIMEZONE + setTimezone-Umrechnung entfernt - Admin-/Customer-Formulare: scheduledAtUtc() -> resolveScheduledAt(), parst direkt in App-Zeitzone; Edit-Hydration ohne setTimezone - ProofPdfRenderer + Boost-Anzeige: setTimezone entfernt - Tests an verschiebungsfreies Verhalten angepasst Co-Authored-By: Claude Opus 4.8 (1M context) --- app/Models/PressRelease.php | 16 +++++----------- app/Services/PressRelease/ProofPdfRenderer.php | 2 +- config/app.php | 2 +- .../admin/press-releases/create.blade.php | 14 +++++++------- .../admin/press-releases/edit.blade.php | 17 ++++++++--------- .../customer/press-releases/create.blade.php | 14 +++++++------- .../customer/press-releases/edit.blade.php | 17 ++++++++--------- .../customer/press-releases/show.blade.php | 2 +- .../Admin/AdminPressReleaseSchedulingTest.php | 16 +++++++--------- .../Feature/Admin/AdminPressReleaseShowTest.php | 4 ++-- .../CustomerPressReleaseSchedulingFormTest.php | 12 +++++------- tests/Feature/PressReleaseShowPhase8aTest.php | 8 ++++---- 12 files changed, 56 insertions(+), 68 deletions(-) diff --git a/app/Models/PressRelease.php b/app/Models/PressRelease.php index f4d0e8dd..ebd1006e 100644 --- a/app/Models/PressRelease.php +++ b/app/Models/PressRelease.php @@ -26,13 +26,6 @@ class PressRelease extends Model /** @use HasFactory */ use HasFactory, HasUniqueSlug, SoftDeletes; - /** - * Anzeige-Zeitzone für vom Nutzer erfasste Termine (scheduled_at, - * embargo_at). In der Datenbank wird weiterhin UTC gespeichert - * (config app.timezone). - */ - public const DISPLAY_TIMEZONE = 'Europe/Berlin'; - /** * @return list */ @@ -113,19 +106,20 @@ class PressRelease extends Model } /** - * Geplanter Veröffentlichungstermin in der Anzeige-Zeitzone (Europe/Berlin). + * Geplanter Veröffentlichungstermin. Die Anwendung läuft durchgängig in + * Europe/Berlin (config app.timezone), daher ist keine Umrechnung nötig. */ public function scheduledAtLocal(): ?Carbon { - return $this->scheduled_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE); + return $this->scheduled_at; } /** - * Sperrfrist (Embargo) in der Anzeige-Zeitzone (Europe/Berlin). + * Sperrfrist (Embargo) in der Anwendungs-Zeitzone (Europe/Berlin). */ public function embargoAtLocal(): ?Carbon { - return $this->embargo_at?->copy()->setTimezone(self::DISPLAY_TIMEZONE); + return $this->embargo_at; } public function user(): BelongsTo diff --git a/app/Services/PressRelease/ProofPdfRenderer.php b/app/Services/PressRelease/ProofPdfRenderer.php index ffaf480c..e6cc7223 100644 --- a/app/Services/PressRelease/ProofPdfRenderer.php +++ b/app/Services/PressRelease/ProofPdfRenderer.php @@ -50,7 +50,7 @@ class ProofPdfRenderer $portalLabel = $pressRelease->portal instanceof Portal ? $pressRelease->portal->label() : (string) $pressRelease->portal; - $publishedAt = $pressRelease->published_at?->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE); + $publishedAt = $pressRelease->published_at; $dateLabel = $publishedAt?->format('d.m.Y') ?? 'n/a'; $timeLabel = $publishedAt?->format('H:i') ?? null; diff --git a/config/app.php b/config/app.php index 324b513a..53126c89 100644 --- a/config/app.php +++ b/config/app.php @@ -65,7 +65,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => 'Europe/Berlin', /* |-------------------------------------------------------------------------- diff --git a/resources/views/livewire/admin/press-releases/create.blade.php b/resources/views/livewire/admin/press-releases/create.blade.php index f7081701..f68ba2d6 100644 --- a/resources/views/livewire/admin/press-releases/create.blade.php +++ b/resources/views/livewire/admin/press-releases/create.blade.php @@ -175,7 +175,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // bewusst prüfen statt über die naive `after:`-Regel. function (string $attribute, mixed $value, \Closure $fail): void { - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -219,16 +219,16 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex } /** - * Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, - * wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. + * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin). + * Null, wenn kein Termin gesetzt ist. */ - protected function scheduledAtUtc(): ?\Carbon\Carbon + protected function resolveScheduledAt(): ?\Carbon\Carbon { if (blank($this->scheduledAt)) { return null; } - return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); + return \Carbon\Carbon::parse($this->scheduledAt); } protected function validateScheduledAtWhenReady(): void @@ -239,7 +239,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex $this->resetErrorBag('scheduledAt'); - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -321,7 +321,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex 'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null, 'keywords' => $this->keywords ?: null, 'backlink_url' => $this->backlinkUrl ?: null, - 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->scheduledAtUtc() : null, + 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->resolveScheduledAt() : null, 'embargo_at' => null, 'status' => $status->value, 'no_export' => $this->noExport, diff --git a/resources/views/livewire/admin/press-releases/edit.blade.php b/resources/views/livewire/admin/press-releases/edit.blade.php index b8a60148..1d758e40 100644 --- a/resources/views/livewire/admin/press-releases/edit.blade.php +++ b/resources/views/livewire/admin/press-releases/edit.blade.php @@ -105,8 +105,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl $this->contactId = $pr->contacts()->withoutGlobalScopes()->first()?->id ?? $this->defaultContactIdFor((int) $pr->company_id); if ($pr->scheduled_at) { - // DB-Wert ist UTC; für die Eingabefelder nach Europe/Berlin wandeln. - $scheduledAt = $pr->scheduled_at->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE); + $scheduledAt = $pr->scheduled_at; $this->publishMode = 'scheduled'; $this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i'); $this->scheduledDate = $scheduledAt->format('Y-m-d'); @@ -222,7 +221,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // bewusst prüfen statt über die naive `after:`-Regel. function (string $attribute, mixed $value, \Closure $fail): void { - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -266,16 +265,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl } /** - * Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, - * wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. + * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin). + * Null, wenn kein Termin gesetzt ist. */ - protected function scheduledAtUtc(): ?\Carbon\Carbon + protected function resolveScheduledAt(): ?\Carbon\Carbon { if (blank($this->scheduledAt)) { return null; } - return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); + return \Carbon\Carbon::parse($this->scheduledAt); } protected function validateScheduledAtWhenReady(): void @@ -286,7 +285,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl $this->resetErrorBag('scheduledAt'); - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -365,7 +364,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl 'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null, 'keywords' => $this->keywords ?: null, 'backlink_url' => $this->backlinkUrl ?: null, - 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->scheduledAtUtc() : null, + 'scheduled_at' => $this->publishMode === 'scheduled' ? $this->resolveScheduledAt() : null, 'embargo_at' => null, 'no_export' => $this->noExport, ]); diff --git a/resources/views/livewire/customer/press-releases/create.blade.php b/resources/views/livewire/customer/press-releases/create.blade.php index a200eeda..3090a40c 100644 --- a/resources/views/livewire/customer/press-releases/create.blade.php +++ b/resources/views/livewire/customer/press-releases/create.blade.php @@ -220,7 +220,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // bewusst prüfen statt über die naive `after:`-Regel. function (string $attribute, mixed $value, \Closure $fail): void { - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -264,16 +264,16 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex } /** - * Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, - * wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. + * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin). + * Null, wenn kein Termin gesetzt ist. */ - protected function scheduledAtUtc(): ?\Carbon\Carbon + protected function resolveScheduledAt(): ?\Carbon\Carbon { if (blank($this->scheduledAt)) { return null; } - return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); + return \Carbon\Carbon::parse($this->scheduledAt); } protected function validateScheduledAtWhenReady(): void @@ -284,7 +284,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex $this->resetErrorBag('scheduledAt'); - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -567,7 +567,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex 'keywords' => $this->keywords ?: null, 'backlink_url' => $this->backlinkUrl ?: null, 'scheduled_at' => $this->publishMode === 'scheduled' - ? $this->scheduledAtUtc() + ? $this->resolveScheduledAt() : null, 'embargo_at' => null, ]; diff --git a/resources/views/livewire/customer/press-releases/edit.blade.php b/resources/views/livewire/customer/press-releases/edit.blade.php index bec65112..358e1d25 100644 --- a/resources/views/livewire/customer/press-releases/edit.blade.php +++ b/resources/views/livewire/customer/press-releases/edit.blade.php @@ -108,8 +108,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl ?? $this->defaultContactIdFor((int) $pr->company_id); if ($pr->scheduled_at) { - // DB-Wert ist UTC; für die Eingabefelder nach Europe/Berlin wandeln. - $scheduledAt = $pr->scheduled_at->copy()->setTimezone(PressRelease::DISPLAY_TIMEZONE); + $scheduledAt = $pr->scheduled_at; $this->publishMode = 'scheduled'; $this->scheduledAt = $scheduledAt->format('Y-m-d\TH:i'); $this->scheduledDate = $scheduledAt->format('Y-m-d'); @@ -289,7 +288,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl // Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen- // bewusst prüfen statt über die naive `after:`-Regel. function (string $attribute, mixed $value, \Closure $fail): void { - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -333,16 +332,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl } /** - * Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt, - * wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt. + * Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin). + * Null, wenn kein Termin gesetzt ist. */ - protected function scheduledAtUtc(): ?\Carbon\Carbon + protected function resolveScheduledAt(): ?\Carbon\Carbon { if (blank($this->scheduledAt)) { return null; } - return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc(); + return \Carbon\Carbon::parse($this->scheduledAt); } protected function validateScheduledAtWhenReady(): void @@ -353,7 +352,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl $this->resetErrorBag('scheduledAt'); - $scheduledAt = $this->scheduledAtUtc(); + $scheduledAt = $this->resolveScheduledAt(); if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) { $this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.')); @@ -435,7 +434,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl 'keywords' => $this->keywords ?: null, 'backlink_url' => $this->backlinkUrl ?: null, 'scheduled_at' => $this->publishMode === 'scheduled' - ? $this->scheduledAtUtc() + ? $this->resolveScheduledAt() : null, 'embargo_at' => null, ]); diff --git a/resources/views/livewire/customer/press-releases/show.blade.php b/resources/views/livewire/customer/press-releases/show.blade.php index 207f4b74..f6518b1e 100644 --- a/resources/views/livewire/customer/press-releases/show.blade.php +++ b/resources/views/livewire/customer/press-releases/show.blade.php @@ -466,7 +466,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends @if ($isBoosted)
- {{ __('Aktiv bis :date', ['date' => $boostedUntil?->copy()->setTimezone(\App\Models\PressRelease::DISPLAY_TIMEZONE)->format('d.m.Y H:i')]) }} + {{ __('Aktiv bis :date', ['date' => $boostedUntil?->format('d.m.Y H:i')]) }}
@endif diff --git a/tests/Feature/Admin/AdminPressReleaseSchedulingTest.php b/tests/Feature/Admin/AdminPressReleaseSchedulingTest.php index 17b6d3cc..280d186b 100644 --- a/tests/Feature/Admin/AdminPressReleaseSchedulingTest.php +++ b/tests/Feature/Admin/AdminPressReleaseSchedulingTest.php @@ -47,10 +47,9 @@ test('admin create form persistiert scheduled_at aus datum und uhrzeit ohne emba $pr = PressRelease::query()->latest('id')->firstOrFail(); - // Eingabe 14:30 erfolgt in Europe/Berlin (CEST, +02:00) und wird als - // 12:30 UTC gespeichert. - expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 12:30:00'); - expect($pr->scheduled_at?->copy()->setTimezone('Europe/Berlin')->format('Y-m-d H:i'))->toBe('2026-06-05 14:30'); + // Die App läuft in Europe/Berlin; die Eingabe 14:30 wird ohne Umrechnung + // als 14:30 gespeichert. + expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00'); expect($pr->embargo_at)->toBeNull(); }); @@ -83,9 +82,8 @@ test('admin edit hydriert scheduled_at in datum und uhrzeit ohne embargo', funct $this->actingAs($admin); $pr = PressRelease::factory()->create([ - // 12:30 UTC entspricht 14:30 in Europe/Berlin (CEST) – so wird der - // Termin in den Eingabefeldern angezeigt. - 'scheduled_at' => '2026-06-05 12:30:00', + // Wert wird unverändert (Europe/Berlin) in den Eingabefeldern angezeigt. + 'scheduled_at' => '2026-06-05 14:30:00', 'embargo_at' => '2026-06-10 08:00:00', ]); @@ -121,8 +119,8 @@ test('admin edit persistiert scheduled_at aus datum und uhrzeit ohne embargo', f $pr->refresh(); - // Eingabe 09:00 in Europe/Berlin (CEST, +02:00) wird als 07:00 UTC gespeichert. - expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-08 07:00:00'); + // Eingabe 09:00 (Europe/Berlin) wird ohne Umrechnung als 09:00 gespeichert. + expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-08 09:00:00'); expect($pr->embargo_at)->toBeNull(); }); diff --git a/tests/Feature/Admin/AdminPressReleaseShowTest.php b/tests/Feature/Admin/AdminPressReleaseShowTest.php index 1acc5d26..1817a384 100644 --- a/tests/Feature/Admin/AdminPressReleaseShowTest.php +++ b/tests/Feature/Admin/AdminPressReleaseShowTest.php @@ -91,10 +91,10 @@ test('admin show zeigt Scheduling-Termin im Review-Workflow', function () { 'scheduled_at' => '2026-06-15 10:00:00', ]); - // 10:00 UTC wird in Europe/Berlin (CEST, +02:00) als 12:00 angezeigt. + // Die App läuft in Europe/Berlin; 10:00 wird unverändert angezeigt. LivewireVolt::test('admin.press-releases.show', ['id' => $pr->id]) ->assertSee('Geplante Veröffentlichung') - ->assertSee('15.06.2026 12:00'); + ->assertSee('15.06.2026 10:00'); }); test('admin show zeigt Embargo-Info im Published-Workflow', function () { diff --git a/tests/Feature/CustomerPressReleaseSchedulingFormTest.php b/tests/Feature/CustomerPressReleaseSchedulingFormTest.php index e49ef184..2329b9ef 100644 --- a/tests/Feature/CustomerPressReleaseSchedulingFormTest.php +++ b/tests/Feature/CustomerPressReleaseSchedulingFormTest.php @@ -52,10 +52,9 @@ test('create form persistiert scheduled_at aus datum und uhrzeit ohne embargo', $pr = PressRelease::query()->latest('id')->firstOrFail(); - // Eingabe 14:30 erfolgt in Europe/Berlin (CEST, +02:00) und wird als - // 12:30 UTC gespeichert. - expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 12:30:00'); - expect($pr->scheduled_at?->copy()->setTimezone('Europe/Berlin')->format('Y-m-d H:i'))->toBe('2026-06-05 14:30'); + // Die App läuft in Europe/Berlin; die Eingabe 14:30 wird ohne Umrechnung + // als 14:30 gespeichert. + expect($pr->scheduled_at?->toDateTimeString())->toBe('2026-06-05 14:30:00'); expect($pr->embargo_at)->toBeNull(); }); @@ -122,9 +121,8 @@ test('edit form hydriert scheduled_at in datum und uhrzeit ohne embargo', functi 'category_id' => $category->id, 'portal' => $company->portal->value, 'status' => 'draft', - // 12:30 UTC entspricht 14:30 in Europe/Berlin (CEST) – so wird der - // Termin in den Eingabefeldern angezeigt. - 'scheduled_at' => '2026-06-05 12:30:00', + // Wert wird unverändert (Europe/Berlin) in den Eingabefeldern angezeigt. + 'scheduled_at' => '2026-06-05 14:30:00', 'embargo_at' => '2026-06-10 08:00:00', ]); $pr->contacts()->sync([$contact->id]); diff --git a/tests/Feature/PressReleaseShowPhase8aTest.php b/tests/Feature/PressReleaseShowPhase8aTest.php index 65bdbd95..c0136457 100644 --- a/tests/Feature/PressReleaseShowPhase8aTest.php +++ b/tests/Feature/PressReleaseShowPhase8aTest.php @@ -73,10 +73,10 @@ test('customer show zeigt geplante Veröffentlichung wenn gesetzt', function () ]); $this->actingAs($customer); - // 09:30 UTC wird in Europe/Berlin (CEST, +02:00) als 11:30 angezeigt. + // Die App läuft in Europe/Berlin; 09:30 wird unverändert angezeigt. LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) ->assertSee('Geplante Veröffentlichung') - ->assertSee('01.07.2026 11:30'); + ->assertSee('01.07.2026 09:30'); }); test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () { @@ -87,10 +87,10 @@ test('customer show zeigt Sperrfrist wenn embargo_at gesetzt', function () { ]); $this->actingAs($customer); - // 12:00 UTC wird in Europe/Berlin (CEST, +02:00) als 14:00 angezeigt. + // Die App läuft in Europe/Berlin; 12:00 wird unverändert angezeigt. LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) ->assertSee('Sperrfrist bis') - ->assertSee('15.08.2026 14:00'); + ->assertSee('15.08.2026 12:00'); }); test('customer show zeigt Kein-Export-Hinweis wenn no_export aktiv', function () {