Repeating or recurring dates that may not follow consistent rules, best user experience?

What is the best user experience for being able to set recurring dates in Drupal 8?

We’ll look at this from two main perspectives. First, for the content editor, how hard it is to do what they need to do as far as setting dates and times. What is it like to add a single date? (The most common use case for us, adding a simple one-off event, as the recurring events on Find It Cambridge tend to be programmatically created, imported from another source.) What is it like to add a few arbitrary dates?

And we’ll

Implicit in all of this is the software developer’s perspective, how can i make this happen without dedicating the entirety of my life to it for a month (or irregularly repeating fourth Tuesdays if any else it moves to the third Tuesday, every Thursday, alternate Mondays, afternoons except on the first and fifteenth of the month, biweekly in both interpretations of the term, etc., indefinitely)?

Clayton Dewey and i (ben melançon) liked the idea of doing just a normal multivalue date field, rather than needing to have some interpreter of complex recurring rules interface with any part of the system that wanted

But the movement of the community seems very much in the opposite direction, with all the editor helping widgets and date display helping widgets being based on defining or reading complex rules, rather than helpers for adding and summarizing multiple dates.

The Recurring dates library for PHP’s readme puts this approach most plainly.

So let’s take a look at those solutions which do exist, before returning again to consider how our approach could work. Ideally there will be some things we can share, but unfortunately there doesn’t seem to be any part of these libraries that takes a set of dates and derives a recurring rule based on that, which could then be used to create a summary, without us having to write summarization code ourselves (which would start with, say, code to look up what day of the week something is on, and get complicated very quickly).

There’s whole giant extensions to PHP’s datetime that don’t touch on summarizing repeating dates or turning a list of dates into a recurrence rule.

https://www.drupal.org/project/date_recur

composer require drupal/date_recur:^2.0

It’s not just Drupal or PHP. This is the industry standard, as represnted by the IETF’s RFC 2445, Internet Calendaring and Scheduling Core Object Specification (iCalendar)’s section 4.8.5.4 on recurrence rule.

Here’s one introduction to RRules. Here’s another general introduction to recurrence that highly recommends using the standard RRULE format.

That second introduction, by a creator of a calendar program, proclaims a “Rule Zero” which is explicitly to not do what we want to do, store individual recurring event instances.

The reasoning is sound, but basically isn’t valid for our use case.

On Find It Cambridge, if something repeats daily, it’s not an event, but a program. We require events be updated every year, or they are considered out-of-date and not shown to the public. Hundreds of occurrences, let alone infinitely repeating events, are simply disallowed as a very rational design choice. We’re not creating a calendaring program, we’re putting together a time-limited event listing for mostly one-off occurrences.

Be that as it may, even if two dilettantes are smarter than the programming community as a whole, for the particular needs we’re solving for, that doesn’t change the fact that all the tools and libraries and integrations and whatnot are being built for the way that the whole programming community has long since decided to go.

This writeup is very similar to how we handled it in Drupal 7 also.


Searched for:

In desperation, i posted something to stackoverflow for which i will be flamed…

How to work with RRULE datetime series and arbitrary, non-RRULE datetime series coherently in one application

How to generate an RRULE (any RRULE, no matter how supoptimal) from a series of dates— or, failing that, to use RRULE-defined dates in an easily compatible way with lists of dates.

In other words, how to deal with arbitrary lists of dates that modern computer science has collectively decided are impossible to convert to RRULEs, while simultaneously supporting the approach that everyone says to take, RRULE.

Yet deriving RRULEs from lists of dates is so obviously a job for computers that i find it hard to believe that it’s not a thing you have to do in third year of undergraduate computer science.

Yes, i’ve seen this answer: “it’s impossible to know the exact rule that generated a collection of dates”

Now, i hope everyone is sitting down, because this is going to blow your minds, but there exists series of repeating dates that were not generated with an RRULE in the first place.

