---
url: https://findutils.com/blog/rrule-explained-how-recurring-calendar-events-work
title: "RRULE Explained: How Recurring Calendar Events Really Work"
description: "Learn how iCalendar RRULE, EXDATE and RECURRENCE-ID define repeating events, why a monthly event on the 31st skips months, and how to list every date."
category: converters
content_type: blog
cluster: calendar
locale: en
read_time: 9
status: published
author: "olgunozoktas"
published_at: 2026-10-17T15:08:00Z
excerpt: "A plain-language tour of the iCalendar recurrence rule: what FREQ, BYDAY, COUNT and UNTIL mean, how exceptions and moved meetings are stored, the surprises around month ends, time zones and WKST, and how to turn a rule into a list of real dates."
tag_ids: ["rrule", "icalendar", "recurring-events", "calendar", "ics"]
tags: ["RRULE", "iCalendar", "Recurring Events", "Calendar", "ICS"]
primary_keyword: "rrule explained"
secondary_keywords: ["icalendar recurrence rule", "rrule examples", "exdate recurrence-id", "rrule bymonthday -1", "rrule vs cron", "expand recurring events"]
tool_tag: "ics-expand-rrule"
related_tool: "ics-expand-rrule"
related_tools: ["ics-expand-rrule", "ics-to-csv", "ics-to-json", "ics-filter", "ics-timezone-convert", "cron-expression-generator"]
updated_at: "2026-10-17T15:08:00Z"
og_image: "/images/content/blog/rrule-explained-how-recurring-calendar-events-work-cover-20261017.webp"
image_alt: "A single calendar card with a looping arrow fans out into a row of dated cards, one crossed out and one shifted to a new slot."
---

An RRULE is the line in an iCalendar (.ics) event that stores a repeating pattern instead of every date, such as `RRULE:FREQ=WEEKLY;BYDAY=MO,WE` for "every Monday and Wednesday". A calendar app computes the dates from the rule each time it shows the event, so a weekly meeting that ran fifty times is still one event in the file. That is why a spreadsheet, a CRM import, or a simple converter often shows one row where there were fifty meetings.

This post explains how a recurring event is stored, what each part of the rule means, the rules that surprise people, and how to list the real dates. FindUtils tools used here: [ICS Expand RRULE](/convert/ics-expand-rrule/), [ICS to CSV](/convert/ics-to-csv/), [ICS to JSON](/convert/ics-to-json/), and the [Cron Expression Generator](/developers/cron-expression-generator/).

## What makes an event recurring?

