Recurrencia
trud-calendar soporta eventos recurrentes mediante la propiedad recurrence en CalendarEvent. El motor de recurrencia sigue la semantica de RFC 5545 RRULE.
Uso basico
Sección titulada «Uso basico»Agrega una propiedad recurrence a cualquier evento:
const events = [ { id: "standup", title: "Standup Diario", start: "2026-03-13T09:00:00", end: "2026-03-13T09:30:00", recurrence: { freq: "daily" }, }, { id: "review", title: "Review de Sprint", start: "2026-03-13T14:00:00", end: "2026-03-13T15:00:00", recurrence: { freq: "weekly", byDay: ["FR"], count: 12, }, },];Expandir eventos recurrentes
Sección titulada «Expandir eventos recurrentes»El calendario no expande automaticamente los eventos recurrentes en instancias. Debes llamar a expandRecurringEvents() de trud-calendar-core antes de pasar los eventos al componente <Calendar>:
import { Calendar } from "trud-calendar";import { expandRecurringEvents } from "trud-calendar-core";
function App() { const expanded = expandRecurringEvents(events, rangeStart, rangeEnd);
return <Calendar events={expanded} />;}expandRecurringEvents devuelve:
- Todos los eventos no recurrentes sin cambios
- Instancias generadas para cada evento recurrente que cae dentro del rango de fechas
Cada instancia generada tiene:
recurringEventId— eliddel evento padreoriginalDate— la fecha para la que se genero esta instancia (ej:"2026-03-17")- Un
idsintetico en el formatoparentId::YYYY-MM-DD
RecurrenceRule
Sección titulada «RecurrenceRule»interface RecurrenceRule { freq: "daily" | "weekly" | "monthly" | "yearly"; interval?: number; // Cada N periodos (default: 1) byDay?: RecurrenceDay[]; // "MO", "TU", "WE", "TH", "FR", "SA", "SU" byMonthDay?: number[]; // Dia del mes: [1, 15] bySetPos?: number[]; // Posicion en el conjunto: [1] = primero, [-1] = ultimo count?: number; // Parar despues de N ocurrencias until?: string; // Parar despues de esta fecha (YYYY-MM-DD)}
type RecurrenceDay = "MO" | "TU" | "WE" | "TH" | "FR" | "SA" | "SU";Patrones comunes
Sección titulada «Patrones comunes»Todos los dias laborables (Lun–Vie)
Sección titulada «Todos los dias laborables (Lun–Vie)»recurrence: { freq: "weekly", byDay: ["MO", "TU", "WE", "TH", "FR"],}Cada 2 semanas los martes y jueves
Sección titulada «Cada 2 semanas los martes y jueves»recurrence: { freq: "weekly", interval: 2, byDay: ["TU", "TH"],}Primer lunes de cada mes
Sección titulada «Primer lunes de cada mes»recurrence: { freq: "monthly", byDay: ["MO"], bySetPos: [1],}Cada ano el 15 de marzo
Sección titulada «Cada ano el 15 de marzo»recurrence: { freq: "yearly",}// La fecha viene de la propiedad `start` del eventoParar despues de 10 ocurrencias
Sección titulada «Parar despues de 10 ocurrencias»recurrence: { freq: "weekly", count: 10,}Parar despues de una fecha especifica
Sección titulada «Parar despues de una fecha especifica»recurrence: { freq: "daily", until: "2026-06-30",}Eventos recurrentes con zonas horarias
Sección titulada «Eventos recurrentes con zonas horarias»Establece timeZone en el evento padre para anclar una serie recurrente a una zona IANA especifica. Cada instancia expandida hereda la zona, y la hora wall-clock se preserva a traves de las transiciones de DST:
{ id: "ny-standup", title: "Standup diario 9 AM NY", start: "2026-03-07T09:00:00", end: "2026-03-07T09:30:00", timeZone: "America/New_York", recurrence: { freq: "daily" },}Este evento se mantiene a las “9:00 AM” en America/New_York todos los dias, incluyendo antes y despues del salto de spring-forward el 2026-03-08 — exactamente como especifican RFC 5545 (TZID) y Google Calendar. Consulta Zonas horarias para detalles.
Excluir fechas (exDates)
Sección titulada «Excluir fechas (exDates)»Usa exDates para omitir fechas especificas de una serie recurrente:
const event = { id: "standup", title: "Standup Diario", start: "2026-03-13T09:00:00", end: "2026-03-13T09:30:00", recurrence: { freq: "daily" }, exDates: ["2026-03-17", "2026-03-24"], // Omitir estas fechas};Editar una sola ocurrencia
Sección titulada «Editar una sola ocurrencia»Para editar solo una instancia de una serie recurrente:
- Agrega el
originalDatede la instancia al arrayexDatesdel evento padre - Crea un nuevo evento independiente (sin
recurrence) para esa fecha con las ediciones
// Antes: el evento padre se repite diariamente// El usuario edita la ocurrencia del 17 de marzo para cambiar el titulo
// 1. Agregar exDate al padreparentEvent.exDates = [...(parentEvent.exDates ?? []), "2026-03-17"];
// 2. Crear excepcion independienteconst exception = { id: "standup::exception::2026-03-17", title: "Standup Modificado", // titulo editado start: "2026-03-17T09:00:00", end: "2026-03-17T09:30:00", // Sin recurrence — es un evento independiente};Editar toda la serie
Sección titulada «Editar toda la serie»Para editar todas las ocurrencias, actualiza el evento padre directamente:
parentEvent.title = "Nuevo Titulo";parentEvent.recurrence = { freq: "weekly", byDay: ["MO", "WE"] };Como las instancias se generan dinamicamente por expandRecurringEvents(), actualizar el padre automaticamente cambia todas las expansiones futuras.
Integracion con backend
Sección titulada «Integracion con backend»La libreria identifica series recurrentes por la presencia de la propiedad recurrence en un evento. Cuando los datos vienen de tu backend:
- Almacena el evento padre con
recurrence,exDates, etc. - Llama a
expandRecurringEvents(events, rangeStart, rangeEnd)para generar instancias - Cuando el usuario edita una instancia, verifica
recurringEventIdpara saber si pertenece a una serie - Usa
originalDatepara saber que ocurrencia especifica se edito
// Obtener de la APIconst events = await api.getEvents();
// Expandir antes de renderizarconst expanded = expandRecurringEvents(events, viewStart, viewEnd);
// Renderizar<Calendar events={expanded} onEventClick={(event) => { if (event.recurringEventId) { // Es una instancia recurrente — preguntar: "editar este o todos?" showScopeDialog(event); } else { // Evento regular openEditModal(event); }}} />