From d63d65c7ac29eb611e2865dc6e2e50efe6bc5c1a Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 4 Nov 2026 10:35:18 +0100 Subject: [PATCH 1/3] fix: repair event theme production CI failures --- app/Enums/EventTheme.php | 12 ++++++++---- tests/Feature/EventThemeTest.php | 20 ++++++++++++++++++-- tests/Unit/Enums/EventThemeTest.php | 4 ++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/Enums/EventTheme.php b/app/Enums/EventTheme.php index d851dd9f1f..665ca63abb 100644 --- a/app/Enums/EventTheme.php +++ b/app/Enums/EventTheme.php @@ -7,6 +7,7 @@ use App\Dto\ReaderClock; use Carbon\CarbonInterface; use Illuminate\Http\Request; +use IntlTimeZone; enum EventTheme: string { @@ -64,11 +65,11 @@ public static function current(?string $choice, CarbonInterface $date): ?self */ public static function choiceFrom(?string $cookie): string { - if ($cookie === self::DEFAULT || self::tryFrom((string) $cookie) instanceof self) { - return $cookie; + if ($cookie === self::DEFAULT) { + return self::DEFAULT; } - return self::AUTO; + return self::tryFrom((string) $cookie)->value ?? self::AUTO; } /** @@ -88,6 +89,9 @@ public static function on(CarbonInterface $date): ?self private static function readerNow(Request $request): CarbonInterface { - return now(ReaderClock::of($request)->timeZone); + $timeZone = ReaderClock::of($request)->timeZone; + + // ICU accepts browser aliases that PHP's date database may omit. + return now($timeZone === null ? null : (IntlTimeZone::getIanaID($timeZone) ?: null)); } } diff --git a/tests/Feature/EventThemeTest.php b/tests/Feature/EventThemeTest.php index b21ca036ac..f766077f2f 100644 --- a/tests/Feature/EventThemeTest.php +++ b/tests/Feature/EventThemeTest.php @@ -46,6 +46,22 @@ ->assertSee('data-event-theme="'.$choice.'"', false); })->with(['halloween', 'christmas', 'newyear']); +it('reads the local event date from browser time zone aliases', function (string $timeZone, string $date): void { + $this->travelTo(Date::parse($date)); + + $this->withUnencryptedCookie('time_zone', $timeZone) + ->get(route('home')) + ->assertOk() + ->assertSee('data-auto-event-theme="halloween"', false) + ->assertSee('data-event-theme="halloween"', false) + ->assertInertia(fn ($page) => $page + ->where('eventTheme', 'halloween') + ->where('clock.timeZone', $timeZone)); +})->with([ + 'a former name browsers still report' => ['Asia/Calcutta', '2026-10-28 20:00:00 UTC'], + 'a legacy regional alias' => ['US/Eastern', '2026-11-02 02:00:00 UTC'], +]); + it('keeps the usual colors for readers who picked default', function (): void { Date::setTestNow('2026-12-25 12:00:00'); @@ -98,8 +114,8 @@ ->assertOk() ->assertInertia(fn ($page) => $page->where('eventYear', $year)); })->with([ - 'New Year\'s Eve' => ['2026-12-31 23:59:59', 2027], - 'New Year\'s Day' => ['2027-01-01 00:00:00', 2027], + "New Year's Eve" => ['2026-12-31 23:59:59', 2027], + "New Year's Day" => ['2027-01-01 00:00:00', 2027], 'the day after' => ['2027-01-02 12:00:00', 2027], ]); diff --git a/tests/Unit/Enums/EventThemeTest.php b/tests/Unit/Enums/EventThemeTest.php index 5658f0c421..954b1c1737 100644 --- a/tests/Unit/Enums/EventThemeTest.php +++ b/tests/Unit/Enums/EventThemeTest.php @@ -19,8 +19,8 @@ 'the day after Christmas' => ['2026-12-26 23:59:59', EventTheme::Christmas], 'between Christmas and New Year' => ['2026-12-29 23:59:59', null], 'two days before New Year' => ['2026-12-30 00:00:00', EventTheme::NewYear], - 'New Year\'s Eve' => ['2026-12-31 12:00:00', EventTheme::NewYear], - 'New Year\'s Day' => ['2027-01-01 12:00:00', EventTheme::NewYear], + "New Year's Eve" => ['2026-12-31 12:00:00', EventTheme::NewYear], + "New Year's Day" => ['2027-01-01 12:00:00', EventTheme::NewYear], 'the day after New Year' => ['2027-01-02 23:59:59', EventTheme::NewYear], 'after the New Year window' => ['2027-01-03 00:00:00', null], 'an ordinary day' => ['2026-06-15 12:00:00', null], From 4479a1c87294f8fc66f11394fea038d2ccc8795c Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 4 Nov 2026 10:55:05 +0100 Subject: [PATCH 2/3] fix: read event dates without requiring ICU 74 --- app/Enums/EventTheme.php | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/app/Enums/EventTheme.php b/app/Enums/EventTheme.php index 665ca63abb..2fd7c8f2d1 100644 --- a/app/Enums/EventTheme.php +++ b/app/Enums/EventTheme.php @@ -7,7 +7,8 @@ use App\Dto\ReaderClock; use Carbon\CarbonInterface; use Illuminate\Http\Request; -use IntlTimeZone; +use Illuminate\Support\Facades\Date; +use IntlDateFormatter; enum EventTheme: string { @@ -26,7 +27,7 @@ public static function forRequest(Request $request): ?self { $choice = $request->cookie('event_theme'); - return self::current(is_string($choice) ? $choice : null, self::readerNow($request)); + return self::current(is_string($choice) ? $choice : null, self::readerDate($request)); } /** @@ -34,7 +35,7 @@ public static function forRequest(Request $request): ?self */ public static function autoForRequest(Request $request): ?self { - return self::on(self::readerNow($request)); + return self::on(self::readerDate($request)); } /** @@ -42,9 +43,9 @@ public static function autoForRequest(Request $request): ?self */ public static function greetedYear(Request $request): int { - $now = self::readerNow($request); + $date = self::readerDate($request); - return $now->month === 12 ? $now->year + 1 : $now->year; + return $date->month === 12 ? $date->year + 1 : $date->year; } /** @@ -87,11 +88,23 @@ public static function on(CarbonInterface $date): ?self }; } - private static function readerNow(Request $request): CarbonInterface + /** + * Read the local calendar with ICU so browser aliases never reach PHP's date parser. + */ + private static function readerDate(Request $request): CarbonInterface { - $timeZone = ReaderClock::of($request)->timeZone; - - // ICU accepts browser aliases that PHP's date database may omit. - return now($timeZone === null ? null : (IntlTimeZone::getIanaID($timeZone) ?: null)); + $now = now(); + $formatter = new IntlDateFormatter( + 'en_US', + IntlDateFormatter::NONE, + IntlDateFormatter::NONE, + ReaderClock::of($request)->timeZone ?? $now->getTimezone(), + IntlDateFormatter::GREGORIAN, + 'yyyy-MM-dd', + ); + $date = $formatter->format($now); + assert(is_string($date)); + + return Date::parse($date, 'UTC'); } } From 305841bf15947fe85d0c42d37bebcb6c466d0826 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 4 Nov 2026 11:12:14 +0100 Subject: [PATCH 3/3] fix: require intl for event theme date formatting --- .github/actions/setup-php/action.yml | 2 +- README.md | 2 +- composer.json | 1 + composer.lock | 5 +++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup-php/action.yml b/.github/actions/setup-php/action.yml index cf4df451c2..0b35b8c7d6 100644 --- a/.github/actions/setup-php/action.yml +++ b/.github/actions/setup-php/action.yml @@ -15,7 +15,7 @@ runs: with: php-version: 8.5 tools: composer:v2 - extensions: pdo_pgsql + extensions: intl, pdo_pgsql coverage: ${{ inputs.coverage }} ini-values: zend.assertions=1, memory_limit=-1 diff --git a/README.md b/README.md index 7d1254b35f..7a0e8e8922 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This is the source code of **[dev-null.sh](https://dev-null.sh)**, a social netw ## Contributing -Requires PHP 8.5+, Composer, Bun, and PostgreSQL 17+ with `pgvector` and `pg_trgm`. Create a `devnull` database, then: +Requires PHP 8.5+ with `intl`, Composer, Bun, and PostgreSQL 17+ with `pgvector` and `pg_trgm`. Create a `devnull` database, then: ```bash git clone https://github.com/dev-null-sh/dev-null.sh.git diff --git a/composer.json b/composer.json index a456e10e1d..e33af1db6a 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "license": "MIT", "require": { "php": "^8.5.0", + "ext-intl": "*", "firebase/php-jwt": "^7.2.1", "inertiajs/inertia-laravel": "^3.6.0", "intervention/image": "^4.4.1", diff --git a/composer.lock b/composer.lock index 24587fb3ca..23eabb6a24 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba88d229cb2087c4ed1e7cdcbaae1196", + "content-hash": "14de94a24ceb94ee8549b30ed5c3714a", "packages": [ { "name": "aws/aws-crt-php", @@ -15894,7 +15894,8 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.5.0" + "php": "^8.5.0", + "ext-intl": "*" }, "platform-dev": {}, "plugin-api-version": "2.9.0"