.rcal
Format Specification
.rcal is an open file format for encoding recurring real-world events. It is designed to be simple to publish, simple to parse, and expressive enough to describe how venues, people, and communities actually operate — including weekly rhythms, monthly patterns, seasonal windows, exceptions, and one-off events.
The format is maintained by EventStrand but is not owned by it. Anyone can read, write, parse, or build tools around .rcal files without permission, license, or fee.
What .rcal is not
.rcal is not a replacement for iCal/iCalendar (.ics). iCal is designed for scheduling commitments — things you are doing. .rcal is designed for encoding possibility — things you could be doing, structured around the recurring rhythms of real-world life.
.rcal is not a calendar sync protocol. It does not implement CALDAV and does not attempt to interoperate with existing calendar infrastructure. It is a simpler, purpose-built format for a different use case.
File Format #
.rcal files are valid JSON documents with an .rcal file extension. They must be UTF-8 encoded. No other encoding is permitted.
MIME type
The registered MIME type for .rcal files is application/vnd.rcal+json. When serving .rcal files over HTTP, use this content type. Parsers should also accept application/json as a fallback.
File extension
Files must use the .rcal extension. This extension is used by applications to identify the format and associate the correct parser.
Hosted strands
A strand may be hosted at a permanent URL. Hosted strand URLs must return the .rcal file with an appropriate content type. EventStrand hosts published strands at eventstrand.com/s/{handle}/{strand-id} but any URL is valid.
.rcal file with meta.live: true is telling subscriber apps that it is permanently hosted at a specific URL — and they should check back for updates. Anyone who has scanned your QR code or installed your strand is silently pointing to that exact address. Move it or change it, and they lose the connection with no way to recover it.
Top-Level Structure #
Every .rcal file represents one strand — a named, themed collection of events from a single publisher. The file has two required top-level keys: rcal (the spec version) and events (an array of event objects). The meta object is optional but strongly recommended.
{
"rcal": "0.1",
"meta": { },
"events": [ ]
}
| Field | Type | Required | Description |
|---|---|---|---|
| rcal | string | Yes | Spec version. Must be a valid semver string. Current value: "0.1". Parsers should warn on unknown versions rather than fail. |
| meta | object | No | Publisher metadata. Contains title, type, location, timezone, and display preferences. See meta object. |
| events | array | Yes | Array of one or more event objects. A file with an empty array is valid but has no content. See events. |
meta object #
The meta object describes the publisher — the venue, person, or organization that created the strand file. Fields here apply to the strand as a whole and can be inherited by individual events.
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | No | Display name of the publisher. Shown as the strand header in apps. E.g. "The Rusty Nail". |
| type | string | No | Publisher category. One of: venue, artist, community, personal, retail, event. Used for display and filtering. Defaults to venue if omitted. |
| description | string | No | Short description of the publisher. One to two sentences. Plain text only. |
| location | object | No | Publisher's physical location. Inherited by all events unless overridden at the event level. See location object. |
| timezone | string | No | IANA timezone identifier. E.g. "America/Chicago", "Europe/London". All times in the file are interpreted in this timezone unless a strand overrides it. Strongly recommended. |
| live | boolean | No | If true, subscriber apps should periodically check the source URL for updates. Requires the file to be hosted at a stable URL. Defaults to false. |
| source_url | string | No | The canonical hosted URL for this file. Required when live is true. Used by subscriber apps to poll for updates. |
| color | string | No | Hex color code used as the accent color in apps. E.g. "#C0392B". Should be a saturated color with sufficient contrast against dark backgrounds. |
| website | string | No | URL of the publisher's website. Must include protocol. E.g. "https://therustynaill.com". |
| published | string | No | ISO 8601 date the file was first published. E.g. "2026-03-17". Informational only. |
| updated | string | No | ISO 8601 datetime of the last modification. E.g. "2026-03-17T14:22:00Z". Used by subscriber apps to detect changes without re-parsing the full file. |
location object #
The location object appears in both meta and individual strand objects. When present on a strand, it overrides the meta-level location for that strand only.
| Field | Type | Required | Description |
|---|---|---|---|
| address | string | No | Street address. E.g. "1234 N Milwaukee Ave". |
| city | string | No | City name. |
| state | string | No | State or province abbreviation. E.g. "IL", "ON". |
| country | string | No | ISO 3166-1 alpha-2 country code. E.g. "US", "GB", "DE". |
| lat | number | No | Latitude in decimal degrees. E.g. 41.9214. Used for proximity filtering in apps. |
| lng | number | No | Longitude in decimal degrees. E.g. -87.7078. Used for proximity filtering in apps. |
| name | string | No | Human-readable name for the location when different from the publisher name. Useful for events that occur at a secondary location. E.g. "Wicker Park Field House". |
| notes | string | No | Free-text location notes. E.g. "Enter through the alley door on Fridays". |
location field inherits meta.location. A strand with a location field replaces the meta location entirely — there is no partial merge. If a strand happens at a different venue but you still want to display the publisher's address, repeat it on the strand.
events[] array #
The events array contains one or more event objects. Each event represents a single recurring event, one-off, or date-listed series published under this strand. A strand file (.rcal) is the strand — a thematic, named presence belonging to one publisher. Events are the things that happen within it.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique identifier for the strand within this file. Lowercase, hyphen-separated. E.g. "friday-jazz". Used by subscriber apps to track updates across file versions. Must be stable — do not change once published. |
| name | string | Yes | Display name of the event. E.g. "Friday Jazz Night". |
| description | string | No | Short description of the event. Plain text. One to three sentences. |
| location | object | No | Location override for this strand. Replaces meta.location entirely. See location object. |
| timezone | string | No | Timezone override for this strand. Inherits meta.timezone if omitted. |
| category | string | No | Event category. See categories reference. |
| vibe_tags | array | No | Array of vibe tag strings. Used for mood-based filtering. See vibe tags reference. Max 5 tags per strand recommended. |
| price | string | No | Price indicator. See price values. Defaults to "unknown" if omitted. |
| price_note | string | No | Human-readable price note. E.g. "$15 at the door, $10 in advance". |
| ticket_url | string | No | URL to purchase tickets or RSVP. Must include protocol. |
| recurrence | array | No | Array of recurrence rule objects. Multiple rules are merged — the strand occurs on all dates generated by any rule. Use for events with a predictable repeating pattern. See recurrence rules. |
| dates | array | No | Explicit list of dates, maintained by the publisher over time. Use for events with no predictable pattern — a pop-up, a DJ who books when they book, a market with irregular scheduling. Each entry is either an ISO 8601 date string ("2026-05-03") or an object with date, time_start, and/or time_end to override times for that occurrence. Plain strings inherit the strand-level time_start and time_end. Publisher updates this list as new dates are confirmed. See dates array. |
| date | string | No | ISO 8601 date for one-off events. E.g. "2026-06-14". Used instead of recurrence for singular events. See one-off events. |
| time_start | string | No | Start time for one-off events in 24h format. E.g. "19:30". For recurring events, time is set per recurrence rule. |
| time_end | string | No | End time for one-off events in 24h format. May cross midnight — "02:00" on a Saturday night event means 2am Sunday. |
| exceptions | array | No | Array of exception objects describing dates the recurring pattern is skipped, cancelled, or modified. See exceptions. |
| lead_time_days | number | No | How many days in advance the app should surface this event. 0 means day-of only. 7 means it appears in the portal up to a week ahead. Defaults to 0. |
| notes | string | No | Free-text notes for attendees. E.g. "No reservations needed. Bar opens at 7pm, music starts at 9pm." |
recurrence[] rules #
Each strand may have one or more recurrence rules. Rules are additive — the strand occurs on all dates produced by any rule. This allows complex patterns like "every weekday plus extended Saturday hours" to be expressed as two simple rules rather than one complicated one.
| Field | Type | Required | Description |
|---|---|---|---|
| pattern | string | Yes | The recurrence pattern type. See recurrence patterns for valid values. |
| every | number | No | Interval multiplier. 1 means every occurrence, 2 means every other, etc. Defaults to 1. Used with weekly and daily patterns. |
| days | array | No | Array of day strings. E.g. ["friday", "saturday"]. Accepts full day names or shorthand values. Required for weekly pattern. |
| month_week | string | No | Which week of the month. One of: first, second, third, fourth, last. Required for monthly_week pattern. |
| month_date | number | No | Day of month as integer 1–31. Required for monthly_date pattern. If the date doesn't exist in a given month (e.g. 31 in April), the occurrence is skipped. |
| time_start | string | No | Start time in 24h format. E.g. "21:00". Interpreted in the strand's timezone. |
| time_end | string | No | End time in 24h format. May be earlier than time_start to indicate the event runs past midnight. |
| season_start | string | No | Month this rule becomes active. Three-letter abbreviation: "jan" through "dec". If omitted, the rule is active year-round. |
| season_end | string | No | Month this rule stops being active (inclusive). Same format as season_start. |
| season_start_day | number | No | Day of month (1–31) within season_start that the rule becomes active. Pairs with season_start to give day-level precision. E.g. season_start: "jun", season_start_day: 21 for the summer solstice. If omitted, the rule activates on the first day of season_start. |
| season_end_day | number | No | Day of month (1–31) within season_end that the rule stops being active (inclusive). If omitted, the rule is active through the last day of season_end. |
| fuzzy_start | boolean | No | If true, the start of this rule's active window is approximate — the exact day may shift year to year (e.g. astronomical solstices and equinoxes). Apps should render a ~ indicator at the start edge rather than a hard boundary. Defaults to false. |
| fuzzy_end | boolean | No | If true, the end of this rule's active window is approximate. Same rendering guidance as fuzzy_start. Defaults to false. |
| start_date | string | No | ISO 8601 date after which this rule becomes active. Useful for rules that begin in the future. |
| end_date | string | No | ISO 8601 date after which this rule stops generating occurrences. Useful for limited-run events. |
exceptions[] array #
Exceptions modify or suppress specific occurrences of a recurring strand. They do not affect the underlying recurrence rules — they are applied as a post-processing layer on the generated occurrence list.
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | Exception type. One of: skip, cancelled_range, modified. See below. |
| date | string | Yes | ISO 8601 date of the exception. For cancelled_range, this is the start date of the range. |
| date_end | string | No | End date for cancelled_range type. Inclusive. All occurrences between date and date_end are suppressed. |
| note | string | No | Human-readable explanation. E.g. "Closed for Thanksgiving", "Private event". Displayed to users in supporting apps. |
| time_start | string | No | Modified start time for modified type. Overrides the recurrence rule's time for this specific occurrence. |
| time_end | string | No | Modified end time for modified type. |
| location | object | No | Modified location for modified type. Overrides the strand's location for this specific occurrence only. |
Exception types
skip
Suppresses a single occurrence. Use when a regularly-scheduled event simply doesn't happen on a specific date.
cancelled_range
Suppresses all occurrences between date and date_end inclusive. Use for closures, holidays, or temporary suspension.
modified
Keeps the occurrence but changes one or more of its properties — time, location, or adds a note. The occurrence still appears in the portal; only specified fields are overridden.
dates[] array #
The dates field is used instead of recurrence or date for events that have a persistent strand identity but no predictable schedule. The publisher maintains this list over time, adding new dates as they are confirmed and removing past ones on each update.
This is the right choice for: pop-up events, artists who announce dates as they book them, markets with irregular timing, or any recurring presence that doesn't fit a weekly or monthly pattern.
recurrence? Recurrence rules are computed — you define the pattern and parsers generate the dates. dates is curated — the publisher decides exactly which dates exist. Both produce a list of occurrences, but the authorship and update model are different. Live strands with dates are how publishers push specific confirmed bookings to all their subscribers at once.
Entry formats
Each entry in the dates array is either a plain ISO 8601 date string or an object. Plain strings inherit the strand-level time_start and time_end. Objects can override any time field for that specific occurrence.
{
"id": "irregular-nights",
"name": "Thursday Residency",
"category": "music",
"vibe_tags": ["social", "curious"],
"time_start": "21:00",
"time_end": "23:30",
"dates": [
"2026-04-09",
"2026-04-23",
{
"date": "2026-05-14",
"time_start": "22:00",
"time_end": "02:00",
"note": "Extended set with guest DJ"
},
"2026-06-04",
"2026-06-18"
]
}
Date entry object fields
| Field | Type | Required | Description |
|---|---|---|---|
| date | string | Yes | ISO 8601 date. E.g. "2026-05-14". |
| time_start | string | No | Start time override in 24h format. Overrides the strand-level time_start for this occurrence only. |
| time_end | string | No | End time override in 24h format. |
| note | string | No | Per-occurrence note. E.g. "Guest DJ", "Moved to upstairs room". |
| location | object | No | Per-occurrence location override. Replaces the strand-level location for this date only. |
Validation rules for dates
date (singular one-off) is mutually exclusive with recurrence and dates. recurrence and dates may coexist on the same event — a single opening date plus an ongoing weekly pattern, for example. A strand must have at least one schedule field.
Past dates in a dates array should be treated as informational by parser apps — they may be displayed in history views but should not appear in "what's coming up" queries. Publishers are encouraged (but not required) to remove past dates on each update to keep file size manageable.
An empty dates array ([]) is valid. It means the strand exists but has no confirmed upcoming dates. Apps may surface it as "coming soon" if the strand has a name and description.
Vibe Tags #
Vibe tags are emotional descriptors applied to strands. Apps use them to filter the portal by how the user is feeling rather than by event category. Every tag below is a valid vibe_tags value. Tags are case-insensitive.
Categories #
The category field classifies an event by type. Unlike vibe tags (which describe mood), categories describe the nature of the activity.
| Value | Description |
|---|---|
| music | Live music, DJ sets, concerts, residencies. |
| food_drink | Happy hours, tastings, pop-up dinners, market stalls. |
| fitness | Classes, open gym, sports, pools, training sessions. |
| arts | Visual art, film screenings, gallery openings, craft. |
| education | Workshops, lectures, classes, talks. |
| community | Meetups, group activities, neighborhood events, clubs. |
| market | Farmers markets, flea markets, vendor fairs. |
| sport | Sports viewing events, game days, leagues. |
| comedy | Stand-up, improv, sketch, open mic (comedy). |
| theater | Plays, performances, spoken word, poetry. |
| spiritual | Religious services, meditation, spiritual gatherings. |
| maintenance | Home maintenance tasks, service intervals, reminders. |
| personal | Birthdays, anniversaries, milestones, personal reminders. |
| general | Default. Use when no other category fits. |
Price Values #
| Value | Description |
|---|---|
| free | Completely free. No cost at any point. |
| free_entry | Free to enter; costs may apply inside (e.g. drinks, food). |
| ticketed | Requires a ticket or paid admission. |
| donation | Suggested or voluntary donation. |
| members_only | Restricted to members; membership may have a fee. |
| varies | Price varies by date or instance. Use price_note for detail. |
| unknown | Default when price is omitted. |
Recurrence Patterns #
every.every: 2 for bi-weekly.month_week and days.month_date.date on the strand (MM-DD format) or a full ISO date.weekly on Monday through Friday. Equivalent to days: ["weekday"].Day Shorthands #
The days field accepts full day names or the following shorthands:
| Value | Expands to |
|---|---|
| weekday | monday, tuesday, wednesday, thursday, friday |
| weekend | saturday, sunday |
| monday – sunday | Full day names, case-insensitive. |
| mon, tue, wed, thu, fri, sat, sun | Three-letter abbreviations, case-insensitive. |
Publisher Profile #
A publisher is any venue, artist, organiser, or person who creates and maintains strands on EventStrand. Each publisher has a profile — a permanent page that lists all their published strands and can be linked, shared, or embedded.
Publisher profiles are managed through the EventStrand portal. They are not encoded in the .rcal format itself — the format describes individual strands, not collections of them. Profile management and multi-strand aggregation are platform features built on top of the format.
| Concept | URL pattern | Description |
|---|---|---|
| Publisher profile | eventstrand.com/p/{handle} | All strands belonging to this publisher. Links to individual strands, shows combined upcoming events, and provides the master QR code. |
| Individual strand | eventstrand.com/s/{handle}/{strand-id} | A single strand. Serves the .rcal file and a human-readable preview page. This is the URL the strand's own QR code points to. |
| Master QR / link | eventstrand.com/p/{handle} | A single code that loads all of the publisher's strands at once. Recommended for front-door placement where a visitor might not know which strand they want. |
/p/{handle} is what most venues should put on their front door. When scanned, it installs all the publisher's strands simultaneously. Individual strand QR codes are useful for targeted contexts — a flyer for one specific night, a table card for a weekly residency, a poster for a seasonal series.
Multiple Strands per Publisher #
A publisher can maintain as many strands as makes sense for their operation. Each strand is a separate .rcal file with its own identity, QR code, URL, and subscriber list. Subscribers can install one strand, several, or all of them.
Strands are the right unit of organisation when a venue runs thematically distinct programmes that different audiences care about differently. A bar that hosts jazz on Fridays and stand-up comedy on Thursdays might keep these as separate strands — a jazz regular doesn't necessarily need the comedy schedule surfaced in their portal, and vice versa.
When to split into multiple strands
- Different audiences who wouldn't install each other's strand
- Different vibes or categories that would confuse the mood filter
- Different locations (two venues, one publisher)
- A seasonal series that should be installable independently
When to keep as one strand
- The audience overlaps significantly and would want everything
- The events share the same location and general vibe
- The publisher wants one QR code to cover their whole programme
Embed widgets
Each strand can be embedded on a publisher's own website as a widget — a compact, styled card showing upcoming events with an install button. Publishers can embed a single strand widget, multiple strand widgets side by side, or a combined widget that aggregates all their strands. Embed code is generated automatically in the EventStrand publisher portal.
.rcal file powers all of them. Update the strand once, every surface stays current.
Live vs Static Strands #
A strand is static by default. It is downloaded once and stored locally by the subscriber app. Updates require the user to re-download the file.
A strand is live when meta.live is true and meta.source_url is set. Subscriber apps should poll the source URL periodically and merge any changes. The recommended polling interval is once every 24 hours. Apps should use the meta.updated field to detect changes without parsing the full file on every poll.
Merging updates
When a subscriber app receives an updated version of a live strand, it should apply the following merge strategy: match events by id. Updated events replace their previous version entirely. New events (new id) are added. Events whose id no longer appears in the updated file are considered removed and should be flagged to the user before deletion.
One-Off Events #
An event with only a date field (no recurrence, no dates) is a one-off — it happens once and never repeats. One-off events coexist alongside recurring events in the same strand file. Note that date can also be combined with recurrence — for example, an opening-night date plus a weekly pattern that follows — but it cannot be combined with dates.
{
"id": "summer-block-party",
"name": "Summer Block Party",
"category": "community",
"date": "2026-07-18",
"time_start": "14:00",
"time_end": "22:00",
"vibe_tags": ["social", "outdoor", "free"],
"price": "free",
"notes": "Rain date July 25."
}
The date field is for a singular event that will never repeat. For an event without a predictable pattern that will have many dates over time, use dates instead — see dates array. One-off events should be surfaced in apps up to lead_time_days before the event date, and removed from the portal after the event passes. For live strands, publishers should remove past one-offs from the file on their next update to keep the file clean.
Validation Rules #
A valid .rcal file must satisfy all of the following:
Hard requirements (reject if violated)
- The file is valid JSON.
- The top-level
rcalkey is present and a non-empty string. - The top-level
eventskey is present and an array (may be empty). - Every strand has a non-empty
idstring. - Every strand has a non-empty
namestring. - Strand
idvalues are unique within the file. - Every recurrence rule has a
patternfield. - One-off events (no
recurrence, nodates) have adatefield. dateis mutually exclusive withrecurrenceanddates. A strand withdatemay not also have either of the others.recurrenceanddatesmay coexist on the same event — occurrences from both are merged.- A strand must have at least one of
recurrence,date, ordates.
Soft requirements (warn, do not reject)
- Unrecognised fields at any level should be preserved and ignored, not rejected.
- Unrecognised
pattern,category, orvibe_tagsvalues should be treated as unknown rather than causing failure. - A
live: truestrand withoutsource_urlshould warn but not fail. - A
datesarray where all entries are in the past should warn but not fail. - An entry in
datesthat is not a valid ISO 8601 date should be skipped with a warning, not cause full parse failure. - A
monthly_weekrule withoutmonth_weekshould be treated asfirst. - Times outside 00:00–23:59 should be rejected per-rule with a warning.
Minimal valid file #
The smallest possible .rcal file — one recurring event, no metadata.
{
"rcal": "0.1",
"events": [
{
"id": "open-swim",
"name": "Open Swim",
"recurrence": [
{
"pattern": "weekly",
"days": ["monday", "wednesday", "friday"],
"time_start": "06:00",
"time_end": "08:00"
}
]
}
]
}
Full venue strand #
A bar with two recurring events, a seasonal rule, an exception, and live hosting.
{
"rcal": "0.1",
"meta": {
"title": "The Rusty Nail",
"type": "venue",
"description": "Jazz bar in Logan Square, Chicago.",
"color": "#C0392B",
"website": "https://therustynail.com",
"timezone": "America/Chicago",
"live": true,
"source_url": "https://eventstrand.com/s/rusty-nail",
"published": "2026-03-17",
"location": {
"address": "1234 N Milwaukee Ave",
"city": "Chicago",
"state": "IL",
"country": "US",
"lat": 41.9214,
"lng": -87.7078
}
},
"events": [
{
"id": "friday-jazz",
"name": "Friday Jazz Residency",
"category": "music",
"vibe_tags": ["mellow", "social"],
"price": "free_entry",
"notes": "Bar opens at 7pm. Music 9pm–midnight.",
"lead_time_days": 1,
"recurrence": [
{
"pattern": "weekly",
"days": ["friday"],
"time_start": "21:00",
"time_end": "00:00"
}
],
"exceptions": [
{
"type": "skip",
"date": "2026-12-25",
"note": "Closed Christmas"
}
]
},
{
"id": "happy-hour",
"name": "Happy Hour",
"category": "food_drink",
"vibe_tags": ["spontaneous", "social"],
"price": "free_entry",
"lead_time_days": 0,
"recurrence": [
{
"pattern": "weekly",
"days": ["weekday"],
"time_start": "16:00",
"time_end": "19:00"
},
{
"pattern": "weekly",
"days": ["saturday"],
"time_start": "14:00",
"time_end": "18:00",
"season_start": "may",
"season_end": "sep"
}
]
}
]
}
Personal strand #
A personal file mixing life milestones, maintenance reminders, and a monthly recurring event.
{
"rcal": "0.1",
"meta": {
"title": "My World",
"type": "personal",
"timezone": "America/New_York",
"live": false
},
"events": [
{
"id": "sisters-birthday",
"name": "Sarah's Birthday",
"category": "personal",
"vibe_tags": ["celebratory"],
"lead_time_days": 14,
"recurrence": [
{
"pattern": "annual",
"month_date": 22,
"season_start": "aug",
"season_end": "aug"
}
]
},
{
"id": "hvac-filter",
"name": "Replace HVAC Filter",
"category": "maintenance",
"vibe_tags": [],
"lead_time_days": 3,
"recurrence": [
{
"pattern": "monthly_date",
"month_date": 1,
"every": 3,
"time_start": "09:00"
}
]
},
{
"id": "book-club",
"name": "Book Club",
"category": "community",
"vibe_tags": ["curious", "social", "cozy"],
"recurrence": [
{
"pattern": "monthly_week",
"month_week": "second",
"days": ["thursday"],
"time_start": "19:00",
"time_end": "21:30"
}
]
}
]
}
Recurring strand with one-off event #
A venue's strand file that includes both a recurring weekly night and a special one-off show next month.
{
"rcal": "0.1",
"meta": {
"title": "The Hideout",
"type": "venue",
"timezone": "America/Chicago",
"live": true,
"source_url": "https://eventstrand.com/s/hideout-chicago"
},
"events": [
{
"id": "open-mic",
"name": "Open Mic Night",
"category": "music",
"vibe_tags": ["curious", "social"],
"price": "free_entry",
"recurrence": [
{
"pattern": "monthly_week",
"month_week": "last",
"days": ["thursday"],
"time_start": "20:00",
"time_end": "23:00"
}
]
},
{
"id": "anniversary-show-2026",
"name": "30th Anniversary Show",
"category": "music",
"vibe_tags": ["celebratory", "social"],
"price": "ticketed",
"price_note": "$20 advance, $25 door",
"ticket_url": "https://dice.fm/hideout-anniversary",
"date": "2026-05-09",
"time_start": "19:00",
"time_end": "02:00",
"lead_time_days": 21
}
]
}
Seasonal span #
A span-style event describing astronomical summer in the northern hemisphere. No specific days or times — the event is the season itself. Apps that render span-style strands (such as ManaCal) display these as solid blocks from start to end date rather than discrete occurrences.
annual pattern with season_start + season_end but no days or time_start. Span-aware apps render these as a continuous block; occurrence-based apps may render only the start date.
{
"rcal": "0.2",
"meta": {
"title": "Summer",
"type": "personal",
"timezone": "America/New_York"
},
"events": [
{
"id": "summer",
"name": "Summer",
"category": "general",
"vibe_tags": ["outdoor", "active", "social"],
"recurrence": [
{
"pattern": "annual",
"season_start": "jun",
"season_start_day": 21,
"season_end": "sep",
"season_end_day": 22,
"fuzzy_start": true,
"fuzzy_end": true
}
]
}
]
}
Both boundaries are marked fuzzy because the exact solstice and equinox dates shift by 1–2 days each year. Span-aware apps should recalculate the precise dates for the viewed year using the Meeus algorithm or equivalent, and render a ~ symbol at each fuzzy edge.
Changelog #
- New recurrence rule fields:
season_start_dayandseason_end_day(integer, 1–31) — pair withseason_start/season_endto specify an exact day within the active month rather than snapping to first/last day of month. - New recurrence rule fields:
fuzzy_startandfuzzy_end(boolean) — signal that the boundary date shifts year to year (e.g. astronomical seasons). Apps should render a~indicator at fuzzy edges and may recalculate the exact date per year using known algorithms. - Motivating use case: seasonal span events (summer, winter, tax season, sports seasons) for consumers such as ManaCal-style dashboards that render date ranges as continuous blocks rather than discrete occurrences.
meta, location, events, recurrence, and exceptions objects. Renames strands[] to events[] to clarify that the file is the strand and the array contains events. Establishes canonical vibe tag list, category list, price values, recurrence patterns, and day shorthands. Introduces live vs static strand model, one-off event support (date), and manually-curated date list support (dates). Defines schedule authoring modes: recurrence (pattern-based), date (singular one-off, mutually exclusive with dates), dates (curated list). recurrence and dates may coexist on one event; date and recurrence may also coexist.
License #
The .rcal specification is released into the public domain under CC0 1.0. No rights reserved.
You may use, implement, extend, fork, or build commercial products based on this specification without attribution, permission, or fee. We ask (but do not require) that compatible implementations use the .rcal file extension and the application/vnd.rcal+json MIME type to aid ecosystem interoperability.