From 065065e9f8fbe729e90df528dc024cb883f7b4fe Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 29 Aug 2025 15:44:45 -0700 Subject: [PATCH 01/14] new constraints & normalize --- composer.json | 63 ++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 5ab99f8..e742c8f 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "transformstudios/events", - "type": "statamic-addon", - "license": "proprietary", "description": "Statamic addon to create & manage events", + "license": "proprietary", + "type": "statamic-addon", "authors": [ { "name": "Erin Dalzell", @@ -10,6 +10,21 @@ "homepage": "https://transformstudios.com" } ], + "require": { + "rlanvin/php-rrule": "^2.3.1", + "spatie/calendar-links": "^1.0", + "spatie/icalendar-generator": "^2.3.3", + "statamic/cms": "^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.1", + "nunomaduro/collision": "^8.0", + "orchestra/testbench": "^9.0", + "phpunit/phpunit": "^11.0", + "spatie/laravel-ray": "^1.35", + "spatie/test-time": "^1.2" + }, + "minimum-stability": "dev", "autoload": { "psr-4": { "TransformStudios\\Events\\": "src" @@ -23,41 +38,23 @@ "TransformStudios\\Events\\Tests\\": "tests" } }, - "extra": { - "statamic": { - "name": "Events", - "description": "Statamic addon to create & manage events" + "config": { + "allow-plugins": { + "pixelfear/composer-dist-plugin": true }, + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { "laravel": { "providers": [ "TransformStudios\\Events\\ServiceProvider" ] + }, + "statamic": { + "description": "Statamic addon to create & manage events", + "name": "Events" } - }, - "require": { - "php": "^8.2", - "edalzell/forma": "^2.0 || ^3.0", - "nesbot/carbon": "^2.0 || ^3.0", - "rlanvin/php-rrule": "^2.3.1", - "spatie/calendar-links": "^1.0", - "spatie/icalendar-generator": "^2.3.3", - "statamic/cms": "^4.5 || ^5.0" - }, - "require-dev": { - "mockery/mockery": "^1.3.1", - "nunomaduro/collision": "^6.0 || ^7.0 || ^8.0", - "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0", - "orchestra/testbench": "^7.0 || ^8.0 || ^9.0", - "spatie/laravel-ray": "^1.35", - "spatie/test-time": "^1.2" - }, - "config": { - "optimize-autoloader": true, - "preferred-install": "dist", - "sort-packages": true, - "allow-plugins": { - "pixelfear/composer-dist-plugin": true - } - }, - "repositories": [] + } } From e815c5053addc121d3e63001bad83b02a87e2ae4 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 29 Aug 2025 15:56:29 -0700 Subject: [PATCH 02/14] alpha --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e742c8f..7f68b88 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "rlanvin/php-rrule": "^2.3.1", "spatie/calendar-links": "^1.0", "spatie/icalendar-generator": "^2.3.3", - "statamic/cms": "^6.0" + "statamic/cms": "6.0.0-alpha.4" }, "require-dev": { "mockery/mockery": "^1.3.1", @@ -24,7 +24,6 @@ "spatie/laravel-ray": "^1.35", "spatie/test-time": "^1.2" }, - "minimum-stability": "dev", "autoload": { "psr-4": { "TransformStudios\\Events\\": "src" From 2c943b39e47f31bb856ce88e28d54d779dddfa6b Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 29 Aug 2025 16:42:54 -0700 Subject: [PATCH 03/14] Forma is not needed anymore --- src/ServiceProvider.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 2de2ade..edd5c95 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,7 +3,6 @@ namespace TransformStudios\Events; use Composer\InstalledVersions; -use Edalzell\Forma\Forma; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Artisan; use Statamic\Facades\Collection; @@ -37,13 +36,6 @@ class ServiceProvider extends AddonServiceProvider Events::class, ]; - public function boot() - { - parent::boot(); - - Forma::add('transformstudios/events'); - } - public function bootAddon() { $this From d6af929b2b43693b6209c1b43d117cc7ae446b33 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Fri, 29 Aug 2025 17:00:12 -0700 Subject: [PATCH 04/14] Convert to Pest (#131) --- .github/workflows/test.yml | 17 +- composer.json | 11 +- tests/Feature/EventsOffsetTest.php | 204 +++--- tests/Feature/IcsControllerTest.php | 391 ++++++----- tests/Feature/TagTest.php | 651 +++++++++---------- tests/Pest.php | 47 ++ tests/TestCase.php | 13 + tests/Unit/DayTest.php | 46 +- tests/Unit/EventFactoryTest.php | 125 ++-- tests/Unit/EventsTest.php | 785 +++++++++++------------ tests/Unit/MultiDayEventsTest.php | 274 +++----- tests/Unit/RecurringDailyEventsTest.php | 475 ++++++-------- tests/Unit/RecurringEventsTest.php | 155 ++--- tests/Unit/RecurringEveryXEventsTest.php | 396 ++++++------ tests/Unit/SingleDayEventsTest.php | 291 ++++----- 15 files changed, 1805 insertions(+), 2076 deletions(-) create mode 100644 tests/Pest.php diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f699c6..b7c9652 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,17 +9,11 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] - php: [8.2, 8.3, 8.4] + php: [8.3, 8.4] laravel: [11.*, 12.*] + stability: [prefer-lowest, prefer-stable] + os: [ubuntu-latest] dependency-version: [prefer-stable] - exclude: - - php: 8.1 - laravel: 11.* - - php: 8.1 - laravel: 12.* - - php: 8.4 - laravel: 10.* name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ubuntu-latest steps: - name: Checkout code @@ -39,8 +33,7 @@ jobs: coverage: none - name: Install dependencies - run: | - composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest - name: Execute tests - run: vendor/bin/phpunit -c phpunit.xml + run: vendor/bin/pest diff --git a/composer.json b/composer.json index 7f68b88..ae20d1c 100644 --- a/composer.json +++ b/composer.json @@ -11,22 +11,22 @@ } ], "require": { + "php": "^8.3", "rlanvin/php-rrule": "^2.3.1", "spatie/calendar-links": "^1.0", "spatie/icalendar-generator": "^2.3.3", "statamic/cms": "6.0.0-alpha.4" }, "require-dev": { - "mockery/mockery": "^1.3.1", - "nunomaduro/collision": "^8.0", - "orchestra/testbench": "^9.0", - "phpunit/phpunit": "^11.0", + "orchestra/testbench": "^9.2 || ^10.0", + "pestphp/pest": "^4.0", "spatie/laravel-ray": "^1.35", "spatie/test-time": "^1.2" }, "autoload": { "psr-4": { - "TransformStudios\\Events\\": "src" + "TransformStudios\\Events\\": "src", + "TransformStudios\\Events\\Tests\\": "tests" }, "files": [ "src/helpers.php" @@ -39,6 +39,7 @@ }, "config": { "allow-plugins": { + "pestphp/pest-plugin": true, "pixelfear/composer-dist-plugin": true }, "optimize-autoloader": true, diff --git a/tests/Feature/EventsOffsetTest.php b/tests/Feature/EventsOffsetTest.php index 96d0a35..31dbe89 100644 --- a/tests/Feature/EventsOffsetTest.php +++ b/tests/Feature/EventsOffsetTest.php @@ -1,117 +1,97 @@ collection('events') - ->slug('recurring-event') - ->id('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'categories' => ['one'], - ])->save(); - - $this->tag = app(Events::class); - } - - #[Test] - public function can_offset_upcoming_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'limit' => 5, - 'offset' => 2, - ]); - - $occurrences = $this->tag->upcoming(); - - $this->assertCount(3, $occurrences); - } - - #[Test] - public function can_offset_between_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'from' => Carbon::now()->toDateString(), - 'to' => Carbon::now()->addWeek(3), - 'offset' => 2, - ]); - - $occurrences = $this->tag->between(); - - $this->assertCount(2, $occurrences); - } - - #[Test] - public function can_offset_today_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '13:00', - 'end_time' => '15:00', - ])->save(); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'offset' => 1, - ]); - - $this->assertCount(1, $this->tag->today()); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'ignore_finished' => true, - 'offset' => 1, - ]); - - $this->assertCount(0, $this->tag->today()); - } - - #[Test] - public function can_offset_single_day_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'offset' => 1, - ]); - - $this->assertCount(0, $this->tag->today()); - } -} + +beforeEach(function () { + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->id('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'categories' => ['one'], + ])->save(); + + $this->tag = app(Events::class); +}); + +test('can offset upcoming occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'limit' => 5, + 'offset' => 2, + ]); + + $occurrences = $this->tag->upcoming(); + + expect($occurrences)->toHaveCount(3); +}); + +test('can offset between occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'from' => Carbon::now()->toDateString(), + 'to' => Carbon::now()->addWeek(3), + 'offset' => 2, + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(2); +}); + +test('can offset today occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '13:00', + 'end_time' => '15:00', + ])->save(); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(1); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'ignore_finished' => true, + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(0); +}); + +test('can offset single day occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(0); +}); diff --git a/tests/Feature/IcsControllerTest.php b/tests/Feature/IcsControllerTest.php index 2e43ae3..7f3b8fd 100755 --- a/tests/Feature/IcsControllerTest.php +++ b/tests/Feature/IcsControllerTest.php @@ -1,214 +1,189 @@ collection('events') - ->slug('single-event') - ->id('the-id') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'address' => '123 Main St', - 'location' => 'The Location', - 'coordinates' => [ - 'latitude' => 40, - 'longitude' => 50, + +beforeEach(function () { + Entry::make() + ->collection('events') + ->slug('single-event') + ->id('the-id') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'address' => '123 Main St', + 'location' => 'The Location', + 'coordinates' => [ + 'latitude' => 40, + 'longitude' => 50, + ], + 'description' => 'The description', + ])->save(); +}); + +test('can create single day event ics file', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $response = $this->get(route('statamic.events.ics.show', [ + 'date' => now()->toDateString(), + 'event' => 'the-id', + ]))->assertDownload('single-event.ics'); + + $content = $response->streamedContent(); + + $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); + $this->assertStringContainsString('LOCATION:123 Main St', $content); + $this->assertStringContainsString('DESCRIPTION:The description', $content); + $this->assertStringContainsString('GEO:40;50', $content); +}); + +test('can create single day recurring event ics file', function () { + Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->id('the-recurring-id') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'location' => 'The Location', + 'description' => 'The description', + ])->save(); + + $response = $this->get(route('statamic.events.ics.show', [ + 'date' => now()->toDateString(), + 'event' => 'the-recurring-id', + ]))->assertDownload('recurring-event.ics'); + + $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); + $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); + $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); + + $this->get(route('statamic.events.ics.show', [ + 'date' => now()->addDay()->toDateString(), + 'event' => 'the-recurring-id', + ]))->assertStatus(404); +}); + +test('can create ics with single date recurrence', function () { + Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->id('the-recurring-id') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'location' => 'The Location', + 'description' => 'The description', + ])->save(); + + $response = $this->get(route('statamic.events.ics.show', [ + 'date' => now()->toDateString(), + 'event' => 'the-recurring-id', + ]))->assertDownload('recurring-event.ics'); + + $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); + $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); + $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); +}); + +test('can create ics with recurrence', function () { + Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->id('the-recurring-id') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'location' => 'The Location', + 'description' => 'The description', + ])->save(); + + $response = $this->get(route('statamic.events.ics.show', [ + 'event' => 'the-recurring-id', + ]))->assertDownload('recurring-event.ics'); + + $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); + $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); + $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); +}); + +test('can create single day multiday event ics file', function () { + Carbon::setTestNow(now()); + + Entry::make() + ->slug('multi-day-event') + ->collection('events') + ->id('the-multi-day-event') + ->data([ + 'title' => 'Multi-day Event', + 'multi_day' => true, + 'location' => 'The Location', + 'description' => 'The description', + 'days' => [ + [ + 'date' => now()->toDateString(), + 'start_time' => '19:00', + 'end_time' => '21:00', + ], + [ + 'date' => now()->addDay()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '15:00', ], - 'description' => 'The description', - ])->save(); - } - - #[Test] - public function can_create_single_day_event_ics_file() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $response = $this->get(route('statamic.events.ics.show', [ - 'date' => now()->toDateString(), - 'event' => 'the-id', - ]))->assertDownload('single-event.ics'); - - $content = $response->streamedContent(); - - $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); - $this->assertStringContainsString('LOCATION:123 Main St', $content); - $this->assertStringContainsString('DESCRIPTION:The description', $content); - $this->assertStringContainsString('GEO:40;50', $content); - } - - #[Test] - public function can_create_single_day_recurring_event_ics_file() - { - Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->id('the-recurring-id') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'location' => 'The Location', - 'description' => 'The description', - ])->save(); - - $response = $this->get(route('statamic.events.ics.show', [ - 'date' => now()->toDateString(), - 'event' => 'the-recurring-id', - ]))->assertDownload('recurring-event.ics'); - - $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); - $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); - $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); - - $this->get(route('statamic.events.ics.show', [ - 'date' => now()->addDay()->toDateString(), - 'event' => 'the-recurring-id', - ]))->assertStatus(404); - } - - #[Test] - public function can_create_ics_with_single_date_recurrence() - { - Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->id('the-recurring-id') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'location' => 'The Location', - 'description' => 'The description', - ])->save(); - - $response = $this->get(route('statamic.events.ics.show', [ - 'date' => now()->toDateString(), - 'event' => 'the-recurring-id', - ]))->assertDownload('recurring-event.ics'); - - $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); - $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); - $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); - - } - - #[Test] - public function can_create_ics_with_recurrence() - { - Carbon::setTestNow(now()->addDay()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->id('the-recurring-id') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'location' => 'The Location', - 'description' => 'The description', - ])->save(); - - $response = $this->get(route('statamic.events.ics.show', [ - 'event' => 'the-recurring-id', - ]))->assertDownload('recurring-event.ics'); - - $this->assertStringContainsString('DTSTART:'.now()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); - $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); - $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); - } - - #[Test] - public function can_create_single_day_multiday_event_ics_file() - { - Carbon::setTestNow(now()); - - Entry::make() - ->slug('multi-day-event') - ->collection('events') - ->id('the-multi-day-event') - ->data([ - 'title' => 'Multi-day Event', - 'multi_day' => true, - 'location' => 'The Location', - 'description' => 'The description', - 'days' => [ - [ - 'date' => now()->toDateString(), - 'start_time' => '19:00', - 'end_time' => '21:00', - ], - [ - 'date' => now()->addDay()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '15:00', - ], - [ - 'date' => now()->addDays(2)->toDateString(), - 'start_time' => '11:00', - 'end_time' => '15:00', - ], + [ + 'date' => now()->addDays(2)->toDateString(), + 'start_time' => '11:00', + 'end_time' => '15:00', ], - ])->save(); - - $this->get(route('statamic.events.ics.show', [ - 'date' => now()->addDays(3)->toDateString(), - 'event' => 'the-multi-day-event', - ]))->assertStatus(404); - - $response = $this->get(route('statamic.events.ics.show', [ - 'date' => now()->addDay()->toDateString(), - 'event' => 'the-multi-day-event', - ]))->assertDownload('multi-day-event.ics'); - - $this->assertStringContainsString('DTSTART:'.now()->addDay()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); - $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); - $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); - } - - #[Test] - public function throws404_error_when_event_does_not_occur_on_date() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->get(route('statamic.events.ics.show', [ - 'date' => now()->addDay()->toDateString(), - 'event' => 'the-id', - ]))->assertStatus(404); - } - - #[Test] - public function throws404_error_when_event_does_not_exist() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->get(route('statamic.events.ics.show', [ - 'date' => now()->addDay()->toDateString(), - 'event' => 'does-not-exist', - ]))->assertStatus(404); - } -} + ], + ])->save(); + + $this->get(route('statamic.events.ics.show', [ + 'date' => now()->addDays(3)->toDateString(), + 'event' => 'the-multi-day-event', + ]))->assertStatus(404); + + $response = $this->get(route('statamic.events.ics.show', [ + 'date' => now()->addDay()->toDateString(), + 'event' => 'the-multi-day-event', + ]))->assertDownload('multi-day-event.ics'); + + $this->assertStringContainsString('DTSTART:'.now()->addDay()->setTimeFromTimeString('11:00')->format('Ymd\THis'), $response->streamedContent()); + $this->assertStringContainsString('LOCATION:The Location', $response->streamedContent()); + $this->assertStringContainsString('DESCRIPTION:The description', $response->streamedContent()); +}); + +test('throws404 error when event does not occur on date', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->get(route('statamic.events.ics.show', [ + 'date' => now()->addDay()->toDateString(), + 'event' => 'the-id', + ]))->assertStatus(404); +}); + +test('throws404 error when event does not exist', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->get(route('statamic.events.ics.show', [ + 'date' => now()->addDay()->toDateString(), + 'event' => 'does-not-exist', + ]))->assertStatus(404); +}); diff --git a/tests/Feature/TagTest.php b/tests/Feature/TagTest.php index ada7d2c..fb124c9 100755 --- a/tests/Feature/TagTest.php +++ b/tests/Feature/TagTest.php @@ -1,352 +1,313 @@ collection('events') - ->slug('recurring-event') - ->id('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'categories' => ['one'], - ])->save(); - - $this->tag = app(Events::class); - } - - #[Test] - public function can_generate_between_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'from' => Carbon::now(), - 'to' => Carbon::now()->addWeek(3), - ]); - - $occurrences = $this->tag->between(); - - $this->assertCount(4, $occurrences); - } - - #[Test] - public function can_generate_between_occurrences_with_default_from() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'to' => Carbon::now()->addWeeks(3), - ]); - - $occurrences = $this->tag->between(); - - $this->assertCount(4, $occurrences); - } - - #[Test] - public function can_generate_calendar_occurrences() - { - Carbon::setTestNow('jan 1, 2022 10:00'); - - Entry::all()->each->delete(); - - Entry::make() - ->collection('events') - ->slug('single-event-start-of-month') - ->data([ - 'title' => 'Single Event - Start of Month', - 'start_date' => Carbon::now()->startOfMonth()->toDateString(), - 'start_time' => '13:00', - 'end_time' => '15:00', - ])->save(); - - Entry::make() - ->collection('events') - ->slug('recurring-event-start-of-month') - ->data([ - 'title' => 'Recurring Event - Start of Month', - 'start_date' => Carbon::now()->startOfMonth()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'categories' => ['one'], - ])->save(); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'month' => now()->englishMonth, - 'year' => now()->year, - ]); - - $occurrences = $this->tag->calendar(); - - $this->assertCount(42, $occurrences); - $this->assertCount(2, Arr::get($occurrences, '5.dates')); - $this->assertTrue(Arr::get($occurrences, '6.no_results')); - } - - #[Test] - public function can_generate_in_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'next' => '3 weeks', - ]); - - $occurrences = $this->tag->in(); - - $this->assertCount(4, $occurrences); - } - - #[Test] - public function can_generate_today_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '13:00', - 'end_time' => '15:00', - ])->save(); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - ]); - - $this->assertCount(2, $this->tag->today()); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'ignore_finished' => true, - ]); - - $this->assertCount(1, $this->tag->today()); - } - - #[Test] - public function can_generate_upcoming_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'limit' => 3, - ]); - - $occurrences = $this->tag->upcoming(); - - $this->assertCount(3, $occurrences); - } - - #[Test] - public function can_generate_upcoming_limited_occurrences() - { - Entry::make() - ->collection('events') - ->slug('another-recurring-event') - ->id('another-recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'limit' => 3, - ]); - - $occurrences = $this->tag->upcoming(); - - $this->assertCount(3, $occurrences); - } - - #[Test] - public function can_paginate_upcoming_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'paginate' => 2, - 'limit' => 10, - ]); - - Cascade::partialMock()->shouldReceive('get') - ->with('uri') - ->andReturn('/events'); - - $pagination = $this->tag->upcoming(); - - $this->assertArrayHasKey('results', $pagination); - $this->assertArrayHasKey('paginate', $pagination); - $this->assertArrayHasKey('total_results', $pagination); - - $this->assertCount(2, $pagination['results']); - $this->assertEquals(2, $pagination['total_results']); - $this->assertEquals('/events?page=2', $pagination['paginate']['next_page']); - } - - #[Test] - public function can_generate_upcoming_occurrences_with_taxonomy_terms() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->id('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '17:00', - 'end_time' => '19:00', - ])->save(); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'from' => Carbon::now()->toDateString(), - 'to' => Carbon::now()->addDay()->toDateString(), - 'taxonomy:categories' => 'one', - ]); - - $occurrences = $this->tag->between(); - - $this->assertCount(1, $occurrences); - } - - #[Test] - public function can_generate_upcoming_occurrences_with_filter() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->id('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '17:00', - 'end_time' => '19:00', - ])->save(); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'from' => Carbon::now()->toDateString(), - 'to' => Carbon::now()->addDay()->toDateString(), - 'title:contains' => 'Single', - ]); - - $occurrences = $this->tag->between(); - - $this->assertCount(1, $occurrences); - } - - #[Test] - public function can_generate_date_event_download_link() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'date' => now(), - 'event' => 'recurring-event', - ]); - - $url = $this->tag->downloadLink(); - - $this->assertEquals('http://localhost/!/events/ics?collection=events&date='.now()->toDateString().'&event=recurring-event', $url); - } - - #[Test] - public function can_generate_event_download_link() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'event' => 'recurring-event', - ]); - - $url = $this->tag->downloadLink(); - - $this->assertEquals('http://localhost/!/events/ics?collection=events&event=recurring-event', $url); - } - - #[Test] - public function can_sort_occurrences_desc() - { - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'limit' => 3, - 'sort' => 'desc', - ]); - - $occurrences = $this->tag->upcoming(); - - $this->assertTrue($occurrences[0]->start->isAfter($occurrences[1]->start)); - $this->assertTrue($occurrences[1]->start->isAfter($occurrences[2]->start)); - } -} + +beforeEach(function () { + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->id('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'categories' => ['one'], + ])->save(); + + $this->tag = app(Events::class); +}); + +test('can generate between occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'from' => Carbon::now(), + 'to' => Carbon::now()->addWeek(3), + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(4); +}); + +test('can generate between occurrences with default from', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'to' => Carbon::now()->addWeeks(3), + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(4); +}); + +test('can generate calendar occurrences', function () { + Carbon::setTestNow('jan 1, 2022 10:00'); + + Entry::all()->each->delete(); + + Entry::make() + ->collection('events') + ->slug('single-event-start-of-month') + ->data([ + 'title' => 'Single Event - Start of Month', + 'start_date' => Carbon::now()->startOfMonth()->toDateString(), + 'start_time' => '13:00', + 'end_time' => '15:00', + ])->save(); + + Entry::make() + ->collection('events') + ->slug('recurring-event-start-of-month') + ->data([ + 'title' => 'Recurring Event - Start of Month', + 'start_date' => Carbon::now()->startOfMonth()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + 'categories' => ['one'], + ])->save(); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'month' => now()->englishMonth, + 'year' => now()->year, + ]); + + $occurrences = $this->tag->calendar(); + + expect($occurrences)->toHaveCount(42); + expect(Arr::get($occurrences, '5.dates'))->toHaveCount(2); + expect(Arr::get($occurrences, '6.no_results'))->toBeTrue(); +}); + +test('can generate in occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'next' => '3 weeks', + ]); + + $occurrences = $this->tag->in(); + + expect($occurrences)->toHaveCount(4); +}); + +test('can generate today occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '13:00', + 'end_time' => '15:00', + ])->save(); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + ]); + + expect($this->tag->today())->toHaveCount(2); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'ignore_finished' => true, + ]); + + expect($this->tag->today())->toHaveCount(1); +}); + +test('can generate upcoming occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'limit' => 3, + ]); + + $occurrences = $this->tag->upcoming(); + + expect($occurrences)->toHaveCount(3); +}); + +test('can generate upcoming limited occurrences', function () { + Entry::make() + ->collection('events') + ->slug('another-recurring-event') + ->id('another-recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'limit' => 3, + ]); + + $occurrences = $this->tag->upcoming(); + + expect($occurrences)->toHaveCount(3); +}); + +test('can paginate upcoming occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'paginate' => 2, + 'limit' => 10, + ]); + + Cascade::partialMock()->shouldReceive('get') + ->with('uri') + ->andReturn('/events'); + + $pagination = $this->tag->upcoming(); + + expect($pagination)->toHaveKey('results'); + expect($pagination)->toHaveKey('paginate'); + expect($pagination)->toHaveKey('total_results'); + + expect($pagination['results'])->toHaveCount(2); + expect($pagination['total_results'])->toEqual(2); + expect($pagination['paginate']['next_page'])->toEqual('/events?page=2'); +}); + +test('can generate upcoming occurrences with taxonomy terms', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->id('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '17:00', + 'end_time' => '19:00', + ])->save(); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'from' => Carbon::now()->toDateString(), + 'to' => Carbon::now()->addDay()->toDateString(), + 'taxonomy:categories' => 'one', + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(1); +}); + +test('can generate upcoming occurrences with filter', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->id('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '17:00', + 'end_time' => '19:00', + ])->save(); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'from' => Carbon::now()->toDateString(), + 'to' => Carbon::now()->addDay()->toDateString(), + 'title:contains' => 'Single', + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(1); +}); + +test('can generate date event download link', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'date' => now(), + 'event' => 'recurring-event', + ]); + + $url = $this->tag->downloadLink(); + + expect($url)->toEqual('http://localhost/!/events/ics?collection=events&date='.now()->toDateString().'&event=recurring-event'); +}); + +test('can generate event download link', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'event' => 'recurring-event', + ]); + + $url = $this->tag->downloadLink(); + + expect($url)->toEqual('http://localhost/!/events/ics?collection=events&event=recurring-event'); +}); + +test('can sort occurrences desc', function () { + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'limit' => 3, + 'sort' => 'desc', + ]); + + $occurrences = $this->tag->upcoming(); + + expect($occurrences[0]->start->isAfter($occurrences[1]->start))->toBeTrue(); + expect($occurrences[1]->start->isAfter($occurrences[2]->start))->toBeTrue(); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..de7b364 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,47 @@ +extend(TestCase::class); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +// expect()->extend('toBeOne', function () { +// return $this->toBe(1); +// }); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 203cae0..cf978cc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,10 +20,23 @@ abstract class TestCase extends AddonTestCase protected string $addonServiceProvider = ServiceProvider::class; + protected $fakeStacheDirectory = __DIR__.'/__fixtures__/dev-null'; + + protected $shouldFakeVersion = true; + protected Collection $collection; protected Blueprint $blueprint; + protected function setUp(): void + { + parent::setUp(); + + if (! file_exists($this->fakeStacheDirectory)) { + mkdir($this->fakeStacheDirectory, 0777, true); + } + } + protected function getEnvironmentSetUp($app) { parent::getEnvironmentSetUp($app); diff --git a/tests/Unit/DayTest.php b/tests/Unit/DayTest.php index 84c3ff5..7aa4843 100755 --- a/tests/Unit/DayTest.php +++ b/tests/Unit/DayTest.php @@ -1,40 +1,26 @@ '2019-11-23', - 'start_time' => '19:00', - ]; +test('can get end when no end time', function () { + $dayData = [ + 'date' => '2019-11-23', + 'start_time' => '19:00', + ]; - $day = new Day(data: $dayData, timezone: 'America/Vancouver'); + $day = new Day(data: $dayData, timezone: 'America/Vancouver'); - $this->assertEquals( - Carbon::parse('2019-11-23')->shiftTimezone('America/Vancouver')->endOfDay(), - $day->end() - ); - } + expect($day->end())->toEqual(Carbon::parse('2019-11-23')->shiftTimezone('America/Vancouver')->endOfDay()); +}); - #[Test] - public function has_no_end_time_when_no_end_time() - { - $dayData = [ - 'date' => '2019-11-23', - 'start_time' => '19:00', - ]; +test('has no end time when no end time', function () { + $dayData = [ + 'date' => '2019-11-23', + 'start_time' => '19:00', + ]; - $day = new Day(data: $dayData, timezone: 'America/Vancouver'); + $day = new Day(data: $dayData, timezone: 'America/Vancouver'); - $this->assertFalse($day->hasEndTime()); - } -} + expect($day->hasEndTime())->toBeFalse(); +}); diff --git a/tests/Unit/EventFactoryTest.php b/tests/Unit/EventFactoryTest.php index ad523d7..1a0dbdc 100755 --- a/tests/Unit/EventFactoryTest.php +++ b/tests/Unit/EventFactoryTest.php @@ -1,93 +1,78 @@ collection('events') - ->data($data); +test('can get event type class', function (string $class, array $data) { + $entry = Entry::make() + ->collection('events') + ->data($data); - $this->assertEquals($class, EventFactory::getTypeClass($entry)); - } + expect(EventFactory::getTypeClass($entry))->toEqual($class); +})->with('provideEventData'); - #[Test] - #[DataProvider('provideEventData')] - public function can_create_correct_event_type(string $class, array $data) - { - $entry = Entry::make() - ->collection('events') - ->data($data); +test('can create correct event type', function (string $class, array $data) { + $entry = Entry::make() + ->collection('events') + ->data($data); - $this->assertInstanceOf($class, EventFactory::createFromEntry($entry)); - } + expect(EventFactory::createFromEntry($entry))->toBeInstanceOf($class); +})->with('provideEventData'); - public static function provideEventData() - { - return [ +dataset('provideEventData', function () { + return [ + [ + SingleDayEvent::class, [ - SingleDayEvent::class, - [ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'timezone' => 'America/Vancouver', - ], + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'timezone' => 'America/Vancouver', ], + ], + [ + SingleDayEvent::class, [ - SingleDayEvent::class, - [ - 'multi_day' => true, - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'timezone' => 'America/Vancouver', - ], + 'multi_day' => true, + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'timezone' => 'America/Vancouver', ], + ], + [ + RecurringEvent::class, [ - RecurringEvent::class, - [ - 'start_date' => Carbon::now()->toDateString(), - 'end_date' => Carbon::now()->addWeek()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - 'timezone' => 'America/Vancouver', - ], + 'start_date' => Carbon::now()->toDateString(), + 'end_date' => Carbon::now()->addWeek()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + 'timezone' => 'America/Vancouver', ], + ], + [ + MultiDayEvent::class, [ - MultiDayEvent::class, - [ - 'recurrence' => 'multi_day', - 'days' => [ - [ - 'date' => '2019-11-23', - 'start_time' => '19:00', - 'end_time' => '21:00', - ], - [ - 'date' => '2019-11-24', - 'start_time' => '11:00', - 'end_time' => '15:00', - ], + 'recurrence' => 'multi_day', + 'days' => [ + [ + 'date' => '2019-11-23', + 'start_time' => '19:00', + 'end_time' => '21:00', + ], + [ + 'date' => '2019-11-24', + 'start_time' => '11:00', + 'end_time' => '15:00', ], - 'timezone' => 'America/Vancouver', ], + 'timezone' => 'America/Vancouver', ], - ]; - } -} + ], + ]; +}); diff --git a/tests/Unit/EventsTest.php b/tests/Unit/EventsTest.php index 54aefb4..edfcea5 100755 --- a/tests/Unit/EventsTest.php +++ b/tests/Unit/EventsTest.php @@ -1,417 +1,386 @@ setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'end_date' => Carbon::now()->addDays(2)->toDateString(), - 'recurrence' => 'daily', - ])->save(); - - $event = tap(Entry::make() - ->collection('events') - ->slug('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '13:00', - ]))->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(now(), now()->addDays(2)->endOfDay()); - - $expectedStartDates = [ - now()->setTimeFromTimeString('11:00'), - now()->setTimeFromTimeString('13:00'), - now()->addDay()->setTimeFromTimeString('11:00'), - now()->addDays(2)->setTimeFromTimeString('11:00'), - ]; - $this->assertCount(4, $occurrences); - - $this->assertEquals($expectedStartDates[0], $occurrences[0]->start); - $this->assertEquals($expectedStartDates[1], $occurrences[1]->start); - $this->assertEquals($expectedStartDates[2], $occurrences[2]->start); - $this->assertEquals($expectedStartDates[3], $occurrences[3]->start); - } - - #[Test] - public function can_paginate_upcoming_occurrences() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->upcoming(10); - - $this->assertCount(10, $occurrences); - $paginator = Events::fromCollection(handle: 'events') - ->pagination(perPage: 2) - ->upcoming(10); - - $this->assertInstanceOf(LengthAwarePaginator::class, $paginator); - $this->assertCount(2, $occurrences = $paginator->items()); - $this->assertEquals(now()->addDay()->setTimeFromTimeString('11:00'), $paginator->items()[1]->start); - - $paginator = Events::fromCollection(handle: 'events') - ->pagination(perPage: 3, page: 3) - ->upcoming(10); - - $this->assertInstanceOf(LengthAwarePaginator::class, $paginator); - $this->assertCount(3, $occurrences = $paginator->items()); - $this->assertEquals(3, $paginator->currentPage()); - } - - #[Test] - public function can_paginate_occurrences_between() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertCount(10, $occurrences); - $paginator = Events::fromCollection(handle: 'events') - ->pagination(perPage: 2) - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertInstanceOf(LengthAwarePaginator::class, $paginator); - - $this->assertCount(2, $occurrences = $paginator->items()); - - $this->assertEquals(now()->addDay()->setTimeFromTimeString('11:00'), $paginator->items()[1]->start); - } - - #[Test] - public function can_filter_events() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - Entry::make() - ->collection('events') - ->slug('other-event') - ->data([ - 'title' => 'Other Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->filter('title:contains', 'Other') - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertCount(10, $occurrences); - } - - #[Test] - public function can_filter_multiple_events() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - Entry::make() - ->collection('events') - ->slug('other-event') - ->data([ - 'title' => 'Other Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - Entry::make() - ->collection('events') - ->slug('other-event') - ->data([ - 'title' => 'Other Event 2', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->filter('title:contains', 'Other') - ->filter('recurrence:is', 'daily') - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertCount(10, $occurrences); - } - - #[Test] - public function can_filter_by_term_events() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - 'categories' => ['one'], - ])->save(); - - Entry::make() - ->collection('events') - ->slug('other-event') - ->data([ - 'title' => 'Other Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - 'categories' => ['two'], - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->terms('categories::two') - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertCount(10, $occurrences); - } - - #[Test] - public function can_filter_by_filter_events() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - Entry::make() - ->collection('events') - ->slug('other-event') - ->data([ - 'title' => 'Other Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->filter('title:contains', 'Recurring') - ->between(now(), now()->addDays(9)->endOfDay()); - - $this->assertCount(10, $occurrences); - } - - #[Test] - public function can_determine_occurs_at_for_single_event() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $entry = Entry::make() - ->collection('events') - ->slug('single-event') - ->id('the-id') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - ]); - - $event = EventFactory::createFromEntry($entry); - - $this->assertTrue($event->occursOnDate(now())); - } - - #[Test] - public function can_determine_occurs_at_for_multiday_event() - { - Carbon::setTestNow(now()); - - $entry = Entry::make() - ->slug('multi-day-event') - ->collection('events') - ->data([ - 'multi_day' => true, - 'days' => [ - [ - 'date' => now()->toDateString(), - 'start_time' => '19:00', - 'end_time' => '21:00', - ], - [ - 'date' => now()->addDay()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '15:00', - ], - [ - 'date' => now()->addDays(2)->toDateString(), - 'start_time' => '11:00', - 'end_time' => '15:00', - ], + +test('can generate dates when now before start', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'end_date' => Carbon::now()->addDays(2)->toDateString(), + 'recurrence' => 'daily', + ])->save(); + + $event = tap(Entry::make() + ->collection('events') + ->slug('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '13:00', + ]))->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(now(), now()->addDays(2)->endOfDay()); + + $expectedStartDates = [ + now()->setTimeFromTimeString('11:00'), + now()->setTimeFromTimeString('13:00'), + now()->addDay()->setTimeFromTimeString('11:00'), + now()->addDays(2)->setTimeFromTimeString('11:00'), + ]; + expect($occurrences)->toHaveCount(4); + + expect($occurrences[0]->start)->toEqual($expectedStartDates[0]); + expect($occurrences[1]->start)->toEqual($expectedStartDates[1]); + expect($occurrences[2]->start)->toEqual($expectedStartDates[2]); + expect($occurrences[3]->start)->toEqual($expectedStartDates[3]); +}); + +test('can paginate upcoming occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->upcoming(10); + + expect($occurrences)->toHaveCount(10); + $paginator = Events::fromCollection(handle: 'events') + ->pagination(perPage: 2) + ->upcoming(10); + + expect($paginator)->toBeInstanceOf(LengthAwarePaginator::class); + expect($occurrences = $paginator->items())->toHaveCount(2); + expect($paginator->items()[1]->start)->toEqual(now()->addDay()->setTimeFromTimeString('11:00')); + + $paginator = Events::fromCollection(handle: 'events') + ->pagination(perPage: 3, page: 3) + ->upcoming(10); + + expect($paginator)->toBeInstanceOf(LengthAwarePaginator::class); + expect($occurrences = $paginator->items())->toHaveCount(3); + expect($paginator->currentPage())->toEqual(3); +}); + +test('can paginate occurrences between', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($occurrences)->toHaveCount(10); + $paginator = Events::fromCollection(handle: 'events') + ->pagination(perPage: 2) + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($paginator)->toBeInstanceOf(LengthAwarePaginator::class); + + expect($occurrences = $paginator->items())->toHaveCount(2); + + expect($paginator->items()[1]->start)->toEqual(now()->addDay()->setTimeFromTimeString('11:00')); +}); + +test('can filter events', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + Entry::make() + ->collection('events') + ->slug('other-event') + ->data([ + 'title' => 'Other Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->filter('title:contains', 'Other') + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($occurrences)->toHaveCount(10); +}); + +test('can filter multiple events', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + Entry::make() + ->collection('events') + ->slug('other-event') + ->data([ + 'title' => 'Other Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + Entry::make() + ->collection('events') + ->slug('other-event') + ->data([ + 'title' => 'Other Event 2', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'weekly', + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->filter('title:contains', 'Other') + ->filter('recurrence:is', 'daily') + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($occurrences)->toHaveCount(10); +}); + +test('can filter by term events', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + 'categories' => ['one'], + ])->save(); + + Entry::make() + ->collection('events') + ->slug('other-event') + ->data([ + 'title' => 'Other Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + 'categories' => ['two'], + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->terms('categories::two') + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($occurrences)->toHaveCount(10); +}); + +test('can filter by filter events', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + Entry::make() + ->collection('events') + ->slug('other-event') + ->data([ + 'title' => 'Other Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->filter('title:contains', 'Recurring') + ->between(now(), now()->addDays(9)->endOfDay()); + + expect($occurrences)->toHaveCount(10); +}); + +test('can determine occurs at for single event', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $entry = Entry::make() + ->collection('events') + ->slug('single-event') + ->id('the-id') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + ]); + + $event = EventFactory::createFromEntry($entry); + + expect($event->occursOnDate(now()))->toBeTrue(); +}); + +test('can determine occurs at for multiday event', function () { + Carbon::setTestNow(now()); + + $entry = Entry::make() + ->slug('multi-day-event') + ->collection('events') + ->data([ + 'multi_day' => true, + 'days' => [ + [ + 'date' => now()->toDateString(), + 'start_time' => '19:00', + 'end_time' => '21:00', + ], + [ + 'date' => now()->addDay()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '15:00', ], - ]); - - $event = EventFactory::createFromEntry($entry); - - $this->assertFalse($event->occursOnDate(now()->subDay())); - $this->assertTrue($event->occursOnDate(now())); - $this->assertTrue($event->occursOnDate(now()->addDay())); - $this->assertTrue($event->occursOnDate(now()->addDays(2))); - $this->assertFalse($event->occursOnDate(now()->addDays(3))); - } - - #[Test] - public function can_exclude_dates() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - 'exclude_dates' => [['date' => Carbon::now()->addDay()->toDateString()]], - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(now(), now()->addDays(3)->endOfDay()); - - $this->assertCount(3, $occurrences); - } - - #[Test] - public function can_handle_empty_exclude_dates() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - 'exclude_dates' => [['id' => 'random-id']], - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(now(), now()->addDays(3)->endOfDay()); - - $this->assertCount(4, $occurrences); - } - - #[Test] - public function can_filter_our_events_with_no_start_date() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->data([ - 'title' => 'Single Event', - 'start_time' => '11:00', - 'end_time' => '12:00', - ])->save(); - Entry::make() - ->collection('events') - ->slug('legacy-multi-day-event') - ->data([ - 'title' => 'Legacy Multi-day Event', - 'multi_day' => true, - 'days' => [ - ['date' => 'bad-date'], + [ + 'date' => now()->addDays(2)->toDateString(), + 'start_time' => '11:00', + 'end_time' => '15:00', ], - ])->save(); - Entry::make() - ->collection('events') - ->slug('legacy-multi-day-event-2') - ->data([ - 'title' => 'Legacy Multi-day Event', - 'multi_day' => true, - ])->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->upcoming(5); - - $this->assertEmpty($occurrences); - } -} + ], + ]); + + $event = EventFactory::createFromEntry($entry); + + expect($event->occursOnDate(now()->subDay()))->toBeFalse(); + expect($event->occursOnDate(now()))->toBeTrue(); + expect($event->occursOnDate(now()->addDay()))->toBeTrue(); + expect($event->occursOnDate(now()->addDays(2)))->toBeTrue(); + expect($event->occursOnDate(now()->addDays(3)))->toBeFalse(); +}); + +test('can exclude dates', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + 'exclude_dates' => [['date' => Carbon::now()->addDay()->toDateString()]], + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(now(), now()->addDays(3)->endOfDay()); + + expect($occurrences)->toHaveCount(3); +}); + +test('can handle empty exclude dates', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('recurring-event') + ->data([ + 'title' => 'Recurring Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + 'exclude_dates' => [['id' => 'random-id']], + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(now(), now()->addDays(3)->endOfDay()); + + expect($occurrences)->toHaveCount(4); +}); + +test('can filter our events with no start date', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->data([ + 'title' => 'Single Event', + 'start_time' => '11:00', + 'end_time' => '12:00', + ])->save(); + Entry::make() + ->collection('events') + ->slug('legacy-multi-day-event') + ->data([ + 'title' => 'Legacy Multi-day Event', + 'multi_day' => true, + 'days' => [ + ['date' => 'bad-date'], + ], + ])->save(); + Entry::make() + ->collection('events') + ->slug('legacy-multi-day-event-2') + ->data([ + 'title' => 'Legacy Multi-day Event', + 'multi_day' => true, + ])->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->upcoming(5); + + expect($occurrences)->toBeEmpty(); +}); diff --git a/tests/Unit/MultiDayEventsTest.php b/tests/Unit/MultiDayEventsTest.php index eca8184..b1b80e6 100755 --- a/tests/Unit/MultiDayEventsTest.php +++ b/tests/Unit/MultiDayEventsTest.php @@ -1,177 +1,117 @@ slug('multi-day-event') - ->collection('events') - ->data([ - 'recurrence' => 'multi_day', - 'days' => [ - [ - 'date' => '2019-11-24', - 'start_time' => '11:00', - 'end_time' => '15:00', - ], - [ - 'date' => '2019-11-25', - 'start_time' => '11:00', - 'end_time' => '15:00', - ], - [ - 'date' => '2019-11-23', - 'start_time' => '19:00', - 'end_time' => '21:00', - ], +beforeEach(function () { + Carbon::setTestNowAndTimezone(now(), 'America/Vancouver'); + + $entry = Entry::make() + ->slug('multi-day-event') + ->collection('events') + ->data([ + 'recurrence' => 'multi_day', + 'days' => [ + [ + 'date' => '2019-11-24', + 'start_time' => '11:00', + 'end_time' => '15:00', + ], + [ + 'date' => '2019-11-25', + 'start_time' => '11:00', + 'end_time' => '15:00', + ], + [ + 'date' => '2019-11-23', + 'start_time' => '19:00', + 'end_time' => '21:00', + ], + ], + 'timezone' => 'America/Vancouver', + ]); + + $this->event = EventFactory::createFromEntry($entry); + + $noEndTimeEntry = Entry::make() + ->collection('events') + ->slug('no-end-time') + ->data([ + 'recurrence' => 'multi_day', + 'days' => [ + [ + 'date' => '2019-11-23', + 'start_time' => '19:00', + ], + [ + 'date' => '2019-11-24', + 'start_time' => '15:00', ], - 'timezone' => 'America/Vancouver', - ]); - - $this->event = EventFactory::createFromEntry($entry); - - $noEndTimeEntry = Entry::make() - ->collection('events') - ->slug('no-end-time') - ->data([ - 'recurrence' => 'multi_day', - 'days' => [ - [ - 'date' => '2019-11-23', - 'start_time' => '19:00', - ], - [ - 'date' => '2019-11-24', - 'start_time' => '15:00', - ], + ], + 'timezone' => 'America/Vancouver', + ]); + + $this->noEndTimeEvent = EventFactory::createFromEntry($noEndTimeEntry); + + $allDayEntry = Entry::make() + ->collection('events') + ->data([ + 'recurrence' => 'multi_day', + 'days' => [ + [ + 'date' => '2019-11-20', ], - 'timezone' => 'America/Vancouver', - ]); - - $this->noEndTimeEvent = EventFactory::createFromEntry($noEndTimeEntry); - - $allDayEntry = Entry::make() - ->collection('events') - ->data([ - 'recurrence' => 'multi_day', - 'days' => [ - [ - 'date' => '2019-11-20', - ], - [ - 'date' => '2019-11-21', - ], + [ + 'date' => '2019-11-21', ], - 'timezone' => 'America/Vancouver', - ]); - $this->allDayEvent = EventFactory::createFromEntry($allDayEntry); - } - - #[Test] - public function canCreateMultiDayEvent() - { - $this->assertTrue($this->event instanceof MultiDayEvent); - $this->assertTrue($this->allDayEvent instanceof MultiDayEvent); - $this->assertTrue($this->noEndTimeEvent instanceof MultiDayEvent); - $this->assertTrue($this->event->isMultiDay()); - $this->assertTrue($this->allDayEvent->isMultiDay()); - $this->assertTrue($this->noEndTimeEvent->isMultiDay()); - } - - #[Test] - public function canGetStart() - { - $this->assertEquals( - Carbon::parse('2019-11-23 19:00')->shiftTimezone('America/Vancouver'), - $this->event->start() - ); - $this->assertEquals( - Carbon::parse('2019-11-20 0:00')->shiftTimezone('America/Vancouver'), - $this->allDayEvent->start() - ); - $this->assertEquals( - Carbon::parse('2019-11-20 0:00')->shiftTimezone('America/Vancouver')->timezone, - $this->event->start()->timezone - ); - } - - #[Test] - public function canGetEnd() - { - $this->assertEquals( - Carbon::parse('2019-11-25 15:00')->shiftTimezone('America/Vancouver'), - $this->event->end() - ); - $this->assertEquals( - Carbon::parse('2019-11-21 23:59:59.999999')->shiftTimezone('America/Vancouver'), - $this->allDayEvent->end() - ); - $this->assertEquals( - Carbon::parse('2019-11-21 23:59:00')->shiftTimezone('America/Vancouver')->timezone, - $this->event->end()->timezone - ); - } - - #[Test] - public function noOccurrencesIfNowAfterEndDate() - { - Carbon::setTestNow('2019-11-26'); - $this->assertEmpty($this->event->nextOccurrences(1)); - } - - #[Test] - public function canGenerateNextOccurrenceIfBefore() - { - Carbon::setTestNowAndTimezone('2019-11-22', 'America/Vancouver'); - - $this->assertEquals( - Carbon::parse('2019-11-23')->setTimeFromTimeString('19:00:00'), - $this->event->nextOccurrences()[0]->start - ); - $this->assertEquals( - Carbon::parse('2019-11-23')->setTimeFromTimeString('21:00'), - $this->event->nextOccurrences()[0]->end - ); - } - - #[Test] - public function canGenerateNextOccurrenceIfDuring() - { - Carbon::setTestNowAndTimezone('2019-11-24 10:00', 'America/Vancouver'); - $this->assertEquals( - Carbon::parse('2019-11-24')->setTimeFromTimeString('11:00:00'), - $this->event->nextOccurrences()[0]->start - ); - } - - #[Test] - public function dayIsAllDayWhenNoStartAndEndTime() - { - $days = $this->allDayEvent->days(); - - $this->assertTrue($days[0]->isAllDay()); - } -} + ], + 'timezone' => 'America/Vancouver', + ]); + $this->allDayEvent = EventFactory::createFromEntry($allDayEntry); +}); + +test('can create multi day event', function () { + expect($this->event instanceof MultiDayEvent)->toBeTrue(); + expect($this->allDayEvent instanceof MultiDayEvent)->toBeTrue(); + expect($this->noEndTimeEvent instanceof MultiDayEvent)->toBeTrue(); + expect($this->event->isMultiDay())->toBeTrue(); + expect($this->allDayEvent->isMultiDay())->toBeTrue(); + expect($this->noEndTimeEvent->isMultiDay())->toBeTrue(); +}); + +test('can get start', function () { + expect($this->event->start())->toEqual(Carbon::parse('2019-11-23 19:00')->shiftTimezone('America/Vancouver')); + expect($this->allDayEvent->start())->toEqual(Carbon::parse('2019-11-20 0:00')->shiftTimezone('America/Vancouver')); + expect($this->event->start()->timezone)->toEqual(Carbon::parse('2019-11-20 0:00')->shiftTimezone('America/Vancouver')->timezone); +}); + +test('can get end', function () { + expect($this->event->end())->toEqual(Carbon::parse('2019-11-25 15:00')->shiftTimezone('America/Vancouver')); + expect($this->allDayEvent->end())->toEqual(Carbon::parse('2019-11-21 23:59:59.999999')->shiftTimezone('America/Vancouver')); + expect($this->event->end()->timezone)->toEqual(Carbon::parse('2019-11-21 23:59:00')->shiftTimezone('America/Vancouver')->timezone); +}); + +test('no occurrences if now after end date', function () { + Carbon::setTestNow('2019-11-26'); + expect($this->event->nextOccurrences(1))->toBeEmpty(); +}); + +test('can generate next occurrence if before', function () { + Carbon::setTestNowAndTimezone('2019-11-22', 'America/Vancouver'); + + expect($this->event->nextOccurrences()[0]->start)->toEqual(Carbon::parse('2019-11-23')->setTimeFromTimeString('19:00:00')); + expect($this->event->nextOccurrences()[0]->end)->toEqual(Carbon::parse('2019-11-23')->setTimeFromTimeString('21:00')); +}); + +test('can generate next occurrence if during', function () { + Carbon::setTestNowAndTimezone('2019-11-24 10:00', 'America/Vancouver'); + expect($this->event->nextOccurrences()[0]->start)->toEqual(Carbon::parse('2019-11-24')->setTimeFromTimeString('11:00:00')); +}); + +test('day is all day when no start and end time', function () { + $days = $this->allDayEvent->days(); + + expect($days[0]->isAllDay())->toBeTrue(); +}); diff --git a/tests/Unit/RecurringDailyEventsTest.php b/tests/Unit/RecurringDailyEventsTest.php index d2ffb50..5feb2b9 100755 --- a/tests/Unit/RecurringDailyEventsTest.php +++ b/tests/Unit/RecurringDailyEventsTest.php @@ -1,275 +1,216 @@ collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'end_date' => Carbon::now()->addDays(2)->toDateString(), - 'recurrence' => 'daily', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - Carbon::setTestNow(now()->addDays(3)); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertEmpty($nextOccurrences); - } - - #[Test] - public function canGenerateNextDayIfNowIsBefore() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - Carbon::setTestNow($startDate->setTimeFromTimeString('10:59:00')); - - $nextOccurrences = $event->nextOccurrences(3); - - $this->assertCount(3, $nextOccurrences); - - $this->assertEquals($startDate, $nextOccurrences->first()->start); - } - - #[Test] - public function canGenerateNextOccurrenceIfNowIsDuring() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'daily', - ]); - - Carbon::setTestNow($startDate->addMinutes(10)); - - $event = EventFactory::createFromEntry($recurringEntry); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertEquals($startDate, $nextOccurrences[0]->start); - } - - // public function test_can_generate_next_day_if_after() - // { - // $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - - // $event = [ - // 'start_date' => $startDate->toDateString(), - // 'start_time' => '11:00', - // 'end_time' => '12:00', - // 'recurrence' => 'daily', - // ]; - - // Carbon::setTestNow($startDate->addMinute()); - - // $event = EventFactory::createFromArray($event); - - // $nextOccurrences = $event->nextOccurrences(1); - - // $this->assertEquals($startDate->addDay(), $nextDate->start()); - // } - - // public function test_can_generate_next_x_dates_from_today_before_event_time() - // { - // $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); - // $event = EventFactory::createFromArray( - // [ - // 'start_date' => $startDate->toDateString(), - // 'start_time' => '11:00', - // 'end_time' => '12:00', - // 'recurrence' => 'daily', - // ] - // ); - - // for ($x = 0; $x < 2; $x++) { - // $events[] = $startDate->copy()->addDays($x); - // } - - // $this->events->add($event); - - // Carbon::setTestNow($startDate->copy()->subMinutes(1)); - - // $nextDates = $this->events->upcoming(2); - - // $this->assertCount(2, $nextDates); - - // $this->assertEquals($events[0], $nextDates[0]->start()); - // $this->assertEquals($events[1], $nextDates[1]->start()); - // } - - // public function test_can_generate_next_x_dates_from_today() - // { - // $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); - // $event = EventFactory::createFromArray([ - // 'start_date' => $startDate->toDateString(), - // 'start_time' => '11:00', - // 'end_time' => '12:00', - // 'recurrence' => 'daily', - // ]); - - // for ($x = 0; $x < 3; $x++) { - // $events[] = $startDate->copy()->addDays($x); - // } - - // $this->events->add($event); - - // Carbon::setTestNow($startDate->copy()->addMinutes(1)); - - // $nextDates = $this->events->upcoming(3); - - // $this->assertCount(3, $nextDates); - - // $this->assertEquals($events[0], $nextDates[0]->start()); - // $this->assertEquals($events[1], $nextDates[1]->start()); - // $this->assertEquals($events[2], $nextDates[2]->start()); - // } - - // public function test_generates_all_occurrences_when_daily_after_start_date() - // { - // $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); - - // $event = EventFactory::createFromArray( - // [ - // 'start_date' => $startDate->copy()->addDay()->toDateString(), - // 'start_time' => '11:00', - // 'end_time' => '12:00', - // 'end_date' => $startDate->copy()->addDays(3)->toDateString(), - // 'recurrence' => 'daily', - // ] - // ); - - // for ($x = 2; $x <= 3; $x++) { - // $events[] = $startDate->copy()->addDays($x); - // } - - // $this->events->add($event); - - // Carbon::setTestNow($startDate->copy()->addDays(1)->addHour(1)); - // $nextEvents = $this->events->upcoming(3); - - // $this->assertCount(2, $nextEvents); - - // $this->assertEquals($events[0], $nextEvents[0]->start()); - // $this->assertEquals($events[1], $nextEvents[1]->start()); - // } - - // public function test_can_get_last_day_when_before() - // { - // Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); - - // $this->events->add(EventFactory::createFromArray([ - // 'id' => 'daily-event', - // 'start_date' => Carbon::now()->toDateString(), - // 'start_time' => '13:00', - // 'end_time' => '15:00', - // 'recurrence' => 'daily', - // 'end_date' => Carbon::now()->addDays(7)->toDateString(), - // ])); - - // $from = Carbon::now()->addDays(7); - // $to = Carbon::now()->endOfDay()->addDays(10); - - // $events = $this->events->all($from, $to); - - // $this->assertCount(1, $events); - // } - - // public function test_generates_all_daily_occurrences_single_event_from_to() - // { - // Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); - - // $this->events->add(EventFactory::createFromArray([ - // 'id' => 'daily-event', - // 'start_date' => Carbon::now()->toDateString(), - // 'start_time' => '13:00', - // 'end_time' => '15:00', - // 'recurrence' => 'daily', - // 'end_date' => Carbon::now()->addDays(7)->toDateString(), - // ])); - - // $from = Carbon::now()->subDays(1); - // $to = Carbon::now()->endOfDay()->addDays(10); - - // $events = $this->events->all($from, $to); - - // $this->assertCount(8, $events); - // } - - // public function test_generates_all_daily_occurrences_single_event_from_to_without_end_date() - // { - // Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); - - // $this->events->add(EventFactory::createFromArray([ - // 'id' => 'daily-event', - // 'start_date' => Carbon::now()->toDateString(), - // 'start_time' => '13:00', - // 'end_time' => '15:00', - // 'recurrence' => 'daily', - // ])); - - // $from = Carbon::now()->subDays(1); - // $to = Carbon::now()->endOfDay()->addDays(10); - - // $events = $this->events->all($from, $to); - - // $this->assertCount(11, $events); - // } - - // public function test_can_exclude_dates() - // { - // Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); - - // $this->events->add(EventFactory::createFromArray([ - // 'id' => 'daily-event', - // 'start_date' => Carbon::now()->toDateString(), - // 'start_time' => '13:00', - // 'end_time' => '15:00', - // 'recurrence' => 'daily', - // 'except' => [ - // ['date' => Carbon::now()->addDays(2)->toDateString()], - // ['date' => Carbon::now()->addDays(4)->toDateString()], - // ], - // ])); - - // $from = Carbon::now()->subDays(1); - // $to = Carbon::now()->endOfDay()->addDays(5); - - // $events = $this->events->all($from, $to)->toArray(); - - // $this->assertCount(4, $events); - // $this->assertEquals(Carbon::now()->toDateString(), $events[0]['start_date']); - // $this->assertEquals(Carbon::now()->addDays(1)->toDateString(), $events[1]['start_date']); - // $this->assertEquals(Carbon::now()->addDays(3)->toDateString(), $events[2]['start_date']); - // $this->assertEquals(Carbon::now()->addDays(5)->toDateString(), $events[3]['start_date']); - // } -} +test('null next date if now after end date', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'end_date' => Carbon::now()->addDays(2)->toDateString(), + 'recurrence' => 'daily', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + Carbon::setTestNow(now()->addDays(3)); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences)->toBeEmpty(); +}); + +test('can generate next day if now is before', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + Carbon::setTestNow($startDate->setTimeFromTimeString('10:59:00')); + + $nextOccurrences = $event->nextOccurrences(3); + + expect($nextOccurrences)->toHaveCount(3); + + expect($nextOccurrences->first()->start)->toEqual($startDate); +}); + +test('can generate next occurrence if now is during', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'daily', + ]); + + Carbon::setTestNow($startDate->addMinutes(10)); + + $event = EventFactory::createFromEntry($recurringEntry); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences[0]->start)->toEqual($startDate); +}); + +// public function test_can_generate_next_day_if_after() +// { +// $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); +// $event = [ +// 'start_date' => $startDate->toDateString(), +// 'start_time' => '11:00', +// 'end_time' => '12:00', +// 'recurrence' => 'daily', +// ]; +// Carbon::setTestNow($startDate->addMinute()); +// $event = EventFactory::createFromArray($event); +// $nextOccurrences = $event->nextOccurrences(1); +// $this->assertEquals($startDate->addDay(), $nextDate->start()); +// } +// public function test_can_generate_next_x_dates_from_today_before_event_time() +// { +// $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); +// $event = EventFactory::createFromArray( +// [ +// 'start_date' => $startDate->toDateString(), +// 'start_time' => '11:00', +// 'end_time' => '12:00', +// 'recurrence' => 'daily', +// ] +// ); +// for ($x = 0; $x < 2; $x++) { +// $events[] = $startDate->copy()->addDays($x); +// } +// $this->events->add($event); +// Carbon::setTestNow($startDate->copy()->subMinutes(1)); +// $nextDates = $this->events->upcoming(2); +// $this->assertCount(2, $nextDates); +// $this->assertEquals($events[0], $nextDates[0]->start()); +// $this->assertEquals($events[1], $nextDates[1]->start()); +// } +// public function test_can_generate_next_x_dates_from_today() +// { +// $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); +// $event = EventFactory::createFromArray([ +// 'start_date' => $startDate->toDateString(), +// 'start_time' => '11:00', +// 'end_time' => '12:00', +// 'recurrence' => 'daily', +// ]); +// for ($x = 0; $x < 3; $x++) { +// $events[] = $startDate->copy()->addDays($x); +// } +// $this->events->add($event); +// Carbon::setTestNow($startDate->copy()->addMinutes(1)); +// $nextDates = $this->events->upcoming(3); +// $this->assertCount(3, $nextDates); +// $this->assertEquals($events[0], $nextDates[0]->start()); +// $this->assertEquals($events[1], $nextDates[1]->start()); +// $this->assertEquals($events[2], $nextDates[2]->start()); +// } +// public function test_generates_all_occurrences_when_daily_after_start_date() +// { +// $startDate = Carbon::now()->setTimeFromTimeString('11:00:00'); +// $event = EventFactory::createFromArray( +// [ +// 'start_date' => $startDate->copy()->addDay()->toDateString(), +// 'start_time' => '11:00', +// 'end_time' => '12:00', +// 'end_date' => $startDate->copy()->addDays(3)->toDateString(), +// 'recurrence' => 'daily', +// ] +// ); +// for ($x = 2; $x <= 3; $x++) { +// $events[] = $startDate->copy()->addDays($x); +// } +// $this->events->add($event); +// Carbon::setTestNow($startDate->copy()->addDays(1)->addHour(1)); +// $nextEvents = $this->events->upcoming(3); +// $this->assertCount(2, $nextEvents); +// $this->assertEquals($events[0], $nextEvents[0]->start()); +// $this->assertEquals($events[1], $nextEvents[1]->start()); +// } +// public function test_can_get_last_day_when_before() +// { +// Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); +// $this->events->add(EventFactory::createFromArray([ +// 'id' => 'daily-event', +// 'start_date' => Carbon::now()->toDateString(), +// 'start_time' => '13:00', +// 'end_time' => '15:00', +// 'recurrence' => 'daily', +// 'end_date' => Carbon::now()->addDays(7)->toDateString(), +// ])); +// $from = Carbon::now()->addDays(7); +// $to = Carbon::now()->endOfDay()->addDays(10); +// $events = $this->events->all($from, $to); +// $this->assertCount(1, $events); +// } +// public function test_generates_all_daily_occurrences_single_event_from_to() +// { +// Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); +// $this->events->add(EventFactory::createFromArray([ +// 'id' => 'daily-event', +// 'start_date' => Carbon::now()->toDateString(), +// 'start_time' => '13:00', +// 'end_time' => '15:00', +// 'recurrence' => 'daily', +// 'end_date' => Carbon::now()->addDays(7)->toDateString(), +// ])); +// $from = Carbon::now()->subDays(1); +// $to = Carbon::now()->endOfDay()->addDays(10); +// $events = $this->events->all($from, $to); +// $this->assertCount(8, $events); +// } +// public function test_generates_all_daily_occurrences_single_event_from_to_without_end_date() +// { +// Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); +// $this->events->add(EventFactory::createFromArray([ +// 'id' => 'daily-event', +// 'start_date' => Carbon::now()->toDateString(), +// 'start_time' => '13:00', +// 'end_time' => '15:00', +// 'recurrence' => 'daily', +// ])); +// $from = Carbon::now()->subDays(1); +// $to = Carbon::now()->endOfDay()->addDays(10); +// $events = $this->events->all($from, $to); +// $this->assertCount(11, $events); +// } +// public function test_can_exclude_dates() +// { +// Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); +// $this->events->add(EventFactory::createFromArray([ +// 'id' => 'daily-event', +// 'start_date' => Carbon::now()->toDateString(), +// 'start_time' => '13:00', +// 'end_time' => '15:00', +// 'recurrence' => 'daily', +// 'except' => [ +// ['date' => Carbon::now()->addDays(2)->toDateString()], +// ['date' => Carbon::now()->addDays(4)->toDateString()], +// ], +// ])); +// $from = Carbon::now()->subDays(1); +// $to = Carbon::now()->endOfDay()->addDays(5); +// $events = $this->events->all($from, $to)->toArray(); +// $this->assertCount(4, $events); +// $this->assertEquals(Carbon::now()->toDateString(), $events[0]['start_date']); +// $this->assertEquals(Carbon::now()->addDays(1)->toDateString(), $events[1]['start_date']); +// $this->assertEquals(Carbon::now()->addDays(3)->toDateString(), $events[2]['start_date']); +// $this->assertEquals(Carbon::now()->addDays(5)->toDateString(), $events[3]['start_date']); +// } diff --git a/tests/Unit/RecurringEventsTest.php b/tests/Unit/RecurringEventsTest.php index 74b1887..f3dcf8d 100755 --- a/tests/Unit/RecurringEventsTest.php +++ b/tests/Unit/RecurringEventsTest.php @@ -1,94 +1,79 @@ collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'recurrence' => 'daily', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - $this->assertTrue($event instanceof RecurringEvent); - $this->assertTrue($event->isRecurring()); - $this->assertFalse($event->isMultiDay()); - } - - #[Test] - public function wont_create_recurring_event_when_multi_day() - { - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'recurrence' => 'multi_day', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - $this->assertTrue($event instanceof SingleDayEvent); - $this->assertFalse($event->isRecurring()); - $this->assertFalse($event->isMultiDay()); - } - - #[Test] - public function can_show_last_occurrence_when_no_end_time() - { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $recurringEntry = tap(Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->addDays(1)->toDateString(), - 'start_time' => '22:00', - 'recurrence' => 'daily', - 'end_date' => Carbon::now()->addDays(2)->toDateString(), - 'timezone' => 'America/Chicago', - ]))->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(Carbon::now(), Carbon::now()->addDays(5)->endOfDay()); - - $this->assertCount(2, $occurrences); - } - - #[Test] - public function can_generate_monthly_by_day_occurrences() - { - Carbon::setTestNow(Carbon::parse('Jan 29 2025 10:00am')); - - $recurringEntry = tap(Entry::make() - ->collection('events') - ->data([ - 'start_date' => ray()->pass(Carbon::now()->addDays(1)->toDateString()), - 'start_time' => '22:00', - 'recurrence' => 'monthly', - 'end_date' => ray()->pass(Carbon::now()->addMonths(3)->toDateString()), - 'timezone' => 'America/Chicago', - 'specific_days' => ['first_sunday', 'last_wednesday'], - ]))->save(); - - $occurrences = Events::fromCollection(handle: 'events') - ->between(Carbon::now(), Carbon::now()->addMonths(4)); - - $this->assertCount(5, $occurrences); - } -} +test('can create recurring event', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'recurrence' => 'daily', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + expect($event instanceof RecurringEvent)->toBeTrue(); + expect($event->isRecurring())->toBeTrue(); + expect($event->isMultiDay())->toBeFalse(); +}); + +test('wont create recurring event when multi day', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'recurrence' => 'multi_day', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + expect($event instanceof SingleDayEvent)->toBeTrue(); + expect($event->isRecurring())->toBeFalse(); + expect($event->isMultiDay())->toBeFalse(); +}); + +test('can show last occurrence when no end time', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $recurringEntry = tap(Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->addDays(1)->toDateString(), + 'start_time' => '22:00', + 'recurrence' => 'daily', + 'end_date' => Carbon::now()->addDays(2)->toDateString(), + 'timezone' => 'America/Chicago', + ]))->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(Carbon::now(), Carbon::now()->addDays(5)->endOfDay()); + + expect($occurrences)->toHaveCount(2); +}); + +test('can generate monthly by day occurrences', function () { + Carbon::setTestNow(Carbon::parse('Jan 29 2025 10:00am')); + + $recurringEntry = tap(Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->addDays(1)->toDateString(), + 'start_time' => '22:00', + 'recurrence' => 'monthly', + 'end_date' => Carbon::now()->addMonths(3)->toDateString(), + 'timezone' => 'America/Chicago', + 'specific_days' => ['first_sunday', 'last_wednesday'], + ]))->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(Carbon::now(), Carbon::now()->addMonths(4)); + + expect($occurrences)->toHaveCount(5); +}); diff --git a/tests/Unit/RecurringEveryXEventsTest.php b/tests/Unit/RecurringEveryXEventsTest.php index e05d604..64e2ba8 100755 --- a/tests/Unit/RecurringEveryXEventsTest.php +++ b/tests/Unit/RecurringEveryXEventsTest.php @@ -1,276 +1,251 @@ collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'weeks', - ]); +test('can create every xevent', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'weeks', + ]); - $event = EventFactory::createFromEntry($recurringEntry); + $event = EventFactory::createFromEntry($recurringEntry); - $this->assertInstanceOf(RecurringEvent::class, $event); - } + expect($event)->toBeInstanceOf(RecurringEvent::class); +}); - #[Test] - public function noOccurencesWhenNowAfterEndDate() - { - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'end_date' => Carbon::now()->addDays(2)->toDateString(), - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); +test('no occurences when now after end date', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'end_date' => Carbon::now()->addDays(2)->toDateString(), + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); - $event = EventFactory::createFromEntry($recurringEntry); + $event = EventFactory::createFromEntry($recurringEntry); - Carbon::setTestNow(now()->addDays(3)); - $nextOccurrences = $event->nextOccurrences(); + Carbon::setTestNow(now()->addDays(3)); + $nextOccurrences = $event->nextOccurrences(); - $this->assertEmpty($nextOccurrences); - } + expect($nextOccurrences)->toBeEmpty(); +}); - #[Test] - public function canGenerateOccurrenceIfNowBefore() - { - $startDate = Carbon::now()->addDay()->setTimeFromTimeString('11:00'); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); +test('can generate occurrence if now before', function () { + $startDate = Carbon::now()->addDay()->setTimeFromTimeString('11:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); - $occurrences = EventFactory::createFromEntry($recurringEntry) - ->nextOccurrences(1); + $occurrences = EventFactory::createFromEntry($recurringEntry) + ->nextOccurrences(1); - $this->assertCount(1, $occurrences); + expect($occurrences)->toHaveCount(1); - $this->assertEquals($startDate, $occurrences[0]->start); + expect($occurrences[0]->start)->toEqual($startDate); - Carbon::setTestNow(now()->setTimeFromTimeString('10:59:59')); - $occurrences = EventFactory::createFromEntry($recurringEntry) - ->nextOccurrences(1); + Carbon::setTestNow(now()->setTimeFromTimeString('10:59:59')); + $occurrences = EventFactory::createFromEntry($recurringEntry) + ->nextOccurrences(1); - $this->assertEquals($startDate, $occurrences[0]->start); - } + expect($occurrences[0]->start)->toEqual($startDate); +}); - #[Test] - public function canGenerateOccurrenceIfDuring() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); +test('can generate occurrence if during', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); - Carbon::setTestNow($startDate->addMinutes(10)); - $occurrences = EventFactory::createFromEntry($recurringEntry) - ->nextOccurrences(1); + Carbon::setTestNow($startDate->addMinutes(10)); + $occurrences = EventFactory::createFromEntry($recurringEntry) + ->nextOccurrences(1); - $this->assertEquals($startDate, $occurrences[0]->start); - } + expect($occurrences[0]->start)->toEqual($startDate); +}); - #[Test] - public function canGenerateOccurrenceIfNowAfterFirstDate() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); +test('can generate occurrence if now after first date', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); - Carbon::setTestNow($startDate->addMinutes(1)->addHour()); + Carbon::setTestNow($startDate->addMinutes(1)->addHour()); - $event = EventFactory::createFromEntry($recurringEntry); + $event = EventFactory::createFromEntry($recurringEntry); - $occurrences = $event->nextOccurrences(1); + $occurrences = $event->nextOccurrences(1); - $this->assertEquals($startDate->addDays(2), $occurrences[0]->start); + expect($occurrences[0]->start)->toEqual($startDate->addDays(2)); - // $nextDate = $event->upcomingDate(Carbon::now()->addDays(2)); + // $nextDate = $event->upcomingDate(Carbon::now()->addDays(2)); + // $this->assertEquals($startDate, $nextDate->start()); +}); - // $this->assertEquals($startDate, $nextDate->start()); - } +test('can generate next occurrence in weeks if now after start', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - #[Test] - public function canGenerateNextOccurrenceInWeeksIfNowAfterStart() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'weeks', + ]); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'weeks', - ]); + Carbon::setTestNow($startDate->addHours(2)); - Carbon::setTestNow($startDate->addHours(2)); + $event = EventFactory::createFromEntry($recurringEntry); - $event = EventFactory::createFromEntry($recurringEntry); + $occurrences = $event->nextOccurrences(1); - $occurrences = $event->nextOccurrences(1); + expect($occurrences[0]->start)->toEqual($startDate->addWeeks(2)); +}); - $this->assertEquals($startDate->addWeeks(2), $occurrences[0]->start); - } +test('can generate next occurrence if now after weeks', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => '2021-01-18', + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'weeks', + ]); - #[Test] - public function canGenerateNextOccurrenceIfNow_after_weeks() - { - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => '2021-01-18', - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'weeks', - ]); + $event = EventFactory::createFromEntry($recurringEntry); - $event = EventFactory::createFromEntry($recurringEntry); + Carbon::setTestNow(Carbon::parse('2021-03-04')->setTimeFromTimeString('11:00:00')); - Carbon::setTestNow(Carbon::parse('2021-03-04')->setTimeFromTimeString('11:00:00')); + $occurrences = $event->nextOccurrences(1); - $occurrences = $event->nextOccurrences(1); + expect($occurrences)->not->toBeEmpty(); - $this->assertNotEmpty($occurrences); + expect($occurrences[0]->start)->toEqual(Carbon::parse('2021-03-15')->setTimeFromTimeString('11:00:00')); +}); - $this->assertEquals(Carbon::parse('2021-03-15')->setTimeFromTimeString('11:00:00'), $occurrences[0]->start); - } +test('can generate next occurrence if now during months', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - #[Test] - public function canGenerateNextOccurrenceIfNowDuringMonths() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'months', + ]); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'months', - ]); + Carbon::setTestNow($startDate->addMinutes(1)); - Carbon::setTestNow($startDate->addMinutes(1)); + $event = EventFactory::createFromEntry($recurringEntry); - $event = EventFactory::createFromEntry($recurringEntry); + $occurrences = $event->nextOccurrences(1); - $occurrences = $event->nextOccurrences(1); + expect($occurrences[0]->start)->toEqual($startDate->setTimeFromTimeString('11:00:00')); +}); - $this->assertEquals($startDate->setTimeFromTimeString('11:00:00'), $occurrences[0]->start); +test('can generate next xoccurrences from today before event time', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); + $event = EventFactory::createFromEntry($recurringEntry); + + for ($x = 0; $x < 2; $x++) { + $events[] = $startDate->addDays($x * 2); } - #[Test] - public function canGenerateNextXOccurrencesFromTodayBeforeEventTime() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); - $event = EventFactory::createFromEntry($recurringEntry); + Carbon::setTestNow($startDate->subMinutes(1)); - for ($x = 0; $x < 2; $x++) { - $events[] = $startDate->addDays($x * 2); - } + $occurrences = $event->nextOccurrences(2); - Carbon::setTestNow($startDate->subMinutes(1)); + expect($occurrences)->toHaveCount(2); - $occurrences = $event->nextOccurrences(2); + expect($occurrences[0]->start)->toEqual($events[0]); + expect($occurrences[1]->start)->toEqual($events[1]); +}); - $this->assertCount(2, $occurrences); +test('can generate all occurrences when after start date daily', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); - $this->assertEquals($events[0], $occurrences[0]->start); - $this->assertEquals($events[1], $occurrences[1]->start); - } - - #[Test] - public function canGenerateAllOccurrencesWhenAfterStartDateDaily() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00:00'); + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->addDay()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'end_date' => $startDate->addDays(5)->toDateString(), + 'recurrence' => 'every', + 'interval' => 2, + 'period' => 'days', + ]); - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->addDay()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'end_date' => $startDate->addDays(5)->toDateString(), - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - ]); + for ($x = 1; $x <= 2; $x++) { + $events[] = $startDate->addDays($x * 2 + 1); + } - for ($x = 1; $x <= 2; $x++) { - $events[] = $startDate->addDays($x * 2 + 1); - } + $event = EventFactory::createFromEntry($recurringEntry); - $event = EventFactory::createFromEntry($recurringEntry); + Carbon::setTestNow($startDate->addDays(1)->addHour(2)); + $occurrences = $event->nextOccurrences(5); - Carbon::setTestNow($startDate->addDays(1)->addHour(2)); - $occurrences = $event->nextOccurrences(5); + expect($occurrences)->toHaveCount(2); - $this->assertCount(2, $occurrences); + expect($occurrences[0]->start)->toEqual($events[0]); + expect($occurrences[1]->start)->toEqual($events[1]); +}); - $this->assertEquals($events[0], $occurrences[0]->start); - $this->assertEquals($events[1], $occurrences[1]->start); - } - /* +/* public function test_can_get_last_day_when_before() { Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); @@ -386,4 +361,3 @@ public function test_returns_null_when_dates_between_dont_have_event() $this->assertEmpty($dates); } */ -} diff --git a/tests/Unit/SingleDayEventsTest.php b/tests/Unit/SingleDayEventsTest.php index 7e1227c..a9895b3 100755 --- a/tests/Unit/SingleDayEventsTest.php +++ b/tests/Unit/SingleDayEventsTest.php @@ -1,165 +1,144 @@ collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'timezone' => 'America/Vancouver', - ]); - - $event = EventFactory::createFromEntry($entry); - - $this->assertTrue($event instanceof SingleDayEvent); - $this->assertFalse($event->isRecurring()); - $this->assertFalse($event->isMultiDay()); - $this->assertTrue($event->hasEndTime()); - $this->assertEquals(new CarbonTimeZone('America/Vancouver'), $event->start()->timezone); - $this->assertEquals(new CarbonTimeZone('America/Vancouver'), $event->end()->timezone); - } - - #[Test] - public function canCreateSingleAllDayEvent() - { - $entry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'all_day' => true, - ]); - - $event = EventFactory::createFromEntry($entry); - - $this->assertTrue($event instanceof SingleDayEvent); - $this->assertTrue($event->isAllDay()); - } - - #[Test] - public function endIsEndOfDayWhenNoEndTime() - { - Carbon::setTestNow(now()); - - $entry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - ]); - - $event = EventFactory::createFromEntry($entry); - - $this->assertEquals('23:59:59', $event->endTime()); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertFalse($nextOccurrences[0]->has_end_time); - $this->assertEquals(now()->endOfDay()->setMicrosecond(0), $nextOccurrences[0]->end); - } - - #[Test] - public function emptyOccurrencesIfNowAfterEndDate() - { - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - Carbon::setTestNow(now()->addDays(1)); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertEmpty($nextOccurrences); - } - - #[Test] - public function canGenerateNextDayIfNowIsBefore() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - - $recurringEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - ]); - - $event = EventFactory::createFromEntry($recurringEntry); - - Carbon::setTestNow($startDate->setTimeFromTimeString('10:59:00')); - - $nextOccurrences = $event->nextOccurrences(2); - - $this->assertCount(1, $nextOccurrences); - - $this->assertEquals($startDate, $nextOccurrences->first()->start); - } - - #[Test] - public function canGenerateNextOccurrenceIfNowIsDuring() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - $single = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - ]); - - $singleNoEndTime = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - ]); - - Carbon::setTestNow($startDate->addMinutes(10)); - - $event = EventFactory::createFromEntry($single); - $noEndTimeEvent = EventFactory::createFromEntry($singleNoEndTime); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertEquals($startDate, $nextOccurrences[0]->start); - $this->assertEquals($startDate, $noEndTimeEvent->nextOccurrences()[0]->start); - } - - #[Test] - public function canSupplementNoEndTime() - { - $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); - $noEndTimeEntry = Entry::make() - ->collection('events') - ->data([ - 'start_date' => $startDate->toDateString(), - 'start_time' => '11:00', - ]); - - Carbon::setTestNow($startDate->addMinutes(10)); - - $event = EventFactory::createFromEntry($noEndTimeEntry); - $nextOccurrences = $event->nextOccurrences(); - - $this->assertFalse($nextOccurrences[0]->has_end_time); - } -} +test('can create single event', function () { + $entry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + 'timezone' => 'America/Vancouver', + ]); + + $event = EventFactory::createFromEntry($entry); + + expect($event instanceof SingleDayEvent)->toBeTrue(); + expect($event->isRecurring())->toBeFalse(); + expect($event->isMultiDay())->toBeFalse(); + expect($event->hasEndTime())->toBeTrue(); + expect($event->start()->timezone)->toEqual(new CarbonTimeZone('America/Vancouver')); + expect($event->end()->timezone)->toEqual(new CarbonTimeZone('America/Vancouver')); +}); + +test('can create single all day event', function () { + $entry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'all_day' => true, + ]); + + $event = EventFactory::createFromEntry($entry); + + expect($event instanceof SingleDayEvent)->toBeTrue(); + expect($event->isAllDay())->toBeTrue(); +}); + +test('end is end of day when no end time', function () { + Carbon::setTestNow(now()); + + $entry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + ]); + + $event = EventFactory::createFromEntry($entry); + + expect($event->endTime())->toEqual('23:59:59'); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences[0]->has_end_time)->toBeFalse(); + expect($nextOccurrences[0]->end)->toEqual(now()->endOfDay()->setMicrosecond(0)); +}); + +test('empty occurrences if now after end date', function () { + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + Carbon::setTestNow(now()->addDays(1)); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences)->toBeEmpty(); +}); + +test('can generate next day if now is before', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + + $recurringEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + ]); + + $event = EventFactory::createFromEntry($recurringEntry); + + Carbon::setTestNow($startDate->setTimeFromTimeString('10:59:00')); + + $nextOccurrences = $event->nextOccurrences(2); + + expect($nextOccurrences)->toHaveCount(1); + + expect($nextOccurrences->first()->start)->toEqual($startDate); +}); + +test('can generate next occurrence if now is during', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + $single = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + 'end_time' => '12:00', + ]); + + $singleNoEndTime = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + ]); + + Carbon::setTestNow($startDate->addMinutes(10)); + + $event = EventFactory::createFromEntry($single); + $noEndTimeEvent = EventFactory::createFromEntry($singleNoEndTime); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences[0]->start)->toEqual($startDate); + expect($noEndTimeEvent->nextOccurrences()[0]->start)->toEqual($startDate); +}); + +test('can supplement no end time', function () { + $startDate = CarbonImmutable::now()->setTimeFromTimeString('11:00'); + $noEndTimeEntry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'start_time' => '11:00', + ]); + + Carbon::setTestNow($startDate->addMinutes(10)); + + $event = EventFactory::createFromEntry($noEndTimeEntry); + $nextOccurrences = $event->nextOccurrences(); + + expect($nextOccurrences[0]->has_end_time)->toBeFalse(); +}); From 03eec2e65c56e727f125b0b7f2312cce6855e2ea Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Fri, 24 Oct 2025 12:04:54 -0700 Subject: [PATCH 05/14] Use addon settings (#132) --- composer.json | 2 +- resources/blueprints/config.yaml | 18 ---- resources/blueprints/settings.yaml | 43 ++++++++++ src/Events.php | 21 ++++- src/Http/Controllers/IcsController.php | 2 +- src/ServiceProvider.php | 42 ++++++---- src/Tags/Events.php | 6 +- src/UpdateScripts/ConvertConfigToSettings.php | 84 +++++++++++++++++++ 8 files changed, 177 insertions(+), 41 deletions(-) delete mode 100644 resources/blueprints/config.yaml create mode 100644 resources/blueprints/settings.yaml create mode 100644 src/UpdateScripts/ConvertConfigToSettings.php diff --git a/composer.json b/composer.json index ae20d1c..0d1f108 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "rlanvin/php-rrule": "^2.3.1", "spatie/calendar-links": "^1.0", "spatie/icalendar-generator": "^2.3.3", - "statamic/cms": "6.0.0-alpha.4" + "statamic/cms": "^6.0.0-alpha.14" }, "require-dev": { "orchestra/testbench": "^9.2 || ^10.0", diff --git a/resources/blueprints/config.yaml b/resources/blueprints/config.yaml deleted file mode 100644 index 28d7083..0000000 --- a/resources/blueprints/config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -timezone: - display: Default Timezone - type: timezones - max_items: 1 - mode: typeahead - default: UTC -collection: - max_items: 1 - mode: select - type: collections - display: 'Events Collection' - icon: collections - localizable: false - listable: hidden - instructions_position: above - visibility: visible - replicator_preview: true - hide_display: false diff --git a/resources/blueprints/settings.yaml b/resources/blueprints/settings.yaml new file mode 100644 index 0000000..6334348 --- /dev/null +++ b/resources/blueprints/settings.yaml @@ -0,0 +1,43 @@ +tabs: + main: + display: Main + sections: + - + fields: + - + handle: collections + field: + type: grid + display: Collections + sortable: false + add_row: 'Add Collection' + full_width_setting: true + fields: + - + handle: collection + field: + type: collections + display: Collection + width: 50 + mode: select + max_items: 1 + - + handle: location_field + field: + type: text + display: 'Location Field' + width: 50 + default: location + default: + - + collection: events + location_field: location + - + handle: timezone + field: + mode: select + max_items: 1 + type: timezones + display: Timezone + full_width_setting: true + default: 'UTC' diff --git a/src/Events.php b/src/Events.php index 1779351..d05c350 100644 --- a/src/Events.php +++ b/src/Events.php @@ -4,10 +4,12 @@ use Carbon\CarbonInterface; use Exception; +use Illuminate\Support\Collection; use Illuminate\Support\Traits\Conditionable; use Statamic\Entries\Entry; use Statamic\Entries\EntryCollection; use Statamic\Extensions\Pagination\LengthAwarePaginator; +use Statamic\Facades\Addon; use Statamic\Facades\Cascade; use Statamic\Facades\Entry as EntryFacade; use Statamic\Facades\Site; @@ -45,18 +47,31 @@ class Events public static function fromCollection(string $handle): self { - return tap(new static())->collection($handle); + return tap(new static)->collection($handle); } public static function fromEntry(string $id): self { - return tap(new static())->event($id); + return tap(new static)->event($id); } - private function __construct() + public static function collectionHandles(): Collection { + return collect(static::setting('collections', ['events']))->keys(); } + public static function setting(string $key, $default = null): mixed + { + return Addon::get('transformstudios/events')->settings()->get($key, $default); + } + + public static function timezone(): string + { + return static::setting('timezone', config('app.timezone')); + } + + private function __construct() {} + public function collapseMultiDays(): self { $this->collapseMultiDays = true; diff --git a/src/Http/Controllers/IcsController.php b/src/Http/Controllers/IcsController.php index 19e7230..bb36994 100644 --- a/src/Http/Controllers/IcsController.php +++ b/src/Http/Controllers/IcsController.php @@ -25,7 +25,7 @@ class IcsController extends Controller public function __invoke(Request $request) { - $handle = $request->get('collection', config('events.collection', 'events')); + $handle = $request->get('collection', 'events'); $date = $request->has('date') ? CarbonImmutable::parse($request->get('date')) : null; $eventId = $request->get('event'); $entry = null; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index edd5c95..886b4de 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -5,16 +5,18 @@ use Composer\InstalledVersions; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Artisan; +use Statamic\Entries\Entry; use Statamic\Facades\Collection; use Statamic\Facades\Site; use Statamic\Fields\Field; +use Statamic\Fields\Value; use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; use TransformStudios\Events\Fieldtypes\Timezones; use TransformStudios\Events\Modifiers\InMonth; use TransformStudios\Events\Modifiers\IsEndOfWeek; use TransformStudios\Events\Modifiers\IsStartOfWeek; -use TransformStudios\Events\Tags\Events; +use TransformStudios\Events\Tags\Events as EventsTag; class ServiceProvider extends AddonServiceProvider { @@ -33,7 +35,7 @@ class ServiceProvider extends AddonServiceProvider ]; protected $tags = [ - Events::class, + EventsTag::class, ]; public function bootAddon() @@ -72,23 +74,18 @@ private function bootCarbon(): self private function bootFields(): self { - Collection::computed(config('events.collection', 'events'), 'timezone', function ($entry, $value) { - $value ??= config('events.timezone', config('app.timezone')); - - if ($entry->blueprint()->fields()->get('timezone')?->fieldtype() instanceof Timezones) { - return $value; - } - - return (new Field('timezone', ['type' => 'timezones', 'max_items' => 1])) - ->setValue($value) - ->setParent($entry) - ->augment() - ->value(); - }); + collect(Events::setting('collections', [['collection' => 'events']])) + ->each(fn (array $collection) => $this + ->defineComputedTimezoneField($collection['collection'])); return $this; } + private function defineComputedTimezoneField(string $handle): void + { + Collection::computed($handle, 'timezone', $this->timezone(...)); + } + private function publishConfig(): self { Statamic::afterInstalled(function ($command) { @@ -97,4 +94,19 @@ private function publishConfig(): self return $this; } + + private function timezone(Entry $entry, $value): string|Value + { + $value ??= Events::timezone(); + + if ($entry->blueprint()->fields()->get('timezone')?->fieldtype() instanceof Timezones) { + return $value; + } + + return (new Field('timezone', ['type' => 'timezones', 'max_items' => 1])) + ->setValue($value) + ->setParent($entry) + ->augment() + ->value(); + } } diff --git a/src/Tags/Events.php b/src/Tags/Events.php index 5fdf720..ec42d76 100755 --- a/src/Tags/Events.php +++ b/src/Tags/Events.php @@ -51,7 +51,7 @@ public function downloadLink(): string return route( 'statamic.events.ics.show', Arr::removeNullValues([ - 'collection' => $this->params->get('collection', config('events.collection', 'events')), + 'collection' => $this->params->get('collection', 'events'), 'date' => $this->params->has('date') ? Carbon::parse($this->params->get('date'))->toDateString() : null, 'event' => $this->params->get('event'), ]) @@ -129,7 +129,7 @@ private function generator(): Generator { $generator = $this->params->has('event') ? Generator::fromEntry($this->params->get('event')) : - Generator::fromCollection($this->params->get('collection', config('events.collection', 'events'))); + Generator::fromCollection($this->params->get('collection', 'events')); return $generator ->site($this->params->get('site')) @@ -140,7 +140,7 @@ private function generator(): Generator )->when( value: $this->parseFilters(), callback: fn (Generator $generator, array $filters) => $generator->filters(filters: $filters) - )-> when( + )->when( value: $this->params->int('offset'), callback: fn (Generator $generator, int $offset) => $generator->offset(offset: $offset) )->when( diff --git a/src/UpdateScripts/ConvertConfigToSettings.php b/src/UpdateScripts/ConvertConfigToSettings.php new file mode 100644 index 0000000..75137b9 --- /dev/null +++ b/src/UpdateScripts/ConvertConfigToSettings.php @@ -0,0 +1,84 @@ +isUpdatingTo('6.0'); + } + + public function update() + { + $this->addon = AddonFacade::get('transformstudios/events'); + + if (is_null($settings = $this->settingsFromConfig())) { + return; + } + + if (! $settings->save()) { + $this->console()->error('Failed to save events settings. Please check your logs for details.'); + + return; + } + + $this->console()->info('Converted events config to settings.'); + + $this->removeConfig(); + } + + private function removeConfig(): void + { + if ($this->files->exists($configPath = config_path('events.php'))) { + $this->files->delete($configPath); + $this->console()->info('Removed old events config file.'); + } + } + + private function settingsFromConfig(): ?Settings + { + $config = Fluent::make($this->addon->config()); + + if ($config->isEmpty()) { + return null; + } + + $collections = collect([$config->collection => null]) + ->merge($config->collections) + ->map(function (array|string $collection, $handle) { + if (is_string($collection)) { + return [ + 'id' => Str::random(8), + 'collection' => $collection, + ]; + } + + $collectionSetting = [ + 'id' => Str::random(8), + 'collection' => $handle, + 'location_field' => Arr::get($collection, 'location_field', 'location'), + ]; + + return Arr::removeNullValues($collectionSetting); + })->reject(fn (array $collection) => $collection['collection'] == 'events' && is_null(Arr::get($collection, 'location_field'))) + ->values() + ->all(); + + $timezone = $config->timezone; + + return $this->addon + ->settings() + ->set(Arr::removeNullValues(compact('collections', 'timezone'))); + } +} From c24863c6aabe9bcd557df6afcf43f9ed134460d4 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Fri, 24 Oct 2025 12:25:41 -0700 Subject: [PATCH 06/14] Autoload addon components (#135) --- src/ServiceProvider.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 886b4de..000d480 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -13,31 +13,13 @@ use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; use TransformStudios\Events\Fieldtypes\Timezones; -use TransformStudios\Events\Modifiers\InMonth; -use TransformStudios\Events\Modifiers\IsEndOfWeek; -use TransformStudios\Events\Modifiers\IsStartOfWeek; -use TransformStudios\Events\Tags\Events as EventsTag; class ServiceProvider extends AddonServiceProvider { - protected $fieldtypes = [ - Timezones::class, - ]; - - protected $modifiers = [ - InMonth::class, - IsEndOfWeek::class, - IsStartOfWeek::class, - ]; - protected $routes = [ 'actions' => __DIR__.'/../routes/actions.php', ]; - protected $tags = [ - EventsTag::class, - ]; - public function bootAddon() { $this From 31128e7578ce2e1cda3861c291f22a732fa02a70 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Fri, 24 Oct 2025 13:01:12 -0700 Subject: [PATCH 07/14] tidy (#136) --- src/Http/Controllers/IcsController.php | 9 ++++----- src/Types/Event.php | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Http/Controllers/IcsController.php b/src/Http/Controllers/IcsController.php index bb36994..ba10861 100644 --- a/src/Http/Controllers/IcsController.php +++ b/src/Http/Controllers/IcsController.php @@ -25,9 +25,9 @@ class IcsController extends Controller public function __invoke(Request $request) { - $handle = $request->get('collection', 'events'); + $handle = $request->string('collection', 'events'); $date = $request->has('date') ? CarbonImmutable::parse($request->get('date')) : null; - $eventId = $request->get('event'); + $eventId = $request->string('event'); $entry = null; abort_if(! is_null($eventId) && is_null($entry = EntryFacade::find($eventId)), 404); @@ -59,14 +59,13 @@ public function __invoke(Request $request) private function downloadIcs(ICalendarEvent|array $event, string $title = 'events') { + // have to use long function syntax so we can echo within it return response()->streamDownload( function () use ($event) { echo Calendar::create()->event(Arr::wrap($event))->get(); }, Str::slugify($title).'.ics', - [ - 'Content-Type' => 'text/calendar; charset=utf-8', - ] + ['Content-Type' => 'text/calendar; charset=utf-8'] ); } } diff --git a/src/Types/Event.php b/src/Types/Event.php index 9a45788..f71f9e3 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -133,9 +133,7 @@ public function toICalendarEvent(string|CarbonInterface $date): ?ICalendarEvent */ public function toICalendarEvents(): array { - return [ - $this->toICalendarEvent($this->start()), - ]; + return Arr::wrap($this->toICalendarEvent($this->start())); } protected function location(Entry $event): ?string From 20183e9ab01b287f0f8519df17be3fb44865b94b Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 24 Oct 2025 13:02:19 -0700 Subject: [PATCH 08/14] Statamic issue has been fixed --- src/Types/Event.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Types/Event.php b/src/Types/Event.php index f71f9e3..7cb1189 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -6,6 +6,7 @@ use Carbon\CarbonImmutable; use Carbon\CarbonInterface; use DateTimeInterface; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use RRule\RRuleInterface; use Spatie\IcalendarGenerator\Components\Event as ICalendarEvent; @@ -22,14 +23,6 @@ public function __get(string $key): mixed return $this->event->$key; } - /* - Can remove this once https://github.com/statamic/cms/pull/11402 is released - */ - public function __isset(string $key): bool - { - return isset($this->event->$key); - } - public function endTime(): string { return $this->end_time ?? now()->endOfDay()->toTimeString(); From e8f412d61f19d17d17626fbfbbebb65c71124565 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 24 Oct 2025 13:49:26 -0700 Subject: [PATCH 09/14] broken for `Values` objects --- src/Types/Event.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Types/Event.php b/src/Types/Event.php index 7cb1189..d2c367b 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -23,6 +23,14 @@ public function __get(string $key): mixed return $this->event->$key; } + /* + Can remove this once https://github.com/statamic/cms/pull/11402 is released + */ + public function __isset(string $key): bool + { + return isset($this->event->$key); + } + public function endTime(): string { return $this->end_time ?? now()->endOfDay()->toTimeString(); From c34d2701dac8ddfee56fed716e35ba02fa6cb350 Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 24 Oct 2025 14:48:03 -0700 Subject: [PATCH 10/14] proper issue --- src/Types/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Types/Event.php b/src/Types/Event.php index d2c367b..a29ebb0 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -24,7 +24,7 @@ public function __get(string $key): mixed } /* - Can remove this once https://github.com/statamic/cms/pull/11402 is released + Can remove this once https://github.com/statamic/cms/pull/12865 is released */ public function __isset(string $key): bool { From 3be03631966b90b31fbf15d69191e3ae61b3042f Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 24 Oct 2025 16:07:53 -0700 Subject: [PATCH 11/14] clearer this way, but leave the `__isset` so folks can use `empty) --- src/Types/Event.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Types/Event.php b/src/Types/Event.php index a29ebb0..a5c801d 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -24,7 +24,9 @@ public function __get(string $key): mixed } /* - Can remove this once https://github.com/statamic/cms/pull/12865 is released + This is needed so that empty($event->days) works. This is due to how PHP handles + `empty`: it gets translated to + `!$class->__isset('property') || empty($class->__get('property')))` */ public function __isset(string $key): bool { @@ -48,7 +50,7 @@ public function isAllDay(): bool public function isMultiDay(): bool { - return boolval(($this->multi_day || $this->recurrence?->value() === 'multi_day') && ! empty($this->days)); + return boolval(($this->multi_day || $this->recurrence?->value() === 'multi_day') && ! empty($this->days())); } public function isRecurring(): bool From 2b541a3164835b1f739bc7123c7d905f59371b0c Mon Sep 17 00:00:00 2001 From: edalzell Date: Fri, 24 Oct 2025 16:15:49 -0700 Subject: [PATCH 12/14] Need this way so it works on non-multi-day events --- src/Types/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Types/Event.php b/src/Types/Event.php index a5c801d..8834234 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -50,7 +50,7 @@ public function isAllDay(): bool public function isMultiDay(): bool { - return boolval(($this->multi_day || $this->recurrence?->value() === 'multi_day') && ! empty($this->days())); + return boolval(($this->multi_day || $this->recurrence?->value() === 'multi_day') && ! empty($this->days)); } public function isRecurring(): bool From 646e259d49500a3c147ed7ab5db77d95b840fc64 Mon Sep 17 00:00:00 2001 From: Erin Dalzell Date: Sun, 26 Oct 2025 11:27:14 -0700 Subject: [PATCH 13/14] Move tests around (#137) --- tests/{Unit => }/DayTest.php | 2 + tests/{Unit => }/EventFactoryTest.php | 2 + tests/{Unit => }/EventsTest.php | 2 + tests/Feature/EventsOffsetTest.php | 97 ------------------- .../Contollers}/IcsControllerTest.php | 2 + .../TagTest.php => Tags/EventsTest.php} | 76 +++++++++++++++ tests/{Unit => Types}/MultiDayEventsTest.php | 2 + .../RecurringDailyEventsTest.php | 3 +- tests/{Unit => Types}/RecurringEventsTest.php | 2 + .../RecurringEveryXEventsTest.php | 3 +- tests/{Unit => Types}/SingleDayEventsTest.php | 2 + 11 files changed, 94 insertions(+), 99 deletions(-) rename tests/{Unit => }/DayTest.php (94%) rename tests/{Unit => }/EventFactoryTest.php (98%) rename tests/{Unit => }/EventsTest.php (99%) delete mode 100644 tests/Feature/EventsOffsetTest.php rename tests/{Feature => Http/Contollers}/IcsControllerTest.php (99%) rename tests/{Feature/TagTest.php => Tags/EventsTest.php} (81%) rename tests/{Unit => Types}/MultiDayEventsTest.php (98%) rename tests/{Unit => Types}/RecurringDailyEventsTest.php (99%) rename tests/{Unit => Types}/RecurringEventsTest.php (98%) rename tests/{Unit => Types}/RecurringEveryXEventsTest.php (99%) rename tests/{Unit => Types}/SingleDayEventsTest.php (98%) diff --git a/tests/Unit/DayTest.php b/tests/DayTest.php similarity index 94% rename from tests/Unit/DayTest.php rename to tests/DayTest.php index 7aa4843..6781d88 100755 --- a/tests/Unit/DayTest.php +++ b/tests/DayTest.php @@ -1,5 +1,7 @@ collection('events') - ->slug('recurring-event') - ->id('recurring-event') - ->data([ - 'title' => 'Recurring Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'weekly', - 'categories' => ['one'], - ])->save(); - - $this->tag = app(Events::class); -}); - -test('can offset upcoming occurrences', function () { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag - ->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'limit' => 5, - 'offset' => 2, - ]); - - $occurrences = $this->tag->upcoming(); - - expect($occurrences)->toHaveCount(3); -}); - -test('can offset between occurrences', function () { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'from' => Carbon::now()->toDateString(), - 'to' => Carbon::now()->addWeek(3), - 'offset' => 2, - ]); - - $occurrences = $this->tag->between(); - - expect($occurrences)->toHaveCount(2); -}); - -test('can offset today occurrences', function () { - Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); - - Entry::make() - ->collection('events') - ->slug('single-event') - ->data([ - 'title' => 'Single Event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '13:00', - 'end_time' => '15:00', - ])->save(); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'offset' => 1, - ]); - - expect($this->tag->today())->toHaveCount(1); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'ignore_finished' => true, - 'offset' => 1, - ]); - - expect($this->tag->today())->toHaveCount(0); -}); - -test('can offset single day occurrences', function () { - Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); - - $this->tag->setContext([]) - ->setParameters([ - 'collection' => 'events', - 'offset' => 1, - ]); - - expect($this->tag->today())->toHaveCount(0); -}); diff --git a/tests/Feature/IcsControllerTest.php b/tests/Http/Contollers/IcsControllerTest.php similarity index 99% rename from tests/Feature/IcsControllerTest.php rename to tests/Http/Contollers/IcsControllerTest.php index 7f3b8fd..d484c15 100755 --- a/tests/Feature/IcsControllerTest.php +++ b/tests/Http/Contollers/IcsControllerTest.php @@ -1,5 +1,7 @@ start->isAfter($occurrences[1]->start))->toBeTrue(); expect($occurrences[1]->start->isAfter($occurrences[2]->start))->toBeTrue(); }); +test('can offset upcoming occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag + ->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'limit' => 5, + 'offset' => 2, + ]); + + $occurrences = $this->tag->upcoming(); + + expect($occurrences)->toHaveCount(3); +}); + +test('can offset between occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'from' => Carbon::now()->toDateString(), + 'to' => Carbon::now()->addWeek(3), + 'offset' => 2, + ]); + + $occurrences = $this->tag->between(); + + expect($occurrences)->toHaveCount(2); +}); + +test('can offset today occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('12:01')); + + Entry::make() + ->collection('events') + ->slug('single-event') + ->data([ + 'title' => 'Single Event', + 'start_date' => Carbon::now()->toDateString(), + 'start_time' => '13:00', + 'end_time' => '15:00', + ])->save(); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(1); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'ignore_finished' => true, + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(0); +}); + +test('can offset single day occurrences', function () { + Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); + + $this->tag->setContext([]) + ->setParameters([ + 'collection' => 'events', + 'offset' => 1, + ]); + + expect($this->tag->today())->toHaveCount(0); +}); diff --git a/tests/Unit/MultiDayEventsTest.php b/tests/Types/MultiDayEventsTest.php similarity index 98% rename from tests/Unit/MultiDayEventsTest.php rename to tests/Types/MultiDayEventsTest.php index b1b80e6..9690dcd 100755 --- a/tests/Unit/MultiDayEventsTest.php +++ b/tests/Types/MultiDayEventsTest.php @@ -1,5 +1,7 @@ Date: Thu, 6 Nov 2025 15:21:50 -0800 Subject: [PATCH 14/14] Convert fieldsets/code to use Timezone Dictionary field (#139) --- resources/fieldsets/event.yaml | 11 +- resources/timezones.json | 2287 -------------------------------- src/Fieldtypes/Timezones.php | 83 -- src/ServiceProvider.php | 4 +- src/Types/Event.php | 8 +- src/Types/MultiDayEvent.php | 2 +- src/Types/RecurringEvent.php | 2 +- 7 files changed, 11 insertions(+), 2386 deletions(-) delete mode 100644 resources/timezones.json delete mode 100644 src/Fieldtypes/Timezones.php diff --git a/resources/fieldsets/event.yaml b/resources/fieldsets/event.yaml index e76ced3..8e1738f 100644 --- a/resources/fieldsets/event.yaml +++ b/resources/fieldsets/event.yaml @@ -17,16 +17,11 @@ fields: - handle: timezone field: + dictionary: timezones max_items: 1 - clearable: false - searchable: true - taggable: false - push_tags: false - cast_booleans: false + default: UTC + type: dictionary display: Timezone - type: timezones - mode: typeahead - width: 33 - handle: all_day field: diff --git a/resources/timezones.json b/resources/timezones.json deleted file mode 100644 index cb99581..0000000 --- a/resources/timezones.json +++ /dev/null @@ -1,2287 +0,0 @@ -[ - { - "timezone": "Australia/Darwin", - "abbreviation": "ACST", - "name": "AUS Central Standard Time" - }, - { - "timezone": "America/Glace_Bay", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "America/Goose_Bay", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "America/Halifax", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "America/Moncton", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "America/Thule", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "Atlantic/Bermuda", - "abbreviation": "AST", - "name": "Atlantic Standard Time" - }, - { - "timezone": "America/Scoresbysund", - "abbreviation": "AST", - "name": "Azores Standard Time" - }, - { - "timezone": "Atlantic/Azores", - "abbreviation": "AST", - "name": "Azores Standard Time" - }, - { - "timezone": "Asia/Baku", - "abbreviation": "AST", - "name": "Azerbaijan Standard Time" - }, - { - "timezone": "Australia/Melbourne", - "abbreviation": "AEST", - "name": "AUS Eastern Standard Time" - }, - { - "timezone": "Australia/Sydney", - "abbreviation": "AEST", - "name": "AUS Eastern Standard Time" - }, - { - "timezone": "America/Anchorage", - "abbreviation": "AKST", - "name": "Alaskan Standard Time" - }, - { - "timezone": "America/Juneau", - "abbreviation": "AKST", - "name": "Alaskan Standard Time" - }, - { - "timezone": "America/Nome", - "abbreviation": "AKST", - "name": "Alaskan Standard Time" - }, - { - "timezone": "America/Sitka", - "abbreviation": "AKST", - "name": "Alaskan Standard Time" - }, - { - "timezone": "America/Yakutat", - "abbreviation": "AKST", - "name": "Alaskan Standard Time" - }, - { - "timezone": "America/Argentina/Buenos_Aires", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Catamarca", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Cordoba", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Jujuy", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/La_Rioja", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Mendoza", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Rio_Gallegos", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Salta", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/San_Juan", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/San_Luis", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Tucuman", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Argentina/Ushuaia", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Buenos_Aires", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Catamarca", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Cordoba", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Jujuy", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "America/Mendoza", - "abbreviation": "AST", - "name": "Argentina Standard Time" - }, - { - "timezone": "Asia/Baghdad", - "abbreviation": "AST", - "name": "Arabic Standard Time" - }, - { - "timezone": "Asia/Aden", - "abbreviation": "AST", - "name": "Arab Standard Time" - }, - { - "timezone": "Asia/Bahrain", - "abbreviation": "AST", - "name": "Arab Standard Time" - }, - { - "timezone": "Asia/Kuwait", - "abbreviation": "AST", - "name": "Arab Standard Time" - }, - { - "timezone": "Asia/Qatar", - "abbreviation": "AST", - "name": "Arab Standard Time" - }, - { - "timezone": "Asia/Riyadh", - "abbreviation": "AST", - "name": "Arab Standard Time" - }, - { - "timezone": "Asia/Dubai", - "abbreviation": "AST", - "name": "Arabian Standard Time" - }, - { - "timezone": "Asia/Muscat", - "abbreviation": "AST", - "name": "Arabian Standard Time" - }, - { - "timezone": "Etc/GMT-4", - "abbreviation": "AST", - "name": "Arabian Standard Time" - }, - { - "timezone": "Asia/Kabul", - "abbreviation": "AST", - "name": "Afghanistan Standard Time" - }, - { - "timezone": "America/Bahia", - "abbreviation": "BST", - "name": "Bahia Standard Time" - }, - { - "timezone": "Europe/Isle_of_Man", - "abbreviation": "BST", - "name": "British Summer Time" - }, - { - "timezone": "Europe/Guernsey", - "abbreviation": "BST", - "name": "British Summer Time" - }, - { - "timezone": "Europe/Jersey", - "abbreviation": "BST", - "name": "British Summer Time" - }, - { - "timezone": "Europe/London", - "abbreviation": "BST", - "name": "British Summer Time" - }, - { - "timezone": "Asia/Dhaka", - "abbreviation": "BST", - "name": "Bangladesh Standard Time" - }, - { - "timezone": "Asia/Thimphu", - "abbreviation": "BST", - "name": "Bangladesh Standard Time" - }, - { - "timezone": "America/Belize", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "America/Costa_Rica", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "America/El_Salvador", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "America/Guatemala", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "America/Managua", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "America/Tegucigalpa", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "Etc/GMT+6", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "Pacific/Galapagos", - "abbreviation": "CAST", - "name": "Central America Standard Time" - }, - { - "timezone": "Antarctica/Vostok", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Asia/Almaty", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Asia/Bishkek", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Asia/Qyzylorda", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Asia/Urumqi", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Etc/GMT-6", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Indian/Chagos", - "abbreviation": "CAST", - "name": "Central Asia Standard Time" - }, - { - "timezone": "Australia/Adelaide", - "abbreviation": "CAST", - "name": "Cen. Australia Standard Time" - }, - { - "timezone": "Australia/Broken_Hill", - "abbreviation": "CAST", - "name": "Cen. Australia Standard Time" - }, - { - "timezone": "America/Campo_Grande", - "abbreviation": "CBST", - "name": "Central Brazilian Standard Time" - }, - { - "timezone": "America/Cuiaba", - "abbreviation": "CBST", - "name": "Central Brazilian Standard Time" - }, - { - "timezone": "America/Regina", - "abbreviation": "CCST", - "name": "Canada Central Standard Time" - }, - { - "timezone": "America/Swift_Current", - "abbreviation": "CCST", - "name": "Canada Central Standard Time" - }, - { - "timezone": "America/Chicago", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Indiana/Knox", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Indiana/Tell_City", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Matamoros", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Menominee", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/North_Dakota/Beulah", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/North_Dakota/Center", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/North_Dakota/New_Salem", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Rainy_River", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Rankin_Inlet", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Resolute", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Winnipeg", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "CST6CDT", - "abbreviation": "CST", - "name": "Central Standard Time" - }, - { - "timezone": "America/Bahia_Banderas", - "abbreviation": "CST", - "name": "Central Standard Time (Mexico)" - }, - { - "timezone": "America/Cancun", - "abbreviation": "CST", - "name": "Central Standard Time (Mexico)" - }, - { - "timezone": "America/Merida", - "abbreviation": "CST", - "name": "Central Standard Time (Mexico)" - }, - { - "timezone": "America/Mexico_City", - "abbreviation": "CST", - "name": "Central Standard Time (Mexico)" - }, - { - "timezone": "America/Monterrey", - "abbreviation": "CST", - "name": "Central Standard Time (Mexico)" - }, - { - "timezone": "Europe/Belgrade", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Bratislava", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Budapest", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Ljubljana", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Podgorica", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Prague", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Tirane", - "abbreviation": "CEST", - "name": "Central Europe Standard Time" - }, - { - "timezone": "Europe/Sarajevo", - "abbreviation": "CEST", - "name": "Central European Standard Time" - }, - { - "timezone": "Europe/Skopje", - "abbreviation": "CEST", - "name": "Central European Standard Time" - }, - { - "timezone": "Europe/Warsaw", - "abbreviation": "CEST", - "name": "Central European Standard Time" - }, - { - "timezone": "Europe/Zagreb", - "abbreviation": "CEST", - "name": "Central European Standard Time" - }, - { - "timezone": "Antarctica/Macquarie", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Etc/GMT-11", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Pacific/Efate", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Pacific/Guadalcanal", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Pacific/Kosrae", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Pacific/Noumea", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Pacific/Ponape", - "abbreviation": "CPST", - "name": "Central Pacific Standard Time" - }, - { - "timezone": "Asia/Yerevan", - "abbreviation": "CST", - "name": "Caucasus Standard Time" - }, - { - "timezone": "Asia/Hong_Kong", - "abbreviation": "CST", - "name": "China Standard Time" - }, - { - "timezone": "Asia/Macau", - "abbreviation": "CST", - "name": "China Standard Time" - }, - { - "timezone": "Asia/Shanghai", - "abbreviation": "CST", - "name": "China Standard Time" - }, - { - "timezone": "Atlantic/Cape_Verde", - "abbreviation": "CVST", - "name": "Cape Verde Standard Time" - }, - { - "timezone": "Etc/GMT+1", - "abbreviation": "CVST", - "name": "Cape Verde Standard Time" - }, - { - "timezone": "Etc/GMT+12", - "abbreviation": "DST", - "name": "Dateline Standard Time" - }, - { - "timezone": "Africa/Addis_Ababa", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Asmera", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Dar_es_Salaam", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Djibouti", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Juba", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Kampala", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Khartoum", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Mogadishu", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Africa/Nairobi", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Antarctica/Syowa", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Etc/GMT-3", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Indian/Antananarivo", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Indian/Comoro", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Indian/Mayotte", - "abbreviation": "EAST", - "name": "E. Africa Standard Time" - }, - { - "timezone": "Australia/Brisbane", - "abbreviation": "EAST", - "name": "E. Australia Standard Time" - }, - { - "timezone": "Australia/Lindeman", - "abbreviation": "EAST", - "name": "E. Australia Standard Time" - }, - { - "timezone": "Asia/Nicosia", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Athens", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Bucharest", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Chisinau", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Helsinki", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Kyiv", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Mariehamn", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Nicosia", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Riga", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Sofia", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Tallinn", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Uzhgorod", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Vilnius", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "Europe/Zaporozhye", - "abbreviation": "EEST", - "name": "E. Europe Standard Time" - }, - { - "timezone": "America/Sao_Paulo", - "abbreviation": "ESAST", - "name": "E. South America Standard Time" - }, - { - "timezone": "America/Detroit", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Havana", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Indiana/Petersburg", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Indiana/Vincennes", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Indiana/Winamac", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Iqaluit", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Kentucky/Monticello", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Louisville", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Montreal", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Nassau", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/New_York", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Nipigon", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Pangnirtung", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Port-au-Prince", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Thunder_Bay", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "America/Toronto", - "abbreviation": "EST", - "name": "Eastern Standard Time" - }, - { - "timezone": "Africa/Cairo", - "abbreviation": "EST", - "name": "Egypt Standard Time" - }, - { - "timezone": "Europe/Helsinki", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Kyiv", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Mariehamn", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Riga", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Sofia", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Tallinn", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Uzhgorod", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Vilnius", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Europe/Zaporozhye", - "abbreviation": "FST", - "name": "FLE Standard Time" - }, - { - "timezone": "Pacific/Fiji", - "abbreviation": "FST", - "name": "Fiji Standard Time" - }, - { - "timezone": "America/Godthab", - "abbreviation": "GST", - "name": "Greenland Standard Time" - }, - { - "timezone": "Atlantic/Canary", - "abbreviation": "GST", - "name": "GMT Standard Time" - }, - { - "timezone": "Atlantic/Faeroe", - "abbreviation": "GST", - "name": "GMT Standard Time" - }, - { - "timezone": "Atlantic/Madeira", - "abbreviation": "GST", - "name": "GMT Standard Time" - }, - { - "timezone": "Europe/Dublin", - "abbreviation": "GST", - "name": "GMT Standard Time" - }, - { - "timezone": "Europe/Lisbon", - "abbreviation": "GST", - "name": "GMT Standard Time" - }, - { - "timezone": "Asia/Nicosia", - "abbreviation": "GST", - "name": "GTB Standard Time" - }, - { - "timezone": "Europe/Athens", - "abbreviation": "GST", - "name": "GTB Standard Time" - }, - { - "timezone": "Europe/Bucharest", - "abbreviation": "GST", - "name": "GTB Standard Time" - }, - { - "timezone": "Europe/Chisinau", - "abbreviation": "GST", - "name": "GTB Standard Time" - }, - { - "timezone": "Asia/Tbilisi", - "abbreviation": "GET", - "name": "Georgian Standard Time" - }, - { - "timezone": "Europe/Isle_of_Man", - "abbreviation": "GMT", - "name": "GMT Standard Time" - }, - { - "timezone": "Europe/Guernsey", - "abbreviation": "GMT", - "name": "GMT Standard Time" - }, - { - "timezone": "Europe/Jersey", - "abbreviation": "GMT", - "name": "GMT Standard Time" - }, - { - "timezone": "Europe/London", - "abbreviation": "GMT", - "name": "GMT Standard Time" - }, - { - "timezone": "Africa/Abidjan", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Accra", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Bamako", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Banjul", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Bissau", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Conakry", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Dakar", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Freetown", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Lome", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Monrovia", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Nouakchott", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Ouagadougou", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Africa/Sao_Tome", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Atlantic/Reykjavik", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Atlantic/St_Helena", - "abbreviation": "GST", - "name": "Greenwich Standard Time" - }, - { - "timezone": "Etc/GMT+10", - "abbreviation": "HST", - "name": "Hawaiian Standard Time" - }, - { - "timezone": "Pacific/Honolulu", - "abbreviation": "HST", - "name": "Hawaiian Standard Time" - }, - { - "timezone": "Pacific/Johnston", - "abbreviation": "HST", - "name": "Hawaiian Standard Time" - }, - { - "timezone": "Pacific/Rarotonga", - "abbreviation": "HST", - "name": "Hawaiian Standard Time" - }, - { - "timezone": "Pacific/Tahiti", - "abbreviation": "HST", - "name": "Hawaiian Standard Time" - }, - { - "timezone": "Asia/Tehran", - "abbreviation": "IRST", - "name": "Iran Standard Time" - }, - { - "timezone": "Asia/Kolkata", - "abbreviation": "IST", - "name": "India Standard Time" - }, - { - "timezone": "Asia/Calcutta", - "abbreviation": "IST", - "name": "India Standard Time" - }, - { - "timezone": "Asia/Jerusalem", - "abbreviation": "IST", - "name": "Israel Standard Time" - }, - { - "timezone": "Asia/Amman", - "abbreviation": "JST", - "name": "Jordan Standard Time" - }, - { - "timezone": "Asia/Dili", - "abbreviation": "JST", - "name": "Japan Standard Time" - }, - { - "timezone": "Asia/Jayapura", - "abbreviation": "JST", - "name": "Japan Standard Time" - }, - { - "timezone": "Asia/Tokyo", - "abbreviation": "JST", - "name": "Japan Standard Time" - }, - { - "timezone": "Etc/GMT-9", - "abbreviation": "JST", - "name": "Japan Standard Time" - }, - { - "timezone": "Pacific/Palau", - "abbreviation": "JST", - "name": "Japan Standard Time" - }, - { - "timezone": "Asia/Kamchatka", - "abbreviation": "KST", - "name": "Kamchatka Standard Time" - }, - { - "timezone": "Europe/Kaliningrad", - "abbreviation": "KST", - "name": "Kaliningrad Standard Time" - }, - { - "timezone": "Asia/Pyongyang", - "abbreviation": "KST", - "name": "Korea Standard Time" - }, - { - "timezone": "Asia/Seoul", - "abbreviation": "KST", - "name": "Korea Standard Time" - }, - { - "timezone": "Africa/Tripoli", - "abbreviation": "LST", - "name": "Libya Standard Time" - }, - { - "timezone": "America/Chihuahua", - "abbreviation": "MST", - "name": "Mountain Standard Time (Mexico)" - }, - { - "timezone": "America/Mazatlan", - "abbreviation": "MST", - "name": "Mountain Standard Time (Mexico)" - }, - { - "timezone": "America/Boise", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Cambridge_Bay", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Denver", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Edmonton", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Inuvik", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Ojinaga", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "America/Yellowknife", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "MST7MST", - "abbreviation": "MST", - "name": "Mountain Standard Time" - }, - { - "timezone": "Africa/Casablanca", - "abbreviation": "MST", - "name": "Morocco Standard Time" - }, - { - "timezone": "Africa/El_Aaiun", - "abbreviation": "MST", - "name": "Morocco Standard Time" - }, - { - "timezone": "Asia/Beirut", - "abbreviation": "MEST", - "name": "Middle East Standard Time" - }, - { - "timezone": "Asia/Brunei", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Asia/Kuala_Lumpur", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Asia/Kuching", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Asia/Makassar", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Asia/Manila", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Asia/Singapore", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Etc/GMT-8", - "abbreviation": "MPST", - "name": "Singapore Standard Time" - }, - { - "timezone": "Europe/Kirov", - "abbreviation": "MSK", - "name": "Moscow Standard Time" - }, - { - "timezone": "Europe/Moscow", - "abbreviation": "MSK", - "name": "Moscow Standard Time" - }, - { - "timezone": "Europe/Simferopol", - "abbreviation": "MSK", - "name": "Moscow Standard Time" - }, - { - "timezone": "Europe/Volgograd", - "abbreviation": "MSK", - "name": "Moscow Standard Time" - }, - { - "timezone": "Europe/Minsk", - "abbreviation": "MSK", - "name": "Moscow Standard Time" - }, - { - "timezone": "America/Montevideo", - "abbreviation": "MST", - "name": "Montevideo Standard Time" - }, - { - "timezone": "Indian/Mahe", - "abbreviation": "MST", - "name": "Mauritius Standard Time" - }, - { - "timezone": "Indian/Mauritius", - "abbreviation": "MST", - "name": "Mauritius Standard Time" - }, - { - "timezone": "Indian/Reunion", - "abbreviation": "MST", - "name": "Mauritius Standard Time" - }, - { - "timezone": "Asia/Rangoon", - "abbreviation": "MST", - "name": "Myanmar Standard Time" - }, - { - "timezone": "Indian/Cocos", - "abbreviation": "MST", - "name": "Myanmar Standard Time" - }, - { - "timezone": "Asia/Anadyr", - "abbreviation": "MST", - "name": "Magadan Standard Time" - }, - { - "timezone": "Asia/Kamchatka", - "abbreviation": "MST", - "name": "Magadan Standard Time" - }, - { - "timezone": "Asia/Magadan", - "abbreviation": "MST", - "name": "Magadan Standard Time" - }, - { - "timezone": "Asia/Srednekolymsk", - "abbreviation": "MST", - "name": "Magadan Standard Time" - }, - { - "timezone": "Asia/Irkutsk", - "abbreviation": "NAEST", - "name": "North Asia East Standard Time" - }, - { - "timezone": "Asia/Krasnoyarsk", - "abbreviation": "NAST", - "name": "North Asia Standard Time" - }, - { - "timezone": "Asia/Novokuznetsk", - "abbreviation": "NCAST", - "name": "N. Central Asia Standard Time" - }, - { - "timezone": "Asia/Novosibirsk", - "abbreviation": "NCAST", - "name": "N. Central Asia Standard Time" - }, - { - "timezone": "Asia/Omsk", - "abbreviation": "NCAST", - "name": "N. Central Asia Standard Time" - }, - { - "timezone": "America/St_Johns", - "abbreviation": "NST", - "name": "Newfoundland Standard Time" - }, - { - "timezone": "Africa/Windhoek", - "abbreviation": "NST", - "name": "Namibia Standard Time" - }, - { - "timezone": "Asia/Kathmandu", - "abbreviation": "NST", - "name": "Nepal Standard Time" - }, - { - "timezone": "Antarctica/McMurdo", - "abbreviation": "NZST", - "name": "New Zealand Standard Time" - }, - { - "timezone": "Pacific/Auckland", - "abbreviation": "NZST", - "name": "New Zealand Standard Time" - }, - { - "timezone": "America/Santa_Isabel", - "abbreviation": "PST", - "name": "Pacific Standard Time (Mexico)" - }, - { - "timezone": "Asia/Karachi", - "abbreviation": "PKT", - "name": "Pakistan Standard Time" - }, - { - "timezone": "America/Santiago", - "abbreviation": "PSST", - "name": "Pacific SA Standard Time" - }, - { - "timezone": "Antarctica/Palmer", - "abbreviation": "PSST", - "name": "Pacific SA Standard Time" - }, - { - "timezone": "America/Los_Angeles", - "abbreviation": "PST", - "name": "Pacific Standard Time" - }, - { - "timezone": "America/Tijuana", - "abbreviation": "PST", - "name": "Pacific Standard Time" - }, - { - "timezone": "America/Vancouver", - "abbreviation": "PST", - "name": "Pacific Standard Time" - }, - { - "timezone": "PST8PDT", - "abbreviation": "PST", - "name": "Pacific Standard Time" - }, - { - "timezone": "America/Asuncion", - "abbreviation": "PYT", - "name": "Paraguay Standard Time" - }, - { - "timezone": "Africa/Ceuta", - "abbreviation": "RST", - "name": "Romance Standard Time" - }, - { - "timezone": "Europe/Brussels", - "abbreviation": "RST", - "name": "Romance Standard Time" - }, - { - "timezone": "Europe/Copenhagen", - "abbreviation": "RST", - "name": "Romance Standard Time" - }, - { - "timezone": "Europe/Madrid", - "abbreviation": "RST", - "name": "Romance Standard Time" - }, - { - "timezone": "Europe/Paris", - "abbreviation": "RST", - "name": "Romance Standard Time" - }, - { - "timezone": "Europe/Astrakhan", - "abbreviation": "SAMT", - "name": "Samara Time" - }, - { - "timezone": "Europe/Samara", - "abbreviation": "SAMT", - "name": "Samara Time" - }, - { - "timezone": "Europe/Ulyanovsk", - "abbreviation": "SAMT", - "name": "Samara Time" - }, - { - "timezone": "Africa/Blantyre", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Bujumbura", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Gaborone", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Harare", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Johannesburg", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Kigali", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Lubumbashi", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Lusaka", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Maputo", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Maseru", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Africa/Mbabane", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Etc/GMT-2", - "abbreviation": "SAST", - "name": "South Africa Standard Time" - }, - { - "timezone": "Antarctica/Davis", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Bangkok", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Hovd", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Jakarta", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Phnom_Penh", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Pontianak", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Saigon", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Vientiane", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Etc/GMT-7", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Indian/Christmas", - "abbreviation": "SAST", - "name": "SE Asia Standard Time" - }, - { - "timezone": "Asia/Damascus", - "abbreviation": "SST", - "name": "Syria Standard Time" - }, - { - "timezone": "America/Araguaina", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Belem", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Cayenne", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Fortaleza", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Maceio", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Paramaribo", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Recife", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "America/Santarem", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "Antarctica/Rothera", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "Atlantic/Stanley", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "Etc/GMT+3", - "abbreviation": "SEST", - "name": "SA Eastern Standard Time" - }, - { - "timezone": "Asia/Colombo", - "abbreviation": "SLST", - "name": "Sri Lanka Standard Time" - }, - { - "timezone": "America/Bogota", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Cayman", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Coral_Harbour", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Eirunepe", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Guayaquil", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Jamaica", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Lima", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Panama", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "America/Rio_Branco", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "Etc/GMT+5", - "abbreviation": "SPST", - "name": "SA Pacific Standard Time" - }, - { - "timezone": "Pacific/Apia", - "abbreviation": "SST", - "name": "Samoa Standard Time" - }, - { - "timezone": "America/Anguilla", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Antigua", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Aruba", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Barbados", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Blanc-Sablon", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Boa_Vista", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Curacao", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Dominica", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Grand_Turk", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Grenada", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Guadeloupe", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Guyana", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Kralendijk", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/La_Paz", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Lower_Princes", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Manaus", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Marigot", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Martinique", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Montserrat", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Port_of_Spain", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Porto_Velho", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Puerto_Rico", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Santo_Domingo", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/St_Barthelemy", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/St_Kitts", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/St_Lucia", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/St_Thomas", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/St_Vincent", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "America/Tortola", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "Etc/GMT+4", - "abbreviation": "SWST", - "name": "SA Western Standard Time" - }, - { - "timezone": "Europe/Istanbul", - "abbreviation": "TST", - "name": "Turkey Standard Time" - }, - { - "timezone": "Asia/Taipei", - "abbreviation": "TST", - "name": "Taipei Standard Time" - }, - { - "timezone": "Australia/Currie", - "abbreviation": "TST", - "name": "Tasmania Standard Time" - }, - { - "timezone": "Australia/Hobart", - "abbreviation": "TST", - "name": "Tasmania Standard Time" - }, - { - "timezone": "Etc/GMT-13", - "abbreviation": "TST", - "name": "Tonga Standard Time" - }, - { - "timezone": "Pacific/Enderbury", - "abbreviation": "TST", - "name": "Tonga Standard Time" - }, - { - "timezone": "Pacific/Fakaofo", - "abbreviation": "TST", - "name": "Tonga Standard Time" - }, - { - "timezone": "Pacific/Tongatapu", - "abbreviation": "TST", - "name": "Tonga Standard Time" - }, - { - "timezone": "Etc/GMT+11", - "abbreviation": "U", - "name": "UTC-11" - }, - { - "timezone": "Pacific/Midway", - "abbreviation": "U", - "name": "UTC-11" - }, - { - "timezone": "Pacific/Niue", - "abbreviation": "U", - "name": "UTC-11" - }, - { - "timezone": "Pacific/Pago_Pago", - "abbreviation": "U", - "name": "UTC-11" - }, - { - "timezone": "America/Noronha", - "abbreviation": "U", - "name": "UTC-02" - }, - { - "timezone": "Atlantic/South_Georgia", - "abbreviation": "U", - "name": "UTC-02" - }, - { - "timezone": "Etc/GMT+2", - "abbreviation": "U", - "name": "UTC-02" - }, - { - "timezone": "Etc/GMT-12", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Funafuti", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Kwajalein", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Majuro", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Nauru", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Tarawa", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Wake", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "Pacific/Wallis", - "abbreviation": "U", - "name": "UTC+12" - }, - { - "timezone": "America/Indiana/Marengo", - "abbreviation": "UEST", - "name": "US Eastern Standard Time" - }, - { - "timezone": "America/Indiana/Vevay", - "abbreviation": "UEST", - "name": "US Eastern Standard Time" - }, - { - "timezone": "America/Indianapolis", - "abbreviation": "UEST", - "name": "US Eastern Standard Time" - }, - { - "timezone": "America/Creston", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "America/Dawson", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "America/Dawson_Creek", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "America/Hermosillo", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "America/Phoenix", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "America/Whitehorse", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "Etc/GMT+7", - "abbreviation": "UMST", - "name": "US Mountain Standard Time" - }, - { - "timezone": "Asia/Choibalsan", - "abbreviation": "UST", - "name": "Ulaanbaatar Standard Time" - }, - { - "timezone": "Asia/Ulaanbaatar", - "abbreviation": "UST", - "name": "Ulaanbaatar Standard Time" - }, - { - "timezone": "America/Danmarkshavn", - "abbreviation": "UTC", - "name": "UTC" - }, - { - "timezone": "Etc/GMT", - "abbreviation": "UTC", - "name": "UTC" - }, - { - "timezone": "UTC", - "abbreviation": "UTC", - "name": "Coordinated Universal Time" - }, - { - "timezone": "America/Caracas", - "abbreviation": "VST", - "name": "Venezuela Standard Time" - }, - { - "timezone": "Asia/Sakhalin", - "abbreviation": "VST", - "name": "Vladivostok Standard Time" - }, - { - "timezone": "Asia/Ust-Nera", - "abbreviation": "VST", - "name": "Vladivostok Standard Time" - }, - { - "timezone": "Asia/Vladivostok", - "abbreviation": "VST", - "name": "Vladivostok Standard Time" - }, - { - "timezone": "Antarctica/Mawson", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Aqtau", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Aqtobe", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Ashgabat", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Dushanbe", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Oral", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Samarkand", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Asia/Tashkent", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Etc/GMT-5", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Indian/Kerguelen", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Indian/Maldives", - "abbreviation": "WAST", - "name": "West Asia Standard Time" - }, - { - "timezone": "Antarctica/Casey", - "abbreviation": "WAST", - "name": "W. Australia Standard Time" - }, - { - "timezone": "Australia/Perth", - "abbreviation": "WAST", - "name": "W. Australia Standard Time" - }, - { - "timezone": "Africa/Algiers", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Bangui", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Brazzaville", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Douala", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Kinshasa", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Lagos", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Libreville", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Luanda", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Malabo", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Ndjamena", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Niamey", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Porto-Novo", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Africa/Tunis", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Etc/GMT-1", - "abbreviation": "WCAST", - "name": "W. Central Africa Standard Time" - }, - { - "timezone": "Arctic/Longyearbyen", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Amsterdam", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Andorra", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Berlin", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Busingen", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Gibraltar", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Luxembourg", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Malta", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Monaco", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Oslo", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Rome", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/San_Marino", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Stockholm", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Vaduz", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Vatican", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Vienna", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Europe/Zurich", - "abbreviation": "WEST", - "name": "W. Europe Standard Time" - }, - { - "timezone": "Antarctica/DumontDUrville", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Etc/GMT-10", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Pacific/Guam", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Pacific/Port_Moresby", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Pacific/Saipan", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Pacific/Truk", - "abbreviation": "WPST", - "name": "West Pacific Standard Time" - }, - { - "timezone": "Asia/Yekaterinburg", - "abbreviation": "YEKT", - "name": "Yekaterinburg Time" - }, - { - "timezone": "Asia/Chita", - "abbreviation": "YST", - "name": "Yakutsk Standard Time" - }, - { - "timezone": "Asia/Khandyga", - "abbreviation": "YST", - "name": "Yakutsk Standard Time" - }, - { - "timezone": "Asia/Yakutsk", - "abbreviation": "YST", - "name": "Yakutsk Standard Time" - } -] diff --git a/src/Fieldtypes/Timezones.php b/src/Fieldtypes/Timezones.php deleted file mode 100644 index cb0e60f..0000000 --- a/src/Fieldtypes/Timezones.php +++ /dev/null @@ -1,83 +0,0 @@ -timezone($key))) { - return [ - 'abbreviation' => $key, - 'timezone' => $key, - ]; - } - - return $augmented; - } - - public function getIndexItems($request) - { - return collect($this->timezones()) - ->map(fn (array $zone) => $this->toItemArray($zone['timezone'])); - } - - public function preProcess($data) - { - if (is_array($data)) { - return [Arr::get($data, 'timezone')]; - } - - return Arr::wrap($data); - } - - public function preProcessIndex($key) - { - if (is_null($key)) { - return null; - } - - if (is_null($augmented = $this->timezone($key))) { - return $key; - } - - return $augmented['timezone']; - } - - protected function toItemArray($key) - { - if (is_null($key)) { - return []; - } - - if (is_null($zone = $this->timezone($key))) { - return []; - } - - return [ - 'id' => $key, - 'title' => $key.' ('.$zone['abbreviation'].')', - ]; - } - - private function timezone(string $key): ?array - { - return collect($this->timezones())->firstWhere('timezone', $key); - } - - /** - * @return array - */ - private function timezones() - { - return cache()->rememberForever( - 'timezones', - fn () => json_decode(file_get_contents(__DIR__.'/../../resources/timezones.json'), true) - ); - } -} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 000d480..ed5fc6a 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -10,9 +10,9 @@ use Statamic\Facades\Site; use Statamic\Fields\Field; use Statamic\Fields\Value; +use Statamic\Fieldtypes\Dictionary; use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; -use TransformStudios\Events\Fieldtypes\Timezones; class ServiceProvider extends AddonServiceProvider { @@ -81,7 +81,7 @@ private function timezone(Entry $entry, $value): string|Value { $value ??= Events::timezone(); - if ($entry->blueprint()->fields()->get('timezone')?->fieldtype() instanceof Timezones) { + if ($entry->blueprint()->fields()->get('timezone')?->fieldtype() instanceof Dictionary) { return $value; } diff --git a/src/Types/Event.php b/src/Types/Event.php index 8834234..4cdabf7 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -87,14 +87,14 @@ public function startTime(): string public function start(): CarbonImmutable { return CarbonImmutable::parse($this->start_date) - ->shiftTimezone($this->timezone['timezone']) + ->shiftTimezone($this->timezone['name']) ->setTimeFromTimeString($this->startTime()); } public function end(): CarbonImmutable { return CarbonImmutable::parse($this->start_date) - ->shiftTimezone($this->timezone['timezone']) + ->shiftTimezone($this->timezone['name']) ->setTimeFromTimeString($this->endTime()); } @@ -169,14 +169,14 @@ protected function toCarbonImmutable(string|CarbonInterface $date): CarbonImmuta { $carbon = is_string($date) ? Carbon::parse($date) : $date; - return $carbon->shiftTimezone($this->timezone['timezone'])->toImmutable(); + return $carbon->shiftTimezone($this->timezone['name'])->toImmutable(); } private function collect(array $dates): Collection { return collect($dates) ->map(fn (DateTimeInterface $date) => $this->supplement( - date: CarbonImmutable::parse($date, $this->timezone['timezone']) + date: CarbonImmutable::parse($date, $this->timezone['name']) ))->filter(); } } diff --git a/src/Types/MultiDayEvent.php b/src/Types/MultiDayEvent.php index 5eaa7e7..c1a8a98 100644 --- a/src/Types/MultiDayEvent.php +++ b/src/Types/MultiDayEvent.php @@ -25,7 +25,7 @@ public function __construct(Entry $event, private bool $collapseMultiDays) ->sortBy('date') ->map(fn (Values $day) => new Day( $day->all(), - $this->timezone['timezone'], + $this->timezone['name'], $day->all_day || $this->isAllDay(), )); } diff --git a/src/Types/RecurringEvent.php b/src/Types/RecurringEvent.php index a59482a..677fa9e 100644 --- a/src/Types/RecurringEvent.php +++ b/src/Types/RecurringEvent.php @@ -57,7 +57,7 @@ protected function rule(): RRuleInterface ]; if ($end = $this->end_date) { - $rule['until'] = Carbon::parse($end)->shiftTimezone($this->timezone['timezone'])->endOfDay(); + $rule['until'] = Carbon::parse($end)->shiftTimezone($this->timezone['name'])->endOfDay(); } if (! empty($days = $this->onSpecificDays())) {