lib-recur Java Library

repository·main·Indexed 42 days ago

https://github.com/dmfs/lib-recur

A Java library for parsing and iterating through recurrence rules (RRULEs), recurrence dates (RDATEs), and exclusion dates (EXDATEs) as defined in RFC 5545 and RFC 2445. It features the RecurrenceSet API for combining and manipulating sets of occurrences, support for multiple RFC compliance modes via RfcMode, and adapters for merging, excluding, and limiting infinite recurrence sets.

Tokens
1.6K
Snippets
9
Records
9
Agent score
3%

What's inside lib-recur

  1. Understand the RecurrenceSet API

    main

    The RecurrenceSet interface is the core abstraction for managing sets of occurrences. It represents the result of any combination of RRULEs, RDATEs, EXDATEs, and EXRULEs as specified in RFC 5545.

    Because RecurrenceSet extends Iterable, you can use it directly in enhanced for loops or with decorators from the jems2 library to manipulate the set of occurrences (e.g., limiting the number of results or stopping at a specific date).

    for (DateTime occurrence : recurrenceSet) {
        // do something with occurrence
    }
  2. Limit or guard infinite RecurrenceSets

    main

    Since RRULEs without COUNT or UNTIL are infinite, you must use decorators to prevent infinite loops.

    1. Using jems2 decorators: Use First<N> to limit to the first $N$ occurrences, or While<Predicate> to stop when a condition is met.
    2. Using lib-recur decorators: Use Preceding<DateTime> to iterate only occurrences before a specific date, or Within<DateTime, DateTime> to iterate within a specific (right-open) interval.
    // Limit to first 1000 occurrences using jems2
    for (DateTime occurrence : new First<>(1000, new OfRule(rrule, startDate))) {
        // ...
    }
    
    // Limit to occurrences before a specific date
    for (DateTime occurrence : new Preceding<>(endDate, new OfRule(rrule, startDate))) {
        // ...
    }
    
    // Iterate within a specific interval (right-open)
    RecurrenceSet occurrencesOf2024 = new Within(start2024, end2025, recurrenceSet);
  3. Find the last instance of a finite RecurrenceSet

    main

    For finite, non-empty RecurrenceSets, you can determine the final occurrence using the LastInstance adapter. It returns an Optional<DateTime>.

    Optional<DateTime> last = new LastInstance(new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=10"), startDate));
  4. Exclude exceptions from a RecurrenceSet

    main

    To remove specific dates from a set of occurrences (e.g., applying EXDATE logic), use the Difference class.

    Note: Exclusions require exact matches. For example, an EXDATE of 20240216 will not exclude 20240216T120000 or 20240216T000000 unless the formats match exactly.

    RecurrenceSet withoutExceptions = new Difference(
        new OfRule(rule, start),
        new OfList(timezone, exdates)
    );
  5. Combine multiple RecurrenceSets

    main

    You can merge multiple sets of occurrences (rules or lists) into a single chronological stream using the Merged class.

    RecurrenceSet merged = new Merged(
        new OfRule(rule, start),
        new OfList(timezone, rdates)
    );
  6. Iterate occurrences of a RecurrenceRule

    main

    To iterate through the occurrences of a single RecurrenceRule, use the OfRule adapter.

    Important: OfRule does not include the startDate in the iteration if the start date itself does not match the rule's pattern. If you need to ensure the startDate is included regardless of whether it matches the rule, use OfRuleAndFirst instead.

    // Standard iteration (skips startDate if it doesn't match rule)
    RecurrenceSet occurrences = new OfRule(rrule, startDate);
    
    // Iteration including the startDate even if it doesn't match
    RecurrenceSet occurrencesWithFirst = new OfRuleAndFirst(rrule, startDate);
  7. Iterate RDates and ExDates

    main

    To iterate through comma-separated lists of dates or datetimes (such as RDATE or EXDATE components), use the OfList adapter.

    for (DateTime occurrence : new OfList(timeZone, rdates)) {
        // do something with occurrence
    }
  8. Build a RecurrenceRule programmatically

    main

    You can construct a RecurrenceRule by specifying a base frequency and then adding components using setByRule.

    Note: When using setByRule for months, the values are 1-based (unlike java.util.Calendar which is 0-based).

    RecurrenceRule rule = new RecurrenceRule(Freq.MONTHLY);
    rule.setCount(20);
    
    // Set months (1-based: Jan, Mar, May, Jul)
    rule.setByRule(Part.BYMONTH, 1, 3, 5, 7);
    
    // Set days of the month using an array
    Integer[] dayArray = new Integer[]{4, 8, 12};
    rule.setByRule(Part.BYMONTHDAY, dayArray);
    
    String ruleStr = rule.toString(); 
    // Result: "FREQ=MONTHLY;BYMONTH=1,3,5,7;BYMONTHDAY=4,8,12;COUNT=20"
  9. Parse RecurrenceRules with different RFC modes

    main

    The library supports different compliance levels via RfcMode. The default mode is RfcMode.RFC5545_LAX.

    • RFC5545_STRICT: Throws InvalidRecurrenceRuleException for rules that violate RFC 5545 (e.g., BYWEEKNO used with non-yearly rules).
    • RFC5545_LAX: More tolerant of RFC 5545 rules.
    • RFC2445_STRICT: Strict compliance with RFC 2445.
    • RFC2445_LAX: Tolerant compliance with RFC 2445.
    // Example of strict parsing throwing an exception
    RecurrenceRule rule = new RecurrenceRule("FREQ=WEEKLY;BYWEEKNO=1;BYDAY=SU", RfcMode.RFC2445_STRICT);