Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ This library provides comprehensive support for parsing and formatting recurrenc
- Validating ranges for numerical values (e.g., seconds, minutes, days).
- Converts between `Calendar.RecurrenceRule` objects and RFC 5545-compliant strings.

**Key RFC 5545 parsing rules** (see [RFC 5545 Section 3.3.10](https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10)):
- **FREQ** is required exactly once; rule parts may appear in **any order** (e.g. `COUNT=5;FREQ=DAILY` is valid). The key must be exactly `FREQ` (case-insensitive).
- **Case-insensitivity**: Property names and enumerated values are case-insensitive (e.g. `freq=daily`, `BYDAY=mo,we`).
- **Content-line folding**: Input is unfolded before parsing (CRLF/LF + space removed). When formatting, use `foldLongLines: true` to fold lines at 75 octets.
- **WKST**: The `WKST` part is accepted when parsing (and ignored); use `emitWKST: true` when formatting to emit `WKST=MO`.
- **SECONDLY** is not supported (Foundation has no corresponding frequency).

---

## Platform Support
Expand Down Expand Up @@ -114,6 +121,16 @@ print(result) // Outputs: "FREQ=DAILY;INTERVAL=2;COUNT=5;BYDAY=MO,WE"

---

## Add-ons

Optional modules that extend RRuleKit for specific integrations:

