# Recurrence > Build recurring events in dash-mui-scheduler with EventCalendarPremium using RRULE strings, RRULE objects and exception dates. --- .. llms_copy::Recurrence .. toc:: ### Recurrence (Premium) Recurring events are a **Premium** feature. Use `dms.EventCalendarPremium` instead of `dms.EventCalendar` — it is identical to the Community calendar but adds a recurrence engine, a Recurrence tab in the edit dialog, and two new per-event keys: `rrule` and `exDates`. .. admonition::Premium :color: yellow `EventCalendarPremium` requires a MUI X Premium license key. Pass it via `licenseKey=os.environ.get("MUI_X_LICENSE_KEY", "")`. Without a valid key the calendar still renders and is fully interactive, but a MUI watermark is shown over it. That is expected — supply a real key to remove it. The data boundary is unchanged from the Community calendar. `events` is a list of plain dicts and is **both input and output**: the component writes the full array back on every create, move, resize or delete. Dates are ISO **strings** (for example `"2024-01-15T10:00:00"` for wall time, or a trailing `Z` for UTC), never Python `datetime` objects. Recurring series are stored as a *single* event dict carrying an `rrule`; the calendar expands it into occurrences for display only. ### Recurrence as an RRULE string The simplest form sets `event["rrule"]` to an RFC-5545 RRULE string. The event below repeats every week on Monday, Wednesday and Friday: ```text FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR ``` `FREQ` is one of `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. `BYDAY` codes are `MO TU WE TH FR SA SU` (for monthly rules you may prefix an ordinal, e.g. `2TU` = second Tuesday, `-1FR` = last Friday). You can also add `COUNT`, `UNTIL` (an ISO string), `BYMONTHDAY` (1–31) and `BYMONTH` (1–12). .. exec::docs.recurrence.recurrence_string ```python # File: docs/recurrence/recurrence_string.py import os from dash import Input, Output, callback import dash_mantine_components as dmc import dash_mui_scheduler as dms # Premium events may carry an `rrule`. The RRULE string form follows RFC-5545: # this event recurs every week on Monday, Wednesday and Friday at 10:00. events = [ { "id": "standup", "title": "Team Standup", "start": "2024-01-15T10:00:00", "end": "2024-01-15T10:30:00", "color": "blue", "rrule": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR", }, { "id": "review", "title": "Sprint Review", "start": "2024-01-19T15:00:00", "end": "2024-01-19T16:00:00", "color": "purple", }, ] component = dmc.Stack( [ dms.EventCalendarPremium( id="recurrence-string-cal", licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""), events=events, defaultView="week", defaultVisibleDate="2024-01-15", height=600, ), dmc.Text("Last action:", fw=600, size="sm"), dmc.Code(id="recurrence-string-action", block=True), ], gap="sm", ) @callback( Output("recurrence-string-action", "children"), Input("recurrence-string-cal", "lastAction"), ) def show_action(last_action): if not last_action: return "No action yet — drag, create, resize or delete an occurrence." return f"{last_action.get('type')} @ {last_action.get('event_timestamp')}" ``` ### Recurrence as an object + exception dates Instead of a string, `rrule` may be an object. This is convenient when you are building the rule programmatically: ```python {"freq": "WEEKLY", "interval": 1, "byDay": ["MO", "WE", "FR"], "count": 10} ``` The keys mirror the RRULE parts: `freq`, `interval`, `byDay`, `byMonthDay`, `byMonth`, `count` and `until`. To remove individual occurrences from a series without breaking the rule, add `exDates` — a list of ISO strings naming the start times to skip: ```python "exDates": ["2024-01-17T08:00:00", "2024-01-19T08:00:00"] ``` The example below recurs every weekday for ten occurrences, with two dates excluded. Note that even though the calendar shows many occurrences, the `events` output still contains a single definition dict. .. exec::docs.recurrence.recurrence_object ```python # File: docs/recurrence/recurrence_object.py import os from dash import Input, Output, callback import dash_mantine_components as dmc import dash_mui_scheduler as dms # `rrule` may also be an object instead of a string. Here the event recurs # every weekday morning for a total of 10 occurrences, but two specific # dates are removed from the series via `exDates` (ISO strings). events = [ { "id": "yoga", "title": "Morning Yoga", "start": "2024-01-15T08:00:00", "end": "2024-01-15T08:45:00", "color": "green", "rrule": { "freq": "WEEKLY", "interval": 1, "byDay": ["MO", "TU", "WE", "TH", "FR"], "count": 10, }, "exDates": [ "2024-01-17T08:00:00", "2024-01-19T08:00:00", ], }, ] component = dmc.Stack( [ dms.EventCalendarPremium( id="recurrence-object-cal", licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""), events=events, defaultView="week", defaultVisibleDate="2024-01-15", height=600, ), dmc.Text("Occurrence count in events output:", fw=600, size="sm"), dmc.Code(id="recurrence-object-count", block=True), ], gap="sm", ) @callback( Output("recurrence-object-count", "children"), Input("recurrence-object-cal", "events"), ) def show_count(events_out): # The component writes the full event array back on every edit. The # recurring definition stays a single dict with `rrule`/`exDates`; # the UI expands it into occurrences for display only. return f"{len(events_out or [])} stored event definition(s)" ``` ### Reading edits back As with the Community calendar, you do not need a callback for the calendar to be interactive — drags, creates and deletes round-trip through Dash on their own. Add a callback only to *display* outputs. The `lastAction` output is `{type, event, event_timestamp}`, where `type` is one of `create`, `update`, `delete`, `move`, `resize` or `change`. The first example above wires `lastAction` into a code block so you can watch edits as they happen. ### EventCalendarPremium props `EventCalendarPremium` accepts every `EventCalendar` prop plus `licenseKey`, and its `events` may include `rrule` and `exDates`. .. kwargs::dash_mui_scheduler.EventCalendarPremium --- *Source: /recurrence*