Back to Super Productivity

Recurring Events Research Report

docs/research/recurring-events-industry-standards.md

18.4.413.1 KB
Original Source

Recurring Events Research Report

Executive Summary

Super Productivity has a functional recurring task system with good foundational patterns (deterministic IDs, sync-safe design, DST handling). However, it implements a custom recurrence model that lacks many patterns users expect from modern task/calendar applications. Adopting RFC 5545 RRULE as the recurrence format would unlock significant capabilities with relatively low implementation cost.


1. Current Implementation Analysis

What Super Productivity Has

FeatureImplementationStatus
Daily recurrencerepeatCycle: 'DAILY'
Weekly recurrencerepeatCycle: 'WEEKLY' + weekday flags
Monthly recurrencerepeatCycle: 'MONTHLY'
Yearly recurrencerepeatCycle: 'YEARLY'
Interval (every N)repeatEvery: number
Weekday selectionmonday, tuesday, etc. booleans
Start datestartDate: string
Skip occurrencesdeletedInstanceDates: string[]
Completion-basedrepeatFromCompletionDate: boolean
Pause/resumeisPaused: boolean
Deterministic IDsrpt_${cfgId}_${dueDay}
DST-safe calculationUses noon (12:00) for comparisons
Subtask templatessubTaskTemplates[]

Strengths

  1. Sync-safe architecture - Deterministic task IDs prevent duplicates across devices
  2. DST handling - Uses noon-based calculations to avoid daylight savings issues
  3. Clean separation - TaskRepeatCfg is separate from tasks, allowing template-based generation
  4. Projection system - Shows future occurrences in schedule without creating actual tasks
  5. Completion-based mode - Supports "after completion" scheduling (not in RFC 5545)

2. Gap Analysis: What's Missing

High-Impact Missing Features

PatternRFC 5545Industry AppsSuper Productivity
Nth weekday of month (e.g., "2nd Tuesday")BYDAY=2TUAll major apps❌ Missing
Last day of monthBYMONTHDAY=-1All major apps❌ Missing
Last weekday of monthBYDAY=-1FRMost apps❌ Missing
End after N occurrencesCOUNT=10All major apps❌ Missing
End on specific dateUNTIL=20251231All major apps❌ Missing
Last weekday (business)BYDAY=MO-FR;BYSETPOS=-1Some apps❌ Missing
Multiple days per monthBYMONTHDAY=1,15Some apps❌ Missing
Every N months on Nth weekdayComplex RRULEGoogle/Outlook❌ Missing

Medium-Impact Missing Features

PatternRFC 5545Impact
Modify single occurrenceRECURRENCE-IDUsers can't reschedule one instance
"This and future" changesRANGE=THISANDFUTUREMust delete and recreate
Bi-weekly on multiple daysINTERVAL=2;BYDAY=MO,WE,FRWorks but limited UI
Specific weeks of yearBYWEEKNO=1,26Rare but useful
Day of yearBYYEARDAY=100Very rare

Current Data Model Limitations

typescript
// Current: Custom format
interface TaskRepeatCfg {
  repeatCycle: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
  repeatEvery: number;
  monday?: boolean;
  tuesday?: boolean;
  // ... 5 more boolean fields
  startDate?: string;
  // No end condition (COUNT/UNTIL)
  // No BYMONTHDAY for specific days
  // No BYDAY ordinal (1MO, -1FR)
}

// RFC 5545: Single string encodes everything
// "FREQ=MONTHLY;BYDAY=2TU;COUNT=12"

3. How Major Apps Compare

FeatureGoogle CalendarTodoistThings 3TickTickSuper Productivity
Basic (D/W/M/Y)
Every N interval
Weekday selection
Nth weekday of month
Last day of month
End after N times
End on date
After completion
Skip occurrence
Natural language
iCal export

4. Industry Standards: RFC 5545 RRULE

The iCalendar specification (RFC 5545) is the most widely adopted standard for defining recurrence patterns. The RRULE (Recurrence Rule) property is the core mechanism.

Core RRULE Components

ParameterDescriptionValid Values
FREQ (Required)Frequency of recurrenceYEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY
INTERVALSpacing between iterationsAny positive integer (default: 1)
COUNTNumber of occurrencesAny positive integer
UNTILEnd date for recurrenceDATE or DATE-TIME value
WKSTWeek start dayMO, TU, WE, TH, FR, SA, SU (default: MO)

BYxxx Rule Parts

