diff --git a/resources/blueprints/settings.yaml b/resources/blueprints/settings.yaml index 453c324..4a0e3c6 100644 --- a/resources/blueprints/settings.yaml +++ b/resources/blueprints/settings.yaml @@ -27,9 +27,9 @@ tabs: - handle: timezone field: - mode: select + dictionary: timezones max_items: 1 - type: timezones + default: UTC + type: dictionary display: Timezone full_width_setting: true - default: 'UTC' diff --git a/resources/fieldsets/event.yaml b/resources/fieldsets/event.yaml index 594ccb4..4993cf2 100644 --- a/resources/fieldsets/event.yaml +++ b/resources/fieldsets/event.yaml @@ -11,7 +11,7 @@ fields: monthly: Monthly every: Every multi_day: Multi-Day - width: 33 + width: 50 display: Recurrence default: none - @@ -22,14 +22,7 @@ fields: default: UTC type: dictionary display: Timezone - - - handle: all_day - field: - type: toggle - width: 33 - display: 'All Day?' - unless: - recurrence: 'equals multi_day' + width: 50 - handle: specific_days field: @@ -75,6 +68,13 @@ fields: multi_day: 'equals true' recurrence: 'equals multi_day' format: Y-m-d + - + handle: end_date_spacer + field: + type: spacer + width: 33 + if: + recurrence: 'equals none' - handle: end_date field: @@ -91,11 +91,44 @@ fields: if: recurrence: 'contains_any daily, weekly, monthly, every' format: Y-m-d + - + handle: exclude_dates + field: + type: grid + fullscreen: false + display: 'Exclude Days' + add_row: 'Add Day' + if_any: + recurrence: 'contains_any monthly, daily, weekly, every' + fields: + - + handle: date + field: + type: date + allow_blank: false + allow_time: false + require_time: false + input_format: YYYY/M/D/YYYY + display: Date + format: Y-m-d + - + handle: times_sections + field: + type: section + display: Times + - + handle: all_day + field: + type: toggle + width: 33 + display: 'All Day?' + unless: + recurrence: 'equals multi_day' - handle: start_time field: type: time - width: 25 + width: 33 display: 'Start Time' instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)' unless_any: @@ -106,7 +139,7 @@ fields: handle: end_time field: type: time - width: 25 + width: 33 display: 'End Time' instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)' unless_any: @@ -170,22 +203,3 @@ fields: field: 'events::event.all_day' config: width: 25 - - - handle: exclude_dates - field: - type: grid - display: 'Exclude Days' - add_row: 'Add Day' - if_any: - recurrence: 'contains_any monthly, daily, weekly, every' - fields: - - - handle: date - field: - type: date - allow_blank: false - allow_time: false - require_time: false - input_format: YYYY/M/D/YYYY - display: Date - format: Y-m-d diff --git a/src/Events.php b/src/Events.php index d05c350..71d9e30 100644 --- a/src/Events.php +++ b/src/Events.php @@ -67,7 +67,7 @@ public static function setting(string $key, $default = null): mixed public static function timezone(): string { - return static::setting('timezone', config('app.timezone')); + return static::setting('timezone', config('statamic.system.display_timezone') ?? config('app.timezone')); } private function __construct() {} @@ -200,7 +200,7 @@ private function isMultiDay(Entry $occurrence): bool private function occurrences(callable $generator): EntryCollection { return $this->entries - ->filter(fn (Entry $occurrence) => $this->hasStartDate($occurrence)) + ->filter(fn (Entry $event) => $this->hasStartDate($event)) // take each event and generate the occurrences ->flatMap(callback: $generator) ->reject(fn (Entry $occurrence) => collect($occurrence->exclude_dates) diff --git a/src/Modifiers/IsEndOfWeek.php b/src/Modifiers/IsEndOfWeek.php index ec09b4e..b7b9301 100755 --- a/src/Modifiers/IsEndOfWeek.php +++ b/src/Modifiers/IsEndOfWeek.php @@ -3,16 +3,26 @@ namespace TransformStudios\Events\Modifiers; use Carbon\CarbonImmutable; +use Statamic\Facades\Site; use Statamic\Modifiers\Modifier; class IsEndOfWeek extends Modifier { public function index($value, $params, $context) { + /* + have to do this because Statamic sets the Carbon locale + to the `lang` of the site, instead of the `locale` + */ + $currentLocale = CarbonImmutable::getLocale(); + CarbonImmutable::setLocale(Site::current()->locale()); + $date = CarbonImmutable::parse($value); - $date->isSameDay($date->locale(CarbonImmutable::getLocale())->startOfWeek()); + $isStartOfWeek = $date->dayOfWeek == now()->endOfWeek()->dayOfWeek; + + CarbonImmutable::setLocale($currentLocale); - return $date->dayOfWeek == now()->endOfWeek()->dayOfWeek; + return $isStartOfWeek; } } diff --git a/src/Modifiers/IsStartOfWeek.php b/src/Modifiers/IsStartOfWeek.php index e011221..b300bf7 100755 --- a/src/Modifiers/IsStartOfWeek.php +++ b/src/Modifiers/IsStartOfWeek.php @@ -3,16 +3,26 @@ namespace TransformStudios\Events\Modifiers; use Carbon\CarbonImmutable; +use Statamic\Facades\Site; use Statamic\Modifiers\Modifier; class IsStartOfWeek extends Modifier { public function index($value, $params, $context) { + /* + have to do this because Statamic sets the Carbon locale + to the `lang` of the site, instead of the `locale` + */ + $currentLocale = CarbonImmutable::getLocale(); + CarbonImmutable::setLocale(Site::current()->locale()); + $date = CarbonImmutable::parse($value); - $date->isSameDay($date->locale(CarbonImmutable::getLocale())->startOfWeek()); + $isStartOfWeek = $date->dayOfWeek == now()->startOfWeek()->dayOfWeek; + + CarbonImmutable::setLocale($currentLocale); - return $date->dayOfWeek == now()->startOfWeek()->dayOfWeek; + return $isStartOfWeek; } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index afc41c3..7679343 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -5,14 +5,17 @@ use Statamic\Entries\Entry; use Statamic\Facades\Collection; use Statamic\Fields\Field; +use Statamic\Fields\Fields; use Statamic\Fields\Value; use Statamic\Fieldtypes\Dictionary; use Statamic\Providers\AddonServiceProvider; +use Statamic\Statamic; class ServiceProvider extends AddonServiceProvider { public function bootAddon() { + // Fields::default('events_timezone', fn () => Statamic::displayTimezone()); collect(Events::setting('collections', [['collection' => 'events']])) ->each(fn (array $collection) => Collection::computed( $collection['collection'], diff --git a/src/Tags/Events.php b/src/Tags/Events.php index 277c407..3bfc413 100755 --- a/src/Tags/Events.php +++ b/src/Tags/Events.php @@ -5,6 +5,8 @@ use Carbon\Carbon; use Carbon\CarbonImmutable; use Carbon\CarbonInterface; +use Carbon\CarbonPeriod; +use Carbon\CarbonPeriodImmutable; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; use Statamic\Contracts\Query\Builder; @@ -12,6 +14,7 @@ use Statamic\Entries\Entry; use Statamic\Entries\EntryCollection; use Statamic\Facades\Compare; +use Statamic\Facades\Site; use Statamic\Support\Arr; use Statamic\Support\Str; use Statamic\Tags\Concerns\OutputsItems; @@ -32,19 +35,56 @@ public function between(): EntryCollection|array public function calendar(): Collection { + $currentLocale = CarbonImmutable::getLocale(); + CarbonImmutable::setLocale(Site::current()->locale()); + $month = $this->params->get('month', now()->englishMonth); $year = $this->params->get('year', now()->year); - $from = parse_date($month.' '.$year)->startOfMonth()->startOfWeek(); - $to = parse_date($month.' '.$year)->endOfMonth()->endOfWeek(); + $from = parse_date($month . ' ' . $year)->startOfMonth()->startOfWeek(); + $to = parse_date($month . ' ' . $year)->endOfMonth()->endOfWeek(); $occurrences = $this ->generator() ->between(from: $from, to: $to) - ->groupBy(fn (Entry $occurrence) => $occurrence->start->toDateString()) - ->map(fn (EntryCollection $occurrences, string $date) => $this->day(date: $date, occurrences: $occurrences)); + ->groupBy(function (Entry $occurrence) { + $periodInTimezone = CarbonPeriodImmutable::between( + $occurrence->start->setTimezone($this->params->get('timezone') ?? Generator::timezone())->startOfDay(), + $occurrence->end->setTimezone($this->params->get('timezone') ?? Generator::timezone())->endOfDay() + ); + + return collect($periodInTimezone->toArray()) + ->map(fn (CarbonImmutable $date) => $date->toDateString()) + ->all(); + }) + ->map(fn(EntryCollection $occurrences, string $date) => $this->day(date: $date, occurrences: $occurrences)); + + $days = $this->output($this->makeEmptyDates(from: $from, to: $to)->merge($occurrences)->values()); + + CarbonImmutable::setLocale($currentLocale); + + return $days; + } + + public function daysOfWeek(): Collection + { + /* + have to do this because Statamic sets the Carbon locale + to the `lang` of the site, instead of the `locale` + */ + $currentLocale = Carbon::getLocale(); + Carbon::setLocale(Site::current()->locale()); + + $days = collect(CarbonPeriod::dates(now()->startOfWeek(), now()->endOfWeek())) + ->map(fn (Carbon $date) => [ + 'short' => $date->format('D')[0], + 'medium' => $date->format('D'), + 'long' => $date->format('l'), + ]); + + Carbon::setLocale($currentLocale); - return $this->output($this->makeEmptyDates(from: $from, to: $to)->merge($occurrences)->values()); + return $days; } public function downloadLink(): string diff --git a/src/Types/Event.php b/src/Types/Event.php index 8728ae6..f340dac 100644 --- a/src/Types/Event.php +++ b/src/Types/Event.php @@ -14,7 +14,7 @@ abstract class Event { - abstract protected function rule(): RRuleInterface; + abstract protected function rule(bool $useEnd = false): RRuleInterface; public function __construct(protected Entry $event) {} @@ -76,7 +76,7 @@ public function occursOnDate(string|CarbonInterface $date): bool public function nextOccurrences(int $limit = 1): Collection { - return $this->collect($this->rule()->getOccurrencesAfter(date: now(), inclusive: true, limit: $limit)); + return $this->collect($this->rule(true)->getOccurrencesAfter(date: now(), inclusive: true, limit: $limit)); } public function startTime(): string diff --git a/src/Types/MultiDayEvent.php b/src/Types/MultiDayEvent.php index 78177bc..c108876 100644 --- a/src/Types/MultiDayEvent.php +++ b/src/Types/MultiDayEvent.php @@ -93,17 +93,8 @@ public function toICalendarEvents(): array ->all(); } - protected function rule(bool $collapseDays = false): RRuleInterface + protected function rule(bool $useEnd = false): RRuleInterface { - // if we're collapsing, then return an rrule instead of rset and use start of first day to end of last day - if ($this->collapseMultiDays) { - return new RRule([ - 'count' => 1, - 'dtstart' => $this->end(), - 'freq' => RRule::DAILY, - ]); - } - return tap( new RSet, fn (RSet $rset) => $this->days->each(fn (Day $day) => $rset->addRRule([ diff --git a/src/Types/RecurringEvent.php b/src/Types/RecurringEvent.php index 773623a..2b5723a 100644 --- a/src/Types/RecurringEvent.php +++ b/src/Types/RecurringEvent.php @@ -52,7 +52,7 @@ public function toICalendarEvents(): array return [$iCalEvent]; } - protected function rule(): RRuleInterface + protected function rule(bool $useEnd = false): RRuleInterface { $rule = [ 'dtstart' => $this->end(), diff --git a/src/Types/SingleDayEvent.php b/src/Types/SingleDayEvent.php index 50128d8..af39d16 100644 --- a/src/Types/SingleDayEvent.php +++ b/src/Types/SingleDayEvent.php @@ -7,11 +7,20 @@ class SingleDayEvent extends Event { - protected function rule(): RRuleInterface + protected function rule(bool $useEnd = false): RRuleInterface { + if ($useEnd) { + return new RRule([ + 'count' => 1, + 'dtstart' => $this->end(), + 'freq' => RRule::DAILY, + ]); + } + return new RRule([ - 'count' => 1, - 'dtstart' => $this->start()->setTimeFromTimeString($this->endTime()), + // 'count' => 1, + 'dtstart' => $this->start(), + 'until' => $this->end(), 'freq' => RRule::DAILY, ]); } diff --git a/tests/EventsTest.php b/tests/EventsTest.php index 0d84ef7..ce03a61 100755 --- a/tests/EventsTest.php +++ b/tests/EventsTest.php @@ -2,6 +2,7 @@ namespace TransformStudios\Events\Tests; +use Carbon\CarbonImmutable; use Illuminate\Support\Carbon; use Statamic\Extensions\Pagination\LengthAwarePaginator; use Statamic\Facades\Entry; @@ -386,3 +387,56 @@ expect($occurrences)->toBeEmpty(); }); + +test('app and event in same timezone ', function () { + $date = CarbonImmutable::createFromDate(2026, 2, 28); + Entry::make() + ->collection('events') + ->data([ + 'start_date' => $date->toDateString(), + 'start_time' => '05:00', + 'end_time' => '23:00', + ])->save(); + + $events1 = Events::fromCollection('events')->between($date->startOfMonth(), $date->endOfMonth()); + $events2 = Events::fromCollection('events')->between($date->startOfMonth(), $date->endOfMonth()->endOfWeek()); + + expect($events1)->toHaveCount(1); + expect($events2)->toHaveCount(1); +}); + +test('app and event in different timezone', function () { + $date = CarbonImmutable::createFromDate(2026, 2, 28); + Entry::make() + ->collection('events') + ->data([ + 'start_date' => $date->toDateString(), + 'timezone' => 'America/Los_Angeles', + 'start_time' => '05:00', + 'end_time' => '23:00', + ])->save(); + + $events1 = Events::fromCollection('events')->between($date->startOfMonth(), $date->endOfMonth()); + $events2 = Events::fromCollection('events')->between($date->startOfMonth(), $date->endOfMonth()->endOfWeek()); + + expect($events1)->toHaveCount(1); + expect($events2)->toHaveCount(1); +}); + +test('event with timezone offset appears on the correct UTC date', function () { + $date = CarbonImmutable::createFromDate(2026, 2, 28); + Entry::make() + ->collection('events') + ->data([ + 'start_date' => $date->toDateString(), + 'timezone' => 'America/Los_Angeles', + 'start_time' => '22:00', + 'end_time' => '23:00', + ])->save(); + + $events1 = Events::fromCollection('events')->between($date->startOfDay(), $date->endOfDay()); + $events2 = Events::fromCollection('events')->between($date->startOfDay()->addDay(), $date->endOfDay()->addDay()); + + expect($events1)->toHaveCount(0); + expect($events2)->toHaveCount(1); +}); diff --git a/tests/Modifiers/IsStartOfWeekTest.php b/tests/Modifiers/IsStartOfWeekTest.php new file mode 100644 index 0000000..0a28048 --- /dev/null +++ b/tests/Modifiers/IsStartOfWeekTest.php @@ -0,0 +1,26 @@ +andReturn(new Site('default', [ + 'name' => 'Laravel', + 'url' => '/', + 'locale' => 'en_US', + 'lang' => 'en', + ], true)); + + Carbon::setTestNow('2026-3-25 12:00pm'); + + $modified = modify('2026-3-22'); + expect($modified)->toBe(true); +}); + +function modify(string $value) +{ + return Modify::value($value)->isStartOfWeek()->fetch(); +} diff --git a/tests/Pest.php b/tests/Pest.php index de7b364..8b301d3 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,7 @@ collection('events')->data($data); } diff --git a/tests/Tags/EventsTest.php b/tests/Tags/EventsTest.php index ad53291..008935f 100755 --- a/tests/Tags/EventsTest.php +++ b/tests/Tags/EventsTest.php @@ -5,6 +5,8 @@ use Illuminate\Support\Carbon; use Statamic\Facades\Cascade; use Statamic\Facades\Entry; +use Statamic\Facades\Site as SiteFacade; +use Statamic\Sites\Site; use Statamic\Support\Arr; use TransformStudios\Events\Tags\Events; @@ -387,3 +389,23 @@ expect($this->tag->today())->toHaveCount(0); }); + +it('sets the correct short form of the days of week', function () { + expect($this->tag->daysOfWeek())->pluck('short')->toMatchArray(['M', 'T', 'W', 'T', 'F', 'S', 'S']); +}); + +it('uses the current site locale to get days of week', function () { + SiteFacade::shouldReceive('current') + ->andReturn(new Site('default', [ + 'name' => 'Laravel', + 'url' => '/', + 'locale' => 'en_US', + 'lang' => 'en', + ], true)); + + expect($this->tag->daysOfWeek())->first()->toBe([ + 'short' => 'S', + 'medium' => 'Sun', + 'long' => 'Sunday', + ]); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index cf978cc..be5d962 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,8 +20,6 @@ abstract class TestCase extends AddonTestCase protected string $addonServiceProvider = ServiceProvider::class; - protected $fakeStacheDirectory = __DIR__.'/__fixtures__/dev-null'; - protected $shouldFakeVersion = true; protected Collection $collection; @@ -32,9 +30,13 @@ protected function setUp(): void { parent::setUp(); - if (! file_exists($this->fakeStacheDirectory)) { - mkdir($this->fakeStacheDirectory, 0777, true); - } + Taxonomy::make('categories')->save(); + Term::make('one')->taxonomy('categories')->dataForLocale('default', [])->save(); + Term::make('two')->taxonomy('categories')->dataForLocale('default', [])->save(); + + $this->collection = CollectionFacade::make('events') + ->taxonomies(['categories']) + ->save(); } protected function getEnvironmentSetUp($app) @@ -43,19 +45,11 @@ protected function getEnvironmentSetUp($app) // Assume the pro edition within tests $app['config']->set('statamic.editions.pro', true); - $app['config']->set('events.timezone', 'UTC'); Statamic::booted(function () { Fieldset::addNamespace('events', __DIR__.'/../resources/fieldsets'); app()->extend(BlueprintRepository::class, fn ($repo) => $repo->setDirectory(__DIR__.'/__fixtures__/blueprints')); - Taxonomy::make('categories')->save(); - Term::make('one')->taxonomy('categories')->dataForLocale('default', [])->save(); - Term::make('two')->taxonomy('categories')->dataForLocale('default', [])->save(); - - $this->collection = CollectionFacade::make('events') - ->taxonomies(['categories']) - ->save(); }); } } diff --git a/tests/Types/RecurringDailyEventsTest.php b/tests/Types/RecurringDailyEventsTest.php index daf09f0..ac7c9b0 100755 --- a/tests/Types/RecurringDailyEventsTest.php +++ b/tests/Types/RecurringDailyEventsTest.php @@ -6,6 +6,7 @@ use Carbon\CarbonImmutable; use Statamic\Facades\Entry; use TransformStudios\Events\EventFactory; +use TransformStudios\Events\Events; test('null next date if now after end date', function () { $recurringEntry = Entry::make() @@ -68,150 +69,24 @@ 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']); -// } +test('app and event in different timezone ', function () { + $startDate = CarbonImmutable::createFromDate(2026, 2, 15); + Entry::make() + ->collection('events') + ->data([ + 'start_date' => $startDate->toDateString(), + 'timezone' => 'America/Los_Angeles', + 'start_time' => '05:00', + 'end_time' => '16:00', + 'recurrence' => 'monthly', + 'specific_days' => ['third_monday'], + ])->save(); + + $events = Events::fromCollection('events') + ->between( + CarbonImmutable::createFromDate(2026, 2, 15)->startOfDay(), + CarbonImmutable::createFromDate(2026, 3, 16)->endOfDay() + ); + + expect($events)->toHaveCount(2); +}); diff --git a/tests/Types/RecurringEveryXEventsTest.php b/tests/Types/RecurringEveryXEventsTest.php index 1079266..87f52d6 100755 --- a/tests/Types/RecurringEveryXEventsTest.php +++ b/tests/Types/RecurringEveryXEventsTest.php @@ -245,120 +245,3 @@ expect($occurrences[0]->start)->toEqual($events[0]); expect($occurrences[1]->start)->toEqual($events[1]); }); - -/* - public function test_can_get_last_day_when_before() - { - Carbon::setTestNow(Carbon::now()->setTimeFromTimeString('10:30')); - - $event = [ - 'id' => 'daily-event', - 'start_date' => Carbon::now()->toDateString(), - 'start_time' => '13:00', - 'end_time' => '15:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'days', - 'end_date' => Carbon::now()->addDays(8)->toDateString(), - ]; - - $this->events->add(EventFactory::createFromArray($event)); - - $from = Carbon::now()->addDays(7); - $to = Carbon::now()->endOfDay()->addDays(10); - - $events = $this->events->all($from, $to); - - $this->assertCount(1, $events); - - $event['start_date'] = Carbon::now()->addDays(8)->toDateString(); - - $this->assertEquals($event, $events[0]->toArray()); - } - - public function test_generates_all_daily_occurrences_single_event_from_to_with_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' => 'every', - 'interval' => 2, - 'period' => 'days', - 'end_date' => Carbon::now()->addDays(8)->toDateString(), - ] - )); - - $from = Carbon::now()->subDays(1); - $to = Carbon::now()->endOfDay()->addDays(10); - - $events = $this->events->all($from, $to); - - $this->assertCount(5, $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' => 'every', - 'interval' => 2, - 'period' => 'days', - ] - )); - - $from = Carbon::now()->subDays(1); - $to = Carbon::now()->endOfDay()->addDays(10); - - $events = $this->events->all($from, $to); - - $this->assertCount(6, $events); - } - - public function test_can_generate_next_x_weeks_if_in_different_weeks() - { - $event = EventFactory::createFromArray( - [ - 'start_date' => '2020-01-03', - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'weeks', - ] - ); - - $day = $event->upcomingDate(Carbon::parse('2021-01-31')); - - $this->assertNotNull($day); - $this->assertEquals('2021-02-12', $day->startDate()); - } - - public function test_returns_null_when_dates_between_dont_have_event() - { - $event = EventFactory::createFromArray( - [ - 'start_date' => '2021-01-29', - 'start_time' => '11:00', - 'end_time' => '12:00', - 'recurrence' => 'every', - 'interval' => 2, - 'period' => 'weeks', - ] - ); - - $dates = $event->datesBetween('2021-02-18', '2021-02-19'); - - $this->assertEmpty($dates); - } -*/ diff --git a/tests/Types/SingleDayEventsTest.php b/tests/Types/SingleDayEventsTest.php index 867f563..3c2032f 100755 --- a/tests/Types/SingleDayEventsTest.php +++ b/tests/Types/SingleDayEventsTest.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use Carbon\CarbonImmutable; use Carbon\CarbonTimeZone; +use Illuminate\Support\Facades\Date; use Statamic\Facades\Entry; use TransformStudios\Events\EventFactory; use TransformStudios\Events\Types\SingleDayEvent; @@ -144,3 +145,37 @@ expect($nextOccurrences[0]->has_end_time)->toBeFalse(); }); + +it('queries occurrences based on timezone', function () { + $utcDate = Date::parse('2026-03-26 11am')->toImmutable(); + $laDate = $utcDate->shiftTimezone('America/Los_Angeles'); + + $entry = makeEvent([ + 'start_date' => '2026-03-26', + 'timezone' => 'America/Los_Angeles', + 'start_time' => '05:00', + 'end_time' => '23:00', + ]); + + $events1 = EventFactory::createFromEntry($entry)->occurrencesBetween($utcDate->startOfDay(), $utcDate->endOfDay()); + $events2 = EventFactory::createFromEntry($entry)->occurrencesBetween($laDate->startOfDay(), $laDate->endOfDay()); + + expect($events1)->toHaveCount(1); + expect($events2)->toHaveCount(1); +}); + +it('retrieves occurrences that span days in different timezone than event', function ($from, $to, $count) { + $entry = Entry::make() + ->collection('events') + ->data([ + 'start_date' => now()->toDateString(), + 'timezone' => 'America/Los_Angeles', + 'start_time' => '05:00', + 'end_time' => '23:00', + ]); + + expect(EventFactory::createFromEntry($entry))->occurrencesBetween($from, $to)->toHaveCount($count); +})->with([ + [now()->startOfDay(), now()->endOfDay()->addDay(), 1], + [now()->startOfDay(), now()->endOfDay(), 1], +]);