| Product | Description |
|--------|-------------|
| **RRuleKitRruleJSInterop** | Interop with [jkbrzt/rrule](https://github.com/jkbrzt/rrule) (JavaScript): parse full `DTSTART` + `RRULE:` content from `rule.toString()`, reject `FREQ=SECONDLY` with a clear error, and format rules with an optional DTSTART line for rrule.js. See `Sources/RRuleKitRruleJSInterop/README.md` and add `RRuleKitRruleJSInterop` to your target dependencies. |

---

## Calendar.RecurrenceRule.End Support

The library includes support for the `.afterOccurrences` and `.afterDate` formats within `Calendar.RecurrenceRule.End`. However, these formats are available only on the following platform versions:
Expand All @@ -129,7 +146,7 @@ The library includes support for the `.afterOccurrences` and `.afterDate` format

## Key RFC 5545 Parsing Rules

- **FREQ** is mandatory and must be the first key in the rule string.
- **FREQ** is required exactly once; rule parts may appear in any order.
- Currently, FREQ=SECONDLY is not supported.
- Only one of COUNT or UNTIL can be specified.
- Keys and values are separated by = and must be delimited by ;.
Expand All @@ -155,18 +172,31 @@ For example:
- `BYMONTHDAY` values must be in the range -31–31.
- `UNTIL` and `COUNT` cannot coexist in the same rule.

### Enforced Rules

The library explicitly enforces the following RFC 5545 rules:

- **FREQ required:** Exactly one `FREQ` part is required; rule parts may appear in any order (e.g. `COUNT=5;FREQ=DAILY` is valid).
- **No SECONDLY:** `FREQ=SECONDLY` is not supported (Foundation’s `RecurrenceRule.Frequency` does not include it). Parsing throws an error.
- **COUNT and UNTIL mutually exclusive:** Only one of `COUNT` or `UNTIL` may appear in a rule; duplicate or conflicting keys cause parse failure.
- **Duplicate keys:** Duplicate keys (e.g. `FREQ=DAILY;FREQ=WEEKLY`) cause parsing to fail.

### Limitations

`FREQ=SECONDLY` is not supported because `Calendar.RecurrenceRule.Frequency` does not currently include this frequency. If the input string specifies `FREQ=SECONDLY`, the library will throw an error.

RFC 5545 errata (e.g. restrictions on `BYDAY` with numeric modifiers when `BYWEEKNO` is present in YEARLY rules) are not validated; invalid combinations are left to Foundation’s `RecurrenceRule` semantics.

---

## Testing

`RRuleKit` includes an extensive test suite that validates the following:

- Correct parsing and formatting of all supported rule parts.
- Compliance with the RFC 5545 standard.
- Compliance with the RFC 5545 standard (FREQ required, any part order, no SECONDLY, duplicate keys rejected).
- Round-trip consistency: parse → format → parse and format → parse → format for representative rules (including UNTIL with date/date-time and multiple BY* parts).
- Large-rule formatting with semantic checks on output.
- Buffer capacity adjustments to handle large RRULE strings efficiently.

---
Expand Down
6 changes: 6 additions & 0 deletions Sources/RRuleKit/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ Each feature is optimized for performance and adheres to the rules and constrain
- Supports both UTC and local time zone representations.
- Includes `TZID` for local times.

- **Enforced RFC 5545 rules**:
- FREQ is required (exactly once, any position); keys and values are case-insensitive.
- Input is unfolded (CRLF/LF + space removed) before parsing; optional line folding when formatting.
- WKST is accepted when parsing and optionally emitted when formatting; SECONDLY is not supported.
- COUNT and UNTIL are mutually exclusive; duplicate keys cause parse failure.

## Topics

### Essentials
Expand Down
18 changes: 18 additions & 0 deletions Sources/RRuleKit/Documentation.docc/FormattingRFC5545.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,21 @@ let rfcString = formatter.format(rrule)
print(rfcString)
// Output: "FREQ=WEEKLY;BYDAY=MO,FR"
```

## Format Options

- **foldLongLines**: When `true`, the formatted string is folded so no line exceeds 75 octets (RFC 5545 Section 3.1). Continuation lines are introduced with CRLF + SPACE. Default is `false`.
- **emitWKST**: When `true`, the formatter appends `;WKST=MO` for RECUR compliance (default week start). WKST is not stored in `RecurrenceRule`. Default is `false`.

Example:

```swift
let formatter = RecurrenceRuleRFC5545FormatStyle(calendar: .current, foldLongLines: true, emitWKST: true)
let rfcString = formatter.format(rrule)
```

## Notes

- Output order follows RFC 5545: FREQ first, then COUNT or UNTIL (if present), INTERVAL, then BY* parts in a fixed order, optionally WKST.
- Date and date-time values in UNTIL are formatted per RFC 5545 (UTC with Z, or TZID for local time).
- Time components default to midnight when omitted.
21 changes: 15 additions & 6 deletions Sources/RRuleKit/Documentation.docc/ParsingRFC5545.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Parsing functionality allows conversion of strings like:
FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;COUNT=10
```

into structured `Calendar.RecurrenceRule objects`.
into structured `Calendar.RecurrenceRule` objects.

## Example

Expand All @@ -21,16 +21,25 @@ import RRuleKit
let parser = RecurrenceRuleRFC5545FormatStyle(calendar: .current)

do {
let rfcString = "FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;COUNT=10"
let recurrenceRule = try parser.parse(rfcString)
let rfcString = "FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;COUNT=10"
let recurrenceRule = try parser.parse(rfcString)
print(recurrenceRule)
} catch {
print("Parsing error: \(error)")
}
```

## Notes
## Key RFC 5545 Parsing Rules

- FREQ is mandatory and must appear first.
- COUNT and UNTIL cannot coexist in a rule.
- **FREQ** is mandatory and must appear exactly once. Rule parts may appear in **any order** (e.g. `COUNT=5;FREQ=DAILY` is valid). The key must be exactly `FREQ` (case-insensitive); `FREQUENCY=DAILY` is rejected.
- **Case-insensitivity**: Property names and enumerated values are case-insensitive (e.g. `freq=daily`, `BYDAY=mo,we`).
- **Content-line folding**: Input is unfolded before parsing: CRLF or LF followed by a single SPACE or HTAB is removed, so folded content lines (e.g. from .ics files) parse correctly.
- **WKST**: The `WKST` rule part is accepted and ignored (Foundation has no week-start API).
- **COUNT** and **UNTIL** cannot coexist; only one may be specified.
- **Duplicate keys** cause parsing to fail.
- **FREQ=SECONDLY** is not supported and will cause parsing to fail.
- Invalid or unsupported keys will cause parsing to fail.

## Limitations

RFC 5545 errata (e.g. BYDAY with numeric modifiers in combination with BYWEEKNO for YEARLY rules) are not validated by the parser.
Loading