ParameterDescriptionValid Values
BYDAYDays of the weekMO, TU, WE, TH, FR, SA, SU (with optional ordinal prefix)
BYMONTHMonths of the year1-12
BYMONTHDAYDays of the month1 to 31 or -31 to -1 (negative = from end)
BYYEARDAYDays of the year1 to 366 or -366 to -1
BYWEEKNOISO 8601 week numbers1 to 53 or -53 to -1
BYSETPOSPosition within recurrence set1 to 366 or -366 to -1

BYDAY with Ordinal Prefix

Each BYDAY value can be preceded by a positive (+n) or negative (-n) integer:

  • +1MO or 1MO = First Monday of the period
  • -1MO = Last Monday of the period
  • +2TU = Second Tuesday

Common RRULE Examples

# Daily for 10 occurrences
RRULE:FREQ=DAILY;COUNT=10

# Weekly on Monday and Friday until end of 2024
RRULE:FREQ=WEEKLY;UNTIL=20241231T235959Z;BYDAY=MO,FR

# Every other week on Monday, Wednesday, Friday
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR

# Monthly on the 15th
RRULE:FREQ=MONTHLY;BYMONTHDAY=15

# Monthly on the last day
RRULE:FREQ=MONTHLY;BYMONTHDAY=-1

# First Monday of every month
RRULE:FREQ=MONTHLY;BYDAY=1MO

# Last Friday of every month
RRULE:FREQ=MONTHLY;BYDAY=-1FR

# Second Tuesday of every month
RRULE:FREQ=MONTHLY;BYDAY=2TU

Exception Handling: EXDATE and RDATE

PropertyPurposeExample
EXDATEExclude specific occurrencesEXDATE:20240710T070000Z
RDATEAdd additional occurrencesRDATE:20241106T080000Z

5. Recommendations

Recommendation 1: Adopt RRULE Format Internally

Priority: High | Effort: Medium

Store recurrence as RFC 5545 RRULE string instead of custom fields:

typescript
interface TaskRepeatCfgV2 {
  id: string;
  projectId: string | null;
  title: string | null;
  tagIds: string[];

  // Replace custom fields with RRULE
  rrule: string;  // e.g., "FREQ=MONTHLY;BYDAY=2TU;COUNT=12"

  // Keep SP-specific extensions
  repeatFromCompletionDate?: boolean;  // Not in RFC 5545
  isPaused: boolean;

  // Exception handling (RFC 5545 compliant)
  exdates?: string[];  // Excluded dates
  rdates?: string[];   // Additional dates (optional)

  // Keep existing task template fields
  defaultEstimate?: number;
  startTime?: string;
  remindAt?: TaskReminderOptionId;
  // ... etc
}

Benefits:

  • Unlocks all RFC 5545 patterns (nth weekday, last day, COUNT, UNTIL, etc.)
  • Single source of truth for recurrence logic
  • Easy iCal import/export compatibility
  • Leverage existing library (rrule.js) for calculations

Recommendation 2: Use rrule.js Library

Priority: High | Effort: Low

bash
npm install rrule

Why rrule.js:

  • 3.7k GitHub stars, battle-tested
  • Full RFC 5545 compliance
  • Natural language output (rule.toText() → "every 2 weeks on Monday, Friday")
  • TypeScript types included
  • ~5KB gzipped

Adaptation needed:

  • Super Productivity uses noon-based DST handling; rrule.js has timezone quirks
  • Wrap rrule.js calls in utility that normalizes to local noon time

Recommendation 3: Implement Missing High-Value Patterns

Priority: High | Effort: Medium

  1. Nth weekday of month - "2nd Tuesday", "Last Friday"

    • RRULE: FREQ=MONTHLY;BYDAY=2TU or BYDAY=-1FR
  2. Last day of month - Critical for billing/invoicing tasks

    • RRULE: FREQ=MONTHLY;BYMONTHDAY=-1
  3. End conditions - "Repeat 10 times" or "Until December 31"

    • RRULE: COUNT=10 or UNTIL=20251231T235959Z

Recommendation 4: Improve Quick Settings UI

Priority: Medium | Effort: Low

typescript
// Recommended quick settings
type TaskRepeatCfgQuickSettingV2 =
  | 'DAILY'
  | 'WEEKDAYS'                    // M-F
  | 'WEEKLY_SAME_DAY'             // Every [current weekday]
  | 'BIWEEKLY_SAME_DAY'           // Every 2 weeks
  | 'MONTHLY_SAME_DATE'           // Same day of month
  | 'MONTHLY_SAME_WEEKDAY'        // e.g., "3rd Wednesday" (NEW)
  | 'MONTHLY_LAST_DAY'            // Last day of month (NEW)
  | 'MONTHLY_LAST_WEEKDAY'        // Last [current weekday] (NEW)
  | 'YEARLY_SAME_DATE'
  | 'CUSTOM';

