Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ You can also use our sample fieldset by importing `events::event`.

* all the single day fields
* `recurrence` - **Optional** - One of `daily`, `weekly`, `monthly`, `annually`, `every`
* `specific_days` - **Optional** - when `recurrence` is `monthly`, you can choose options like every 3rd Tuesday, etc. You can have more than one.
* `interval` - **Optional** - required if `recurrence` is `every` and indicates the frequency of the event
* `period` - **Optional** - required if `recurrence` is `every` and indicates the period of recurrence. One of `days`, `weeks`, `months`, `years`
* `end_date` - **Optional** - when is the last event. If `recurrence` is set and this is not, the event goes on forever
Expand Down
97 changes: 19 additions & 78 deletions resources/fieldsets/event.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@ fields:
width: 33
display: Recurrence
default: none
instructions_position: above
listable: hidden
visibility: visible
replicator_preview: true
taggable: false
push_tags: false
multiple: false
clearable: false
searchable: true
cast_booleans: false
hide_display: false
-
handle: timezone
field:
Expand All @@ -36,9 +25,6 @@ fields:
cast_booleans: false
display: Timezone
type: timezones
icon: select
listable: hidden
instructions_position: above
mode: typeahead
width: 33
-
Expand All @@ -47,14 +33,17 @@ fields:
type: toggle
width: 33
display: 'All Day?'
instructions_position: above
listable: hidden
visibility: visible
replicator_preview: true
default: false
hide_display: false
unless:
recurrence: 'equals multi_day'
-
handle: specific_days
field:
dictionary: month_day_recurrence
type: dictionary
display: 'Which Day(s)'
width: 100
if:
recurrence: 'equals monthly'
-
handle: interval
field:
Expand Down Expand Up @@ -85,18 +74,8 @@ fields:
input_format: M/D/YYYY
width: 50
display: 'Start Date'
instructions_position: above
listable: hidden
visibility: visible
replicator_preview: true
mode: single
inline: true
full_width: true
columns: 1
rows: 1
time_enabled: false
time_seconds_enabled: false
hide_display: false
unless_any:
multi_day: 'equals true'
recurrence: 'equals multi_day'
Expand All @@ -110,19 +89,9 @@ fields:
input_format: M/D/YYYY
display: 'End Date'
width: 50
listable: hidden
mode: single
time_enabled: false
time_required: false
full_width: true
inline: true
columns: 1
rows: 1
instructions_position: above
visibility: visible
replicator_preview: true
time_seconds_enabled: false
hide_display: false
if:
recurrence: 'contains_any daily, weekly, monthly, every'
-
Expand All @@ -132,12 +101,6 @@ fields:
width: 25
display: 'Start Time'
instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)'
instructions_position: above
listable: hidden
visibility: visible
replicator_preview: true
seconds_enabled: false
hide_display: false
unless_any:
multi_day: 'equals true'
all_day: 'equals true'
Expand All @@ -149,12 +112,6 @@ fields:
width: 25
display: 'End Time'
instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)'
instructions_position: above
listable: hidden
visibility: visible
replicator_preview: true
seconds_enabled: false
hide_display: false
unless_any:
multi_day: 'equals true'
all_day: 'equals true'
Expand All @@ -163,7 +120,12 @@ fields:
handle: days
field:
type: grid
mode: table
display: 'Event Days'
add_row: 'Add Day'
min_rows: 1
if_any:
multi_day: 'equals true'
recurrence: 'equals multi_day'
fields:
-
handle: date
Expand Down Expand Up @@ -210,24 +172,14 @@ fields:
field: 'events::event.all_day'
config:
width: 25
display: 'Event Days'
add_row: 'Add Day'
listable: hidden
reorderable: true
instructions_position: above
visibility: visible
replicator_preview: true
min_rows: 1
fullscreen: true
hide_display: false
if_any:
multi_day: 'equals true'
recurrence: 'equals multi_day'
-
handle: exclude_dates
field:
type: grid
mode: table
display: 'Exclude Days'
add_row: 'Add Day'
if_any:
recurrence: 'contains_any monthly, daily, weekly, every'
fields:
-
handle: date
Expand All @@ -238,14 +190,3 @@ fields:
require_time: false
input_format: M/D/YYYY
display: Date
display: 'Exclude Days'
add_row: 'Add Day'
listable: hidden
reorderable: true
instructions_position: above
visibility: visible
replicator_preview: true
fullscreen: true
hide_display: false
if_any:
recurrence: 'contains_any monthly, daily, weekly, every'
56 changes: 56 additions & 0 deletions src/Dictionaries/MonthDayRecurrence.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace TransformStudios\Events\Dictionaries;

use Statamic\Dictionaries\BasicDictionary;

