We are working on a site featuring an event calendar. Each event is stored in a single page, containing a start and end date. Now we’d need the option to make events recuring in given intervals (every day, every week, every month, every year) with optional end date (“repeat until …”).
Are there any existing solutions to handle this scenario in Kirby?
Not that I know of. I had a project where I used a start date, end date and two additional fields: a select field for the interval, and a structure field for exceptions. Then calculated all events from this data.
I thought of something similar but it sounds computationally intensive for Kirby to get all dates related to – say – the current week. Or is this neglectable? We are not dealing with that many dates.
I would limit the end date, either through the date field itself or by setting your end date (if none is given) to let’s say 1 year or so in the future (or less, depending on interval). You can do this dynamically.
You can also cache your results, so I don’t see any problems.
I actually need something like this too at the moment, but there is a small issue with repeating events. It works ok if the event is, for example, on the second Sunday of every month. That you can repeat easily but if the event is always the 13th day of the month, that gets harder. This year that day might be a Monday and the event should be on a Sunday. Being able to adjust the event to the 12th for that year is a tricky thing to implement.
We actually gave up and just use a structure field so that the specific dates can be picked manually for the next few years.
How would this work if the event started at say, 10pm one day but didn’t finish until 2am the next day, like a gig or something?. How would you repeat an event that does not start and end on the same day?
Hey @nilshoerrmann, did you ever get around to apply this solution to your project?
I just found this thread and have the exact same situation: The event calendar I’m working on needs a subpage for each event, so I store them as individual pages. I can’t seem to crack this repeat problem though:
So far, I have unsuccessfully tried it with the kirby-recurr plugin which has helped me output a list of repeating dates out of a structure field, but I struggle to feed the rest of the data back into that array and then generate the necessary subpages out of it.
For the other approach, so starting from “actual” pages (content folders) for the events, I am suspecting that I would need to clone the repeating events’ pages somehow but got stuck there
I would be super thankful if you have any old code from your project that could help me figure this out! Unfortunately my php knowledge is not yet on the level I would need to turn the method @texnixe proposed into something useful.
Yes, we finished that project sucessfully but it’s a complex setup that I cannot share here publicly. But maybe this custom collection will give you an idea how to solve your issues. Be aware we also have a structure field to define exeptions – like canceled or moved recurrences – which might this a bit hard to read.
<?php
return function ($site) {
$boundaries = [new DateTime('today'), new DateTime('+6 month')];
$recurrings = $site
->find('kalender')
->children()
->filter(function ($child) {
return $child->interval()->isNotEmpty();
});
$recurrences = new Pages();
foreach ($recurrings as $recurring) {
$start = $end = new DateTime($recurring->start());
if ($recurring->end()->isNotEmpty()) {
$end = new DateTime($recurring->end());
}
$duration = $start->diff($end);
$interval = new DateInterval($recurring->interval());
$endinterval = $boundaries[1];
if ($recurring->endinterval()->isNotEmpty()) {
$endinterval = $recurring->endinterval()->toDateTime();
}
// The end date is exclude thus we have to add an extra day to the end
$period = new DatePeriod(
$start,
$interval,
$endinterval->modify('+1 day')
);
$exceptions = $recurring
->exceptions()
->toStructure()
->filterBy('date', 'date >=', $boundaries[0]->format('Y-m-d'))
->filterBy('date', 'date <=', $boundaries[1]->format('Y-m-d'));
foreach ($period as $occurrence) {
$date = $occurrence->format('Y-m-d');
// Ignore occurrences outside of current calendar boundaries
if ($occurrence < $boundaries[0]) {
continue;
}
// Check exceptions
if ($exceptions->isNotEmpty()) {
$skip = false;
foreach ($exceptions as $exception) {
if ($exception->date()->value() === $date) {
if ($exception->change()->isNotEmpty()) {
// Apply date change
$occurrence = $exception->change()->toDateTime();
} else {
$skip = true;
}
}
}
// Ignore canceled occurrence
if ($skip === true) {
continue;
}
}
$content = $recurring->content()->toArray();
$content['start'] = $date;
$content['end'] = $occurrence->add($duration)->format('Y-m-d');
$recurrences->append(
$recurring->clone([
'slug' =>
Str::replace($date, '-', '') . '-' . $recurring->slug(),
'content' => $content
])
);
}
}
return $recurrences;
};