Recommendation 5: Preserve "After Completion" Mode

Priority: Medium | Effort: N/A (already implemented)

This is a competitive advantage. RFC 5545 doesn't support this, but Todoist, Things, and TickTick all do. Keep the repeatFromCompletionDate flag as a Super Productivity extension.


6. Implementation Roadmap

Phase 1: Foundation (Low Risk)

  1. Add rrule dependency
  2. Create utility wrapper for rrule.js with DST-safe handling
  3. Add rrule field to TaskRepeatCfg model (keep old fields)
  4. Write bidirectional conversion: old format ↔ RRULE string
  5. Update getNextRepeatOccurrence() to use rrule.js when rrule field present

Phase 2: New Patterns (Medium Risk)

  1. Update UI to support new patterns (nth weekday, last day, etc.)
  2. Add end conditions (COUNT, UNTIL) to UI
  3. Add new quick settings
  4. Migrate existing configs to RRULE format on save

Phase 3: Polish (Low Risk)

  1. Add natural language display: "Repeats every 2nd Tuesday"
  2. Improve heatmap to show projected future occurrences
  3. Consider iCal export for calendar integration

Phase 4: Advanced (High Risk, Optional)

  1. Single instance modifications
  2. "This and future" changes
  3. iCal import with recurrence

7. RRULE Mapping Examples

User RequestCurrent SPRRULE
Every dayDAILY, every=1FREQ=DAILY
Every 3 daysDAILY, every=3FREQ=DAILY;INTERVAL=3
Every MondayWEEKLY, mon=trueFREQ=WEEKLY;BYDAY=MO
Every 2 weeks on Mon, WedWEEKLY, every=2, mon=true, wed=trueFREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE
Monthly on 15thMONTHLY, every=1FREQ=MONTHLY;BYMONTHDAY=15
2nd Tuesday monthly❌ Not possibleFREQ=MONTHLY;BYDAY=2TU
Last Friday monthly❌ Not possibleFREQ=MONTHLY;BYDAY=-1FR
Last day of month❌ Not possibleFREQ=MONTHLY;BYMONTHDAY=-1
Yearly on March 15YEARLY, every=1FREQ=YEARLY;BYMONTH=3;BYMONTHDAY=15
10 times then stop❌ Not possibleFREQ=WEEKLY;COUNT=10
Until Dec 31, 2025❌ Not possibleFREQ=WEEKLY;UNTIL=20251231

8. Library Comparison

Criteriarrule.jsrSchedulelater.js
RFC 5545 ComplianceHighPartialNo
Bundle Size~5KB gzipSmaller~8KB
TypeScriptYesNativeCommunity
Timezone SupportQuirkyExcellentBasic
NLP SupportYesNoYes
Active MaintenanceSlowerYesFork only
GitHub Stars3.7k431.7k

Recommendation: Use rrule.js for RFC 5545 compliance and community support. Wrap timezone handling carefully.


9. Key Files in Current Implementation

FeatureFile
Task date propertiessrc/app/features/tasks/task.model.ts
Recurring config modelsrc/app/features/task-repeat-cfg/task-repeat-cfg.model.ts
Repeat calculationsrc/app/features/task-repeat-cfg/store/get-next-repeat-occurrence.util.ts
Repeat selectorssrc/app/features/task-repeat-cfg/store/task-repeat-cfg.selectors.ts
Repeat servicesrc/app/features/task-repeat-cfg/task-repeat-cfg.service.ts
Dialog UIsrc/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/
Schedule integrationsrc/app/features/schedule/schedule.service.ts
Planner integrationsrc/app/features/planner/planner.service.ts

Conclusion

Super Productivity's recurring task system has solid foundations (sync safety, DST handling) but uses a limited custom format. Adopting RFC 5545 RRULE—while preserving the repeatFromCompletionDate extension—would:

  1. Enable commonly requested patterns (nth weekday, last day, end conditions)
  2. Reduce maintenance burden by leveraging battle-tested library
  3. Enable future iCal integration for calendar sync
  4. Align with industry standards used by Google, Microsoft, Apple

The recommended approach is incremental: add RRULE support alongside existing fields, migrate gradually, and preserve backward compatibility.