Quando lavori con calendari, turni, reminder, eventi ricorrenti o qualsiasi cosa che “si ripete nel tempo”, prima o poi ti scontri con questa domanda:
“Come descrivo una ricorrenza senza reinventare mezzo calendario di Google?”
La risposta esiste da anni, funziona benissimo e non è stata sostituita da nulla: RFC 5545, lo standard che definisce l’iCalendar.
È qui che nasce la famigerata RRULE, la sintassi capace di descrivere qualunque ripetizione immaginabile.
Lo standard iCalendar è tuttora la base dei principali servizi di calendario (Google, Apple, Outlook) ed è quello a cui si rifanno le librerie moderne di scheduling.
In questo articolo vediamo cos’è RFC 5545, come funziona RRULE e come usarla in un software moderno senza farsi male.
Che cos’è RFC 5545 (iCalendar)
RFC 5545 definisce il formato dati iCalendar, cioè il modo standard con cui si rappresentano e scambiano eventi, attività, timezone, disponibilità e relative ricorrenze (file .ics).
Dentro trovi:
- Eventi e to-do
- Gestione dei fusi orari
- Eccezioni e modifiche a singole occorrenze
- Regole di ricorrenza (RRULE, RDATE, EXDATE)
Insomma: se il tuo prodotto gestisce cose che si ripetono nel tempo, questo è il terreno di gioco.
Cos’è la RRULE
La proprietà RRULE definisce il pattern con cui un evento si ripete. La RFC la descrive come una “recurrence rule specification” composta da varie parti combinabili tra loro.
Le parti più comuni sono:
- FREQ — frequenza principale (DAILY, WEEKLY, MONTHLY, YEARLY…)
- INTERVAL — ogni quanto ripetere la frequenza
- BYDAY, BYMONTHDAY, BYMONTH, BYSETPOS — filtri/selector per casi più complessi
- UNTIL o COUNT — quando termina la ricorrenza
Esempi base:
FREQ=DAILY
FREQ=WEEKLY;BYDAY=MO,WE,FR
FREQ=MONTHLY;BYDAY=TH;BYSETPOS=3
L’ultimo esempio significa: “terzo giovedì di ogni mese”. Pattern come questo sono difficili con altri sistemi tipo CRON,
ma RRULE li esprime in modo naturale.
RRULE + eccezioni: il modello completo
Una ricorrenza non vive solo di RRULE. Il modello iCalendar prevede un recurrence set:
- RRULE — la regola principale
- RDATE — date aggiuntive incluse manualmente
- EXDATE — date da saltare (cancellazioni)
- RECURRENCE-ID — modifiche puntuali a una singola istanza
Questa combinazione permette di gestire eccezioni reali senza rompere la serie.
Perché è uno standard che non muore
Descrivere ricorrenze è oggettivamente difficile:
- mesi con durate diverse
- settimane ISO e inizio settimana variabile
- fusi orari e cambio ora legale (DST)
- pattern astratti (es. “ultimo giorno lavorativo del mese”)
- modifiche a singole istanze
- cancellazioni selettive
RFC 5545 copre tutti questi casi senza perdere compatibilità con l’ecosistema globale.
Non è uno standard “nuovo” esteticamente, ma è stabile, universale e battle-tested da miliardi di eventi di calendario.
RRULE contro occorrenze materializzate: come ragiona un software moderno?
Se stai implementando ricorrenze in un sistema, hai due strategie “pure”, nessuna perfetta da sola.
1) Calcolo a runtime della RRULE
Salvi una serie “master” con RRULE e generi le occorrenze “al volo” quando devi visualizzare o validare un intervallo.
- Pro: DB pulito, ricorrenze infinite gestibili
- Contro: espansione costosa se fai query complesse su lunghi periodi
2) Materializzazione delle occorrenze
Crei una tabella occurrences con tutte le singole istanze già espanse.
- Pro: query semplici e velocissime, ottimo per report e analytics
- Contro: serie molto lunghe o infinite diventano ingestibili; aggiornare la serie richiede rigenerazione
La realtà: quasi tutti usano un ibrido
La soluzione pratica più diffusa è:
- Salvare l’evento master con RRULE
- Salvare eccezioni (EXDATE / override)
- Materializzare solo una finestra temporale (es. prossimi 60–90 giorni), rigenerandola quando serve
Così ottieni performance da materializzazione senza creare un debito infinito di occorrenze.
Esempi reali di RRULE
Ogni lunedì, mercoledì e venerdì
FREQ=WEEKLY;BYDAY=MO,WE,FR
Il primo giorno di ogni mese
FREQ=MONTHLY;BYMONTHDAY=1
Ogni Natale
FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25
Il terzo giovedì del mese
FREQ=MONTHLY;BYDAY=TH;BYSETPOS=3
Nota: costrutti avanzati come BYSETPOS operano su un insieme di istanze generate nell’intervallo della regola.
Per questo hanno limiti precisi definiti dalla RFC.
Librerie consigliate (non fare parsing a mano)
Implementare una RRULE da zero è quasi sempre un errore: gli edge case sono tanti, sottili e difficili da prevedere.
Pattern complessi, fusi orari, BYSETPOS, settimane ISO e modifiche puntuali alle istanze rendono la gestione artigianale fragile e costosa da manutenere.
Un principio che ripeto sempre è semplice: se pensi a qualcosa, qualcuno l’ha già fatta meglio di te.
Se vuoi sperimentare, studiare o metterti alla prova: fallo pure.
Ma per un ambiente stabile, di produzione, con utenti reali, affidarsi a librerie mature è la scelta più sicura.
Queste librerie seguono lo standard RFC 5545, hanno anni di utilizzo, una community attiva e implementano già tutte le logiche più complesse. Inoltre, molte integrano sistemi di caching e ottimizzazioni interne che rendono l’espansione delle ricorrenze significativamente più veloce rispetto a soluzioni casalinghe.
-
JavaScript — rrule / rrule.js
GitHub: https://github.com/jakubroztocil/rrule -
Python — dateutil.rrule
GitHub: https://github.com/dateutil/dateutil -
PHP — php-rrule
GitHub: https://github.com/rlanvin/php-rrule -
Java — ical4j
GitHub: https://github.com/ical4j/ical4j -
Go — rrule-go
GitHub: https://github.com/teambition/rrule-go
Conclusione
RFC 5545 e RRULE restano lo standard di riferimento per eventi ricorrenti:
sono compatibili ovunque, gestiscono casi complessi e ti evitano di reinventare logiche fragili.
Per un software moderno la strategia migliore è spesso ibrida: RRULE come sorgente di verità, eccezioni persistite e occorrenze generate a finestra quando servono.
Se nel tuo prodotto esiste il concetto di “evento che si ripete”, imparare RRULE è uno dei migliori investimenti tecnici che puoi fare.