class MonthDayRecurrence extends BasicDictionary
{
protected function getItems(): array
{
return [
['label' => 'First Sunday', 'value' => 'first_sunday', 'rrule' => '1SU'],
['label' => 'First Monday', 'value' => 'first_monday', 'rrule' => '1MO'],
['label' => 'First Tuesday', 'value' => 'first_tuesday', 'rrule' => '1TU'],
['label' => 'First Wednesday', 'value' => 'first_wednesday', 'rrule' => '1WE'],
['label' => 'First Thursday', 'value' => 'first_thursday', 'rrule' => '1TH'],
['label' => 'First Friday', 'value' => 'first_friday', 'rrule' => '1FR'],
['label' => 'First Saturday', 'value' => 'first_saturday', 'rrule' => '1SA'],
['label' => 'Second Sunday', 'value' => 'second_sunday', 'rrule' => '2SU'],
['label' => 'Second Monday', 'value' => 'second_monday', 'rrule' => '2MO'],
['label' => 'Second Tuesday', 'value' => 'second_tuesday', 'rrule' => '2TU'],
['label' => 'Second Wednesday', 'value' => 'second_wednesday', 'rrule' => '2WE'],
['label' => 'Second Thursday', 'value' => 'second_thursday', 'rrule' => '2TH'],
['label' => 'Second Friday', 'value' => 'second_friday', 'rrule' => '2FR'],
['label' => 'Second Saturday', 'value' => 'second_saturday', 'rrule' => '2SA'],
['label' => 'Third Sunday', 'value' => 'third_sunday', 'rrule' => '3SU'],
['label' => 'Third Monday', 'value' => 'third_monday', 'rrule' => '3MO'],
['label' => 'Third Tuesday', 'value' => 'third_tuesday', 'rrule' => '3TU'],
['label' => 'Third Wednesday', 'value' => 'third_wednesday', 'rrule' => '3WE'],
['label' => 'Third Thursday', 'value' => 'third_thursday', 'rrule' => '3TH'],
['label' => 'Third Friday', 'value' => 'third_friday', 'rrule' => '3FR'],
['label' => 'Third Saturday', 'value' => 'third_saturday', 'rrule' => '3SA'],
['label' => 'Fourth Sunday', 'value' => 'fourth_sunday', 'rrule' => '4SU'],
['label' => 'Fourth Monday', 'value' => 'fourth_monday', 'rrule' => '4MO'],
['label' => 'Fourth Tuesday', 'value' => 'fourth_tuesday', 'rrule' => '4TU'],
['label' => 'Fourth Wednesday', 'value' => 'fourth_wednesday', 'rrule' => '4WE'],
['label' => 'Fourth Thursday', 'value' => 'fourth_thursday', 'rrule' => '4TH'],
['label' => 'Fourth Friday', 'value' => 'fourth_friday', 'rrule' => '4FR'],
['label' => 'Fourth Saturday', 'value' => 'fourth_saturday', 'rrule' => '4SA'],
['label' => 'Fifth Sunday', 'value' => 'fifth_sunday', 'rrule' => '5SU'],
['label' => 'Fifth Monday', 'value' => 'fifth_monday', 'rrule' => '5MO'],
['label' => 'Fifth Tuesday', 'value' => 'fifth_tuesday', 'rrule' => '5TU'],
['label' => 'Fifth Wednesday', 'value' => 'fifth_wednesday', 'rrule' => '5WE'],
['label' => 'Fifth Thursday', 'value' => 'fifth_thursday', 'rrule' => '5TH'],
['label' => 'Fifth Friday', 'value' => 'fifth_friday', 'rrule' => '5FR'],
['label' => 'Fifth Saturday', 'value' => 'fifth_saturday', 'rrule' => '5SA'],
['label' => 'Last Sunday', 'value' => 'last_sunday', 'rrule' => '-1SU'],
['label' => 'Last Monday', 'value' => 'last_monday', 'rrule' => '-1MO'],
['label' => 'Last Tuesday', 'value' => 'last_tuesday', 'rrule' => '-1TU'],
['label' => 'Last Wednesday', 'value' => 'last_wednesday', 'rrule' => '-1WE'],
['label' => 'Last Thursday', 'value' => 'last_thursday', 'rrule' => '-1TH'],
['label' => 'Last Friday', 'value' => 'last_friday', 'rrule' => '-1FR'],
['label' => 'Last Saturday', 'value' => 'last_saturday', 'rrule' => '-1SA'],
];
}
}
10 changes: 10 additions & 0 deletions src/Types/RecurringEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace TransformStudios\Events\Types;

use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use RRule\RRule;
use RRule\RRuleInterface;
Expand All @@ -11,6 +12,11 @@

class RecurringEvent extends Event
{
public function onSpecificDays(): array
{
return $this->specific_days ?? [];
}

public function interval(): int
{
return $this->interval ?? 1;
Expand Down Expand Up @@ -44,6 +50,10 @@ protected function rule(): RRuleInterface
$rule['until'] = Carbon::parse($end)->shiftTimezone($this->timezone['timezone'])->endOfDay();
}

if (! empty($days = $this->onSpecificDays())) {
$rule['byday'] = Arr::pluck($days, 'rrule');
}

return new RRule($rule);
}

Expand Down
28 changes: 25 additions & 3 deletions tests/Unit/RecurringEventsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
class RecurringEventsTest extends TestCase
{
#[Test]
public function canCreateRecurringEvent()
public function can_create_recurring_event()
{
$recurringEntry = Entry::make()
->collection('events')
Expand All @@ -32,7 +32,7 @@ public function canCreateRecurringEvent()
}

#[Test]
public function wontCreateRecurringEventWhenMultiDay()
public function wont_create_recurring_event_when_multi_day()
{
$recurringEntry = Entry::make()
->collection('events')
Expand All @@ -50,7 +50,7 @@ public function wontCreateRecurringEventWhenMultiDay()
}

#[Test]
public function canShowLastOccurrenceWhenNoEndTime()
public function can_show_last_occurrence_when_no_end_time()
{
Carbon::setTestNow(now()->setTimeFromTimeString('10:00'));

Expand All @@ -69,4 +69,26 @@ public function canShowLastOccurrenceWhenNoEndTime()

$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);
}
}
Loading