A recurring event is one `VEVENT` with a start time and a rule. The recurrence format is defined in [RFC 5545](https://www.rfc-editor.org/rfc/rfc5545), the iCalendar standard that Google Calendar, Apple Calendar, and Outlook exports use. Five properties work together:

| Property | Role | Example |
|----------|------|---------|
| `DTSTART` | The first occurrence; every later one copies its time of day | `DTSTART;TZID=Europe/Istanbul:20261005T093000` |
| `RRULE` | The repeating pattern | `RRULE:FREQ=WEEKLY;BYDAY=MO,WE;COUNT=8` |
| `RDATE` | Extra dates added to the pattern | `RDATE;TZID=Europe/Istanbul:20261024T093000` |
| `EXDATE` | Dates removed from the pattern | `EXDATE;TZID=Europe/Istanbul:20261014T093000` |
| `RECURRENCE-ID` | Marks a separate `VEVENT`, with the same `UID`, that replaces one occurrence | `RECURRENCE-ID;TZID=Europe/Istanbul:20261019T093000` |

RFC 5545 builds the final list, called the recurrence set, in a fixed order: take every start produced by `RRULE` and `RDATE`, then remove every start listed in `EXDATE`. Duplicates count once, and `EXDATE` always wins over the inclusion properties.

## The parts of an RRULE

An RRULE is a list of `NAME=VALUE` parts separated by semicolons. Only `FREQ` is required; every other part narrows or expands the pattern. The parts below cover nearly every rule a calendar app writes.

| Part | Meaning | Notes from RFC 5545 |
|------|---------|---------------------|
| `FREQ` | The base unit: `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY` (also `HOURLY`, `MINUTELY`, `SECONDLY`) | Required, and allowed only once |
| `INTERVAL` | Repeat every n units | Defaults to 1; `INTERVAL=2` with `WEEKLY` means every other week |
| `COUNT` | Stop after n occurrences | `DTSTART` counts as the first one |
| `UNTIL` | Stop at this date or time, inclusive | Never together with `COUNT`; in UTC when `DTSTART` has a time zone |
| `BYDAY` | Days of the week, optionally numbered | `2TU` is the second Tuesday, `-1FR` the last Friday; numbers only with `MONTHLY` or `YEARLY` |
| `BYMONTHDAY` | Days of the month | `-1` is the last day, `-3` the third-to-last |
| `BYMONTH` | Months of the year, 1 to 12 | `BYMONTH=11` keeps November |
| `WKST` | The day a week starts | Defaults to Monday; changes results for weekly rules with `INTERVAL` above 1 |
| `BYSETPOS` | Picks the nth result inside each period | `FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1` is the last weekday of the month |

## RRULE examples with real dates

Reading a rule is easier with dates next to it. Each row below shows a rule, its start date, and the first occurrences it produces under RFC 5545.

| Pattern | Rule | DTSTART | First occurrences |
|---------|------|---------|-------------------|
| Every other week on Tuesday and Thursday | `FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH` | Tue 2026-10-20 | Oct 20, Oct 22, Nov 3, Nov 5, Nov 17, Nov 19 |
| Second Tuesday of every month | `FREQ=MONTHLY;BYDAY=2TU` | 2026-10-13 | Oct 13, Nov 10, Dec 8, 2027-01-12 |
| Last Friday of every month | `FREQ=MONTHLY;BYDAY=-1FR` | 2026-10-30 | Oct 30, Nov 27, Dec 25, 2027-01-29 |
| Last day of every month | `FREQ=MONTHLY;BYMONTHDAY=-1` | 2026-07-31 | Jul 31, Aug 31, Sep 30, Oct 31, Nov 30, Dec 31 |
| Fourth Thursday of November | `FREQ=YEARLY;BYMONTH=11;BYDAY=4TH` | 2026-11-26 | 2026-11-26, 2027-11-25, 2028-11-23 |

To check a rule of your own, put it in an .ics file and drop it into [ICS Expand RRULE](/convert/ics-expand-rrule/), which writes every occurrence between two dates.

## Surprise 1: a monthly event on the 31st skips months

A rule that repeats on the 31st does not move to the 30th in shorter months; it skips them. RFC 5545 says a recurrence that produces an invalid date, such as February 30, must be ignored and not counted.

So `FREQ=MONTHLY` with `DTSTART` on 2026-07-31 produces Jul 31, Aug 31, Oct 31, and Dec 31. September and November have no 31st, so they have no occurrence. The RFC's own example shows the same effect: `FREQ=MONTHLY;BYMONTHDAY=15,30;COUNT=5` from 15 January 2007 gives January 15 and 30, February 15, and March 15 and 30.

The fix is to state what you mean. `BYMONTHDAY=-1` is the last day of every month, whatever its length. The same rule explains why a yearly event that starts on 29 February appears only in leap years.

## Surprise 2: EXDATE does not extend a COUNT

`COUNT` is applied while the rule is evaluated, and `EXDATE` is removed afterwards. A cancelled occurrence still uses up one of the counted slots.

Take a standup with `RRULE:FREQ=WEEKLY;BYDAY=MO,WE;COUNT=8` starting Monday 2026-10-05. The rule produces Oct 5, 7, 12, 14, 19, 21, 26, and 28. Add `EXDATE` for Oct 14 and seven meetings remain; the series still ends on Oct 28 rather than gaining an extra date in November. Use `RDATE` to add a make-up meeting.

## Surprise 3: a moved meeting keeps its original ID

When one occurrence moves, the calendar does not edit the rule. It writes a second `VEVENT` with the same `UID` and a `RECURRENCE-ID` equal to the original start of that occurrence. RFC 5545 is explicit: if a Friday meeting moves to Thursday, the `RECURRENCE-ID` still holds the Friday.

A tool that ignores `RECURRENCE-ID` can list that meeting twice: once at the time the rule gives and once at the new time. [ICS Expand RRULE](/convert/ics-expand-rrule/) replaces the original occurrence with the moved one, so the output lists the meeting once, at its new time.

## Surprise 4: time zones and daylight saving time

A recurring event keeps its wall-clock time, not its UTC time. RFC 5545 recommends a `DTSTART` with a time zone reference (`TZID`) so that every occurrence starts at the same local time.

A weekly 09:00 meeting in `America/New_York` is 13:00 UTC on 26 October 2026 and 14:00 UTC on 2 November 2026, because daylight saving time ends in the United States on 1 November 2026. The local time stays 09:00 throughout. A time without `TZID` and without a trailing `Z` is "floating": it means 09:00 wherever the attendee is.

Two practical rules follow. When `DTSTART` has a `TZID`, the RFC requires `UNTIL` to be written in UTC. And when you need the same meetings in another zone, convert the whole file with [ICS Timezone Convert](/convert/ics-timezone-convert/) rather than editing times by hand.

## Surprise 5: WKST changes results

`WKST` sets the first day of the week, and it matters when a weekly rule has an `INTERVAL` above 1 and several `BYDAY` values. RFC 5545 gives this example, starting Tuesday 5 August 1997:

- `FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO` gives August 5, 10, 19, and 24.
- The same rule with `WKST=SU` gives August 5, 17, 19, and 31.

The difference comes from which Sunday belongs to the "every other" week. If your calendar shows a fortnightly event on unexpected Sundays, check `WKST` first.

## RRULE vs cron: which one describes your schedule?

Both describe repeating times, but they solve different problems. Cron schedules jobs on one machine; RRULE describes events people attend, with a start, an end, and exceptions.

| Aspect | iCalendar RRULE | Cron expression |
|--------|-----------------|-----------------|
| Defined in | RFC 5545 | POSIX `crontab` and its variants |
| Start and end | `DTSTART`, plus `COUNT` or `UNTIL` | None; runs until removed |
| "Last Friday of the month" | `BYDAY=-1FR` | Not in POSIX cron; some implementations add their own syntax |
| Every other week | `INTERVAL=2` | No direct field |
| Time zone | Per event, with `TZID` | Not part of the POSIX format; each scheduler has its own setting |
| Exceptions | `EXDATE`, `RDATE`, `RECURRENCE-ID` | None |
| Day-of-month and day-of-week together | Both must match | Either may match (POSIX) |

The last row trips people up. The [POSIX crontab specification](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html) says that when both the day-of-month and day-of-week fields are restricted, a day matching either one runs the job. In an RRULE, `FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13` means only Fridays that fall on the 13th. Build and check cron schedules in the [Cron Expression Generator](/developers/cron-expression-generator/).

## How to see every occurrence of a recurring event

Expanding a rule turns one recurring event into one event per date. On the [ICS Expand RRULE](/convert/ics-expand-rrule/) page, the calendar is read in your browser and nothing is uploaded.

### Step 1: Export the calendar

Export the calendar as an .ics file. In Google Calendar, use Settings, then Import & export, then Export; in Apple Calendar, File, then Export; in Outlook, File, then Save Calendar.

### Step 2: Drop the file and set the window

Drop the .ics file on the page and set From and To. A rule without `COUNT` or `UNTIL` repeats forever, so the window decides which occurrences are written. Each series stops at a cap of 366 occurrences by default, adjustable up to 2000.

### Step 3: Read the notes

The notes name every series that was cut at the cap, every monthly series that skips short months, and every rule part the page does not expand. It expands `DAILY`, `WEEKLY`, `MONTHLY`, and `YEARLY` rules with `INTERVAL`, `COUNT`, `UNTIL`, `BYDAY` (including ordinals), `BYMONTHDAY`, `BYMONTH`, and `WKST`, plus `EXDATE`, `RDATE`, and `RECURRENCE-ID`. A rule that uses `BYSETPOS`, `BYYEARDAY`, `BYWEEKNO`, `BYHOUR`, `BYMINUTE`, `BYSECOND`, or an hourly or finer frequency keeps its first occurrence only.

### Step 4: Download the format you need

Choose .ics to import into another calendar, JSON for a script, or CSV for a spreadsheet. Each occurrence gets its own `UID`, so import the expanded .ics into a new, separate calendar; importing it next to the original series creates a second copy of every meeting.

If you only need the rule as text, [ICS to CSV](/convert/ics-to-csv/) and [ICS to JSON](/convert/ics-to-json/) keep one row or object per event with the `RRULE` in its own field. To keep only some events before expanding, use [ICS Filter](/convert/ics-filter/).

## FAQ

### What does RRULE stand for?

RRULE is the "recurrence rule" property of the iCalendar format, defined in RFC 5545. Its value lists the frequency and the parts that pick dates, such as `FREQ=MONTHLY;BYDAY=2TU`.

### Does DTSTART count as an occurrence?

Yes. RFC 5545 says the `DTSTART` value always counts as the first occurrence, including toward `COUNT`. A rule with `COUNT=10` produces ten dates in total, the start included.

### How do I make an event repeat on the last day of every month?

Use `FREQ=MONTHLY;BYMONTHDAY=-1`. A rule that repeats on the 31st skips months with fewer days, because RFC 5545 ignores invalid dates instead of moving them.

### What is the difference between EXDATE and RECURRENCE-ID?

`EXDATE` removes an occurrence from the series. `RECURRENCE-ID` belongs to a separate event with the same `UID` and replaces one occurrence with new details, such as a different time or room.

### Can I use COUNT and UNTIL together?

No. RFC 5545 allows either `COUNT` or `UNTIL` in one rule, not both. Without either, the rule repeats forever.

### Is ICS Expand RRULE free, and is my calendar uploaded?

It is free and needs no signup. On the tool page the .ics file is read and expanded in your browser, and the calendar text is not uploaded.

## Tools used in this post

- **[ICS Expand RRULE](/convert/ics-expand-rrule/)** — turn recurring events into one event per occurrence between two dates
- **[ICS to CSV](/convert/ics-to-csv/)** — one spreadsheet row per event, with the rule kept as text
- **[ICS to JSON](/convert/ics-to-json/)** — one JSON object per event for scripts
- **[ICS Filter](/convert/ics-filter/)** — keep only the events you need by date, keyword, category, or status
- **[ICS Timezone Convert](/convert/ics-timezone-convert/)** — move a calendar's events to another time zone
- **[Cron Expression Generator](/developers/cron-expression-generator/)** — build cron schedules and see the next run times
- **[ICS to CSV guide](/guides/ics-to-csv/)** — the spreadsheet columns explained
- **[Cron Expression Generator guide](/guides/cron-expression-generator-guide/)** — how five-field cron schedules work
- **[CSV to ICS guide](/guides/csv-to-ics/)** — turn a spreadsheet of events into a calendar file

## Sources

- [RFC 5545: Internet Calendaring and Scheduling Core Object Specification (iCalendar)](https://www.rfc-editor.org/rfc/rfc5545), sections 3.3.10 (Recurrence Rule), 3.8.4.4 (Recurrence ID), 3.8.5.1 (Exception Date-Times), 3.8.5.2 (Recurrence Date-Times), and 3.8.5.3 (RRULE)
- [The Open Group Base Specifications: crontab](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html)