(Not just being facetious; i was surprised myself that the library system in a significant US city would not be using RRULE internally, but to be very concrete about the real-world need for this capability, it does not and we’ll close with an example from it.)

So to be very clear, i’m not looking for the ‘best’ RRULE for a sequence of datetimes, which would be a judgement call anyway. I’m looking for any valid RRULE automatically produced from a list of repeating dates. For example: A fine start would be checking if it’s daily and, failing that, producing the ugliest RRULE known to human or machine, that’s just a string of exceptions.

And maybe that’s another way to approach this: suggestions or examples from when humans are using an RRULE widget to provide the series of events that aren’t following nice repeating rules. Do you fall back to just allowing additional dates?

Anyhow, here’s that promised real-world example of an array of dates that will have to be regularly dealt with that do not derive from an RRULE themselves, but could probably messily be reduced to one:

        "future_dates": [
            {
                "event_id": 4998685,
                "start": "2019-10-01T10:30:00-04:00"
            },
            {
                "event_id": 4998686,
                "start": "2019-10-08T10:30:00-04:00"
            },
            {
                "event_id": 4998687,
                "start": "2019-10-15T10:30:00-04:00"
            },
            {
                "event_id": 4998688,
                "start": "2019-10-22T10:30:00-04:00"
            },
            {
                "event_id": 4998689,
                "start": "2019-10-29T10:30:00-04:00"
            },
            {
                "event_id": 4998690,
                "start": "2019-11-05T10:30:00-05:00"
            },
            {
                "event_id": 4998691,
                "start": "2019-11-12T10:30:00-05:00"
            },
            {
                "event_id": 4998692,
                "start": "2019-11-19T10:30:00-05:00"
            },
            {
                "event_id": 4998693,
                "start": "2019-11-26T10:30:00-05:00"
            },
            {
                "event_id": 4998694,
                "start": "2019-12-03T10:30:00-05:00"
            },
            {
                "event_id": 4998695,
                "start": "2019-12-10T10:30:00-05:00"
            },
            {
                "event_id": 4998696,
                "start": "2019-12-17T10:30:00-05:00"
            },
            {
                "event_id": 4998698,
                "start": "2019-12-31T10:30:00-05:00"
            },
            {
                "event_id": 4998699,
                "start": "2020-01-07T10:30:00-05:00"
            },
            {
                "event_id": 4998700,
                "start": "2020-01-14T10:30:00-05:00"
            },
            {
                "event_id": 4998701,
                "start": "2020-01-21T10:30:00-05:00"
            },
            {
                "event_id": 4998702,
                "start": "2020-01-28T10:30:00-05:00"
            },
            {
                "event_id": 4998703,
                "start": "2020-02-04T10:30:00-05:00"
            },
            {
                "event_id": 4998704,
                "start": "2020-02-11T10:30:00-05:00"
            },
            {
                "event_id": 4998705,
                "start": "2020-02-18T10:30:00-05:00"
            },
            {
                "event_id": 4998706,
                "start": "2020-02-25T10:30:00-05:00"
            },
            {
                "event_id": 4998707,
                "start": "2020-03-03T10:30:00-05:00"
            },
            {
                "event_id": 4998708,
                "start": "2020-03-10T10:30:00-04:00"
            },
            {
                "event_id": 4998709,
                "start": "2020-03-17T10:30:00-04:00"
            },
            {
                "event_id": 4998710,
                "start": "2020-03-24T10:30:00-04:00"
            },
            {
                "event_id": 4998711,
                "start": "2020-03-31T10:30:00-04:00"
            },
            {
                "event_id": 4998712,
                "start": "2020-04-07T10:30:00-04:00"
            },
            {
                "event_id": 4998713,
                "start": "2020-04-14T10:30:00-04:00"
            },
            {
                "event_id": 4998714,
                "start": "2020-04-21T10:30:00-04:00"
            },
            {
                "event_id": 4998715,
                "start": "2020-04-28T10:30:00-04:00"
            },
            {
                "event_id": 4998716,
                "start": "2020-05-05T10:30:00-04:00"
            },
            {
                "event_id": 4998717,
                "start": "2020-05-12T10:30:00-04:00"
            },
            {
                "event_id": 4998718,
                "start": "2020-05-19T10:30:00-04:00"
            },
            {
                "event_id": 4998719,
                "start": "2020-05-26T10:30:00-04:00"
            },
            {
                "event_id": 4998720,
                "start": "2020-06-02T10:30:00-04:00"
            },
            {
                "event_id": 4998721,
                "start": "2020-06-09T10:30:00-04:00"
            },
            {
                "event_id": 4998722,
                "start": "2020-06-16T10:30:00-04:00"
            },
            {
                "event_id": 4998723,
                "start": "2020-06-23T10:30:00-04:00"
            },
            {
                "event_id": 4998724,
                "start": "2020-06-30T10:30:00-04:00"
            },
            {
                "event_id": 4998725,
                "start": "2020-07-07T10:30:00-04:00"
            },
            {
                "event_id": 4998726,
                "start": "2020-07-14T10:30:00-04:00"
            },
            {
                "event_id": 4998727,
                "start": "2020-07-21T10:30:00-04:00"
            },
            {
                "event_id": 4998728,
                "start": "2020-07-28T10:30:00-04:00"
            },
            {
                "event_id": 4998729,
                "start": "2020-08-04T10:30:00-04:00"
            },
            {
                "event_id": 4998730,
                "start": "2020-08-11T10:30:00-04:00"
            },
            {
                "event_id": 4998731,
                "start": "2020-08-18T10:30:00-04:00"
            },
            {
                "event_id": 4998732,
                "start": "2020-08-25T10:30:00-04:00"
            },
            {
                "event_id": 4998733,
                "start": "2020-09-01T10:30:00-04:00"
            },
            {
                "event_id": 4998734,
                "start": "2020-09-08T10:30:00-04:00"
            },
            {
                "event_id": 4998735,
                "start": "2020-09-15T10:30:00-04:00"
            },
            {
                "event_id": 4998736,
                "start": "2020-09-22T10:30:00-04:00"
            },
            {
                "event_id": 4998737,
                "start": "2020-09-29T10:30:00-04:00"
            },
            {
                "event_id": 4998738,
                "start": "2020-10-06T10:30:00-04:00"
            },
            {
                "event_id": 4998739,
                "start": "2020-10-13T10:30:00-04:00"
            },
            {
                "event_id": 4998740,
                "start": "2020-10-20T10:30:00-04:00"
            },
            {
                "event_id": 4998741,
                "start": "2020-10-27T10:30:00-04:00"
            },
            {
                "event_id": 4998742,
                "start": "2020-11-03T10:30:00-05:00"
            },
            {
                "event_id": 4998743,
                "start": "2020-11-10T10:30:00-05:00"
            },
            {
                "event_id": 4998744,
                "start": "2020-11-17T10:30:00-05:00"
            },
            {
                "event_id": 4998745,
                "start": "2020-11-24T10:30:00-05:00"
            },
            {
                "event_id": 4998746,
                "start": "2020-12-01T10:30:00-05:00"
            },
            {
                "event_id": 4998747,
                "start": "2020-12-08T10:30:00-05:00"
            },
            {
                "event_id": 4998748,
                "start": "2020-12-15T10:30:00-05:00"
            },
            {
                "event_id": 4998749,
                "start": "2020-12-22T10:30:00-05:00"
            },
            {
                "event_id": 4998750,
                "start": "2020-12-29T10:30:00-05:00"
            },
            {
                "event_id": 4998751,
                "start": "2021-01-05T10:30:00-05:00"
            },
            {
                "event_id": 4998752,
                "start": "2021-01-12T10:30:00-05:00"
            }
        ]