Recurring Events

We’re about to start building recurring events for Roundabout, but before we do, I’d love to tap into the collective wisdom of the atproto community.

Our existing events are stored as community.lexicon.calendar.event records. Those records don’t capture necessary information about event recurrence, so we’ll need to either extend the lexicon or use sidecar records.

The data that we want to store is relatively complex. RFC 5545 is comprehensive on what recurrence rules we might want to support[1], but without wanting to prejudice the conversation, I’d say it’s unlikely we’ll implement RFC 5545’s data model[2].

Historically, the subset of recurrence options we’d support would have been limited by UI/UX concerns, but now we should be able to support them all and have plain text reliably parsed into a complex recursion rule[3].

Our plan is to implement this “soon” and not wait for community consensus around a lexicon. That said, we’d like to implement something that doesn’t suck and is likely to offer us an easy pathway to adopting a future community lexicon.

So, with the above as context / scope for discussion, I have a few questions, and feel free to raise additional issues:

  1. Recurrence is normally implemented as “virtual”: recurring events are synthetic instances of a base event. There are a number of pros and cons to this, compared with doing the concrete “create a new record for each recurrence” approach, particularly over atproto.

    How could or should an atproto lexicon handle this aspect of recurring events?

  2. Does anyone in this community have strong preferences around the data model for storing recursion rules?

  3. Does anyone have any recommendations for actual code that implements event recurrence? This is an area I’ve so far avoided in my career, but know that it’s fraught. I would love to use actual reliable code, recommended by people with scars.

I do have a number of my own thoughts here, but want to open with “actual questions, not comments.” :purple_heart: Please tag folks who you think have wisdom to share on this (@ngerakines.me!)


  1. Base frequency: DAILY, WEEKLY, MONTHLY, YEARLY

    • Spacing: INTERVAL=n → every n units (e.g. every 2 weeks)
    • Limiters:
      • COUNT=n → stop after n occurrences
      • UNTIL=DATE → stop at a specific time
    • Day selection: BYDAY=MO,TU,WE,TH,FR,SA,SU depending on frequency (weekly, monthly, yearly)
    • Month / date selection: BYMONTH=1..12, BYMONTHDAY=1..31 or negative (-1 = last day)
    • Ordinal selection (the “nth” logic): BYSETPOS=1,2,…,-1

    The above, expressed as example patterns:

    • Daily / every N days
    • Weekly on specific weekdays
    • Monthly on a date (15th, last day)
    • Monthly on weekday position (1st Friday, last Monday)
    • Yearly on a fixed date
    • Yearly on a weekday pattern (e.g. 2nd Sunday of May)
    • Every four years on the first Tuesday following a Monday in November (Nov. 7, 2028 for those keeping track).

    With exceptions, and multiple rulesets allowed (treated as a union). ↩︎

  2. To prejudice the conversation, imho iCal represents a deeply broken approach to the whole question of calendaring and events on the internet, and I think a clean-room atproto-based approach to events could open up some amazing possibilities, and could hopefully supplant iCal as the de-facto event/calendaring standard on the internet. ↩︎

  3. I’m sharing this to disclose the scope of our implementation, not to have a debate about LLMs. For what it’s worth, I could imagine a sufficiently rich deterministic implementation being possible today, at least for one language. ↩︎

2 Likes

This is great and I’m glad that you’re tackling this.

I think of event records as the result of instruction. Most of the time, the instruction comes from an individual that is using a form to create the event record. I see recurring events as a series of event records produced by a tool given with an instruction record.

So the first thing that comes to mind is creating something like community.lexicon.calendar.template with the recurring data embedded into it.

I think some complexity, like having competing applications vying the opportunity to act on the instruction as a future problem that doesn’t practically need to be solved now.

2 Likes

As for the data structure, I think the iCal spec is pretty solid. The tl;dr is that it defines one or more sets of interval and interval start times in which the event is duplicated contextually. An event template that that has a structure like this sounds reasonable:

{
    "repeat": {
        "frequency": "MONTHLY",
        "byday": "3TH",
        "interval": 1,
        "count": 12,
        "exclude": [
            "2026-07-16T14:00:00",
            "2026-12-17T14:00:00"
        ]
    }
}
1 Like

I like the idea of managing recursion this way, but might recommend community.lexicon.calendar.eventTemplate that way we don’t have any confusion on what the template is for.

I’ll just tell a story from the field.

Luma used to support recurring events. And this was mostly like the Meetup use case.

They ran into complexity with one off editing I guess?

And now only support “duplicating”.

That is - no recurring at all, you need to use the duplicate command and then it’s permanently forked.

I think “the meetup use case” - 2nd Tuesday of every month - is important to support.

As an event organizer - once an event is actually being planned/finalized many things about it get edited a lot — while I might edit the future template only rarely.

Maybe something here around scheduled vs planned status?

Sorry no code! Excited to have this as a feature!

1 Like

I’ve dabbled around with calendars in the past, and you’re right, recurrence is a beast. I don’t have any code to provide you that I’ve worked on, but I am happy to point you to battle-tested code that’s available in open-source libraries and projects. If that’s something you’re interested in seeing, I’ll pull it together for you!

Ultimately this is what I did in Smoke Signal early on. You could duplicate an event and it’d create a draft of the dupe so you could set the date, update description content, etc.

I think one of the reasonable trade-offs is that doing it this way means you don’t need additional ongoing auth.

Two things come to mind:

  1. How important are truly indefinitely recurring events? Is it feasible from a ux perspective to have things recurring within a span, i.e every Tuesday this year?
  2. A useful approach could be a counterfactual approach, where you define the rkey format for recurring events so records referencing them can be published without the base records themselves existing. Then if the creator wants to specificy something extra about a particular event in a series they can publish that record.

For indefinitely recurring events, I think the value is in the system that is using that information to create new events easily. If I say “here’s a weekly meetup that is every Tuesday at 7:00 PM” then the system could have a practical limit of “I’ll create the next 12 for you” with some sort of follow-up action to create a rolling window of them periodically.

I feel strongly against the idea that events should be “virtual”. It completely breaks interactions and strong guarantees like AT-URI and CID pairs as strong references if one app can just “assume” to know what a future event AT-URI and CID “might” be. Its a slippery slope in this decentralized space.

It’s not perfect, but we’re using Event Series in OpenMeet: Example. There is also a “duplicate event” feature for the same reasons @bmann.ca mentioned above.

Event Series in OpenMeet don’t have a lexicon yet; they are external representations defined like this:

  • recurrenceRule: { freq: ‘weekly’, byday: [‘TU’], … } // iCal RRULE as JSON
  • templateEvent: reference to Event (primary event with all details)
  • events: Event[] (list of references to materialized occurrences)
  • timezone: needed to handle event time changes due to Daylight savings time

Instead of materializing the complete series all at once, there is a manual step to create individual occurrences, so that we don’t pre-create a potentially huge number of events. This aligns with @ngerakines.me thoughts, I believe.

Direct responses:

  1. Add my vote to not using virtual events.
  2. Use ICal spec for recurrence rules
  3. I can point you to TS code for doing this, but it’s just glue around ICal

I’m interested in knowing where you land because I’ll also be making the transition from external to an ATproto representation.

2 Likes