@volt-package/rrule
v0.1.0
Published
A secure and optimized RRULE (RFC 5545) parser with leap year correction support
Maintainers
Readme
@volt-package/rrule
Security-hardened and high-performance RRULE parser compliant with RFC 5545 (iCalendar)
🎯 Key Features
- ✅ Security First: ReDoS attack prevention, input validation, resource limits
- ⚡ High Performance: Lazy evaluation, O(1) memory usage, LRU caching
- 🇰🇷 Korean Business Logic: Leap year correction (Feb 29 → Feb 28)
- 📦 Zero Dependencies: No external dependencies
- 🔧 Full TypeScript Support: Type-safe guaranteed
- 🌍 Internationalization: English/Korean natural language conversion
📦 Installation
npm install @volt-package/rrule
# or
bun add @volt-package/rrule🚀 Quick Start
Basic Usage
import { RRule, toText } from '@volt-package/rrule';
// Create from RRULE string
const rrule = new RRule('FREQ=DAILY;COUNT=5');
// Or create from options object
const rrule2 = new RRule({
freq: 'WEEKLY',
dtstart: new Date('2024-01-01'),
count: 10,
byDay: ['MO', 'WE', 'FR']
});
// Get all dates
const dates = rrule.all();
console.log(dates);
// Convert to natural language
console.log(toText(rrule)); // "every day for 5 times"
console.log(toText(rrule, { language: 'ko' })); // "매일 5회"Optimized Import (Smaller Bundle)
// Import core functionality only (no toText)
// Bundle size: 4.4 KB (gzipped) vs 5.5 KB (full)
import { RRule, RRuleSet } from '@volt-package/rrule/core';
const rrule = new RRule('FREQ=DAILY;COUNT=5');
const dates = rrule.all();
// Import toText separately only if needed
import { toText } from '@volt-package/rrule';
console.log(toText(rrule));Lazy Evaluation (Recommended)
// Memory-efficient iteration
for (const date of rrule.iterator()) {
console.log(date);
}
// Get first N dates
const firstFive = rrule.take(5);
// Get dates within range
const dates = rrule.between(
new Date('2024-01-01'),
new Date('2024-12-31')
);📚 Main Features
1. Basic Recurrence Patterns
// Daily
new RRule('FREQ=DAILY;COUNT=7');
// Weekly (specific weekdays)
new RRule('FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10');
// Monthly (specific days)
new RRule('FREQ=MONTHLY;BYMONTHDAY=1,15;COUNT=12');
// Yearly
new RRule('FREQ=YEARLY;BYMONTH=1,7;COUNT=5');2. Advanced Filtering
BYSETPOS: Select Specific Positions
// Last Friday of each month
new RRule('FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1;COUNT=12');
// First and third Monday of each month
new RRule('FREQ=MONTHLY;BYDAY=MO;BYSETPOS=1,3;COUNT=24');BYYEARDAY: Specific Days of Year
// Days 1, 100, 200 of each year
new RRule('FREQ=YEARLY;BYYEARDAY=1,100,200;COUNT=6');
// Last day of each year (using negative)
new RRule('FREQ=YEARLY;BYYEARDAY=-1;COUNT=5');BYWEEKNO: ISO Week Numbers
// Mondays of weeks 1 and 20 each year
new RRule('FREQ=YEARLY;BYWEEKNO=1,20;BYDAY=MO;COUNT=4');3. RRuleSet: Complex Rule Combinations
import { RRuleSet } from '@volt-package/rrule';
const set = new RRuleSet();
// Weekday workdays
set.rrule({
freq: 'DAILY',
dtstart: new Date('2024-01-01'),
until: new Date('2024-12-31'),
byDay: ['MO', 'TU', 'WE', 'TH', 'FR']
});
// Exclude holidays
set.exdate(new Date('2024-01-01')); // New Year
set.exdate(new Date('2024-12-25')); // Christmas
// Add special work days
set.rdate(new Date('2024-12-31')); // Year-end special work
const workDays = set.all();4. Natural Language Conversion
import { RRule, toText } from '@volt-package/rrule';
const rrule = new RRule('FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1;COUNT=12');
// English
console.log(toText(rrule));
// "every month on the last Friday for 12 times"
// Korean
console.log(toText(rrule, { language: 'ko' }));
// "매월 마지막 금요일에 12회"
// Or pass options directly
console.log(toText({ freq: 'DAILY', count: 5 }));
// "every day for 5 times"5. Leap Year Correction (Korean Business Logic)
// Yearly recurrence starting from Feb 29
const rrule = new RRule({
freq: 'YEARLY',
dtstart: new Date('2024-02-29'), // Leap year
count: 5
});
const dates = rrule.all();
// 2024-02-29 (leap year)
// 2025-02-28 (non-leap year → auto-corrected)
// 2026-02-28 (non-leap year → auto-corrected)
// 2027-02-28 (non-leap year → auto-corrected)
// 2028-02-29 (leap year)🔒 Security Features
1. ReDoS Prevention
// Uses scanner-based parser (instead of regex)
// Safe against malicious input2. Input Validation
// Input length limit
const rrule = new RRule('FREQ=DAILY...', {
maxInputLength: 2000 // default
});
// Allowlist for RFC 5545 standard keywords only3. Resource Limits
const rrule = new RRule('FREQ=SECONDLY;COUNT=10000', {
maxIterations: 730, // Maximum iteration count
maxDuration: 5, // Maximum duration (years)
timeout: 50 // Operation timeout (ms)
});🎯 API Documentation
RRule Class
Constructor
new RRule(options: RRuleOptions | string, safetyConfig?: SafetyConfig)Methods
all(): Date[]
Returns all dates as array (memory warning)
iterator(): Generator<Date>
Returns lazy evaluation generator (recommended)
take(n: number): Date[]
Returns first N dates
first(): Date | null
Returns first date
between(after: Date, before: Date, includeEnds?: boolean): Date[]
Returns dates within range
toString(): string
Converts to RRULE string
getOptions(): Readonly<RRuleOptions>
Returns options object
Standalone Functions
toText(rrule: RRule | RRuleOptions, options?: { language?: 'en' | 'ko' }): string
Converts RRULE to natural language text
import { RRule, toText } from '@volt-package/rrule';
const rrule = new RRule('FREQ=DAILY;COUNT=5');
console.log(toText(rrule)); // "every day for 5 times"
console.log(toText(rrule, { language: 'ko' })); // "매일 5회"RRuleOptions Interface
interface RRuleOptions {
freq: 'YEARLY' | 'MONTHLY' | 'WEEKLY' | 'DAILY' | 'HOURLY' | 'MINUTELY' | 'SECONDLY';
dtstart?: Date;
until?: Date;
count?: number;
interval?: number;
byMonth?: number[]; // 1-12
byMonthDay?: number[]; // 1-31, -1 = last day
byDay?: Weekday[]; // 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'
byHour?: number[]; // 0-23
byMinute?: number[]; // 0-59
bySecond?: number[]; // 0-59
bySetPos?: number[]; // Position selection (1 = first, -1 = last)
byYearDay?: number[]; // 1-366, -1 = last day
byWeekNo?: number[]; // 1-53, ISO week number
wkst?: Weekday; // Week start day
tzid?: string; // IANA timezone identifier
}RRuleSet Class
Methods
rrule(rrule: RRule | RRuleOptions | string): RRuleSet
Add inclusion rule
rdate(date: Date): RRuleSet
Add inclusion date
exrule(rrule: RRule | RRuleOptions | string): RRuleSet
Add exclusion rule
exdate(date: Date): RRuleSet
Add exclusion date
all(limit?: number): Date[]
Returns all dates
between(after: Date, before: Date, includeEnds?: boolean): Date[]
Returns dates within range
first(): Date | null
Returns first date
iterator(): Generator<Date>
Returns lazy evaluation generator
🏗️ Architecture
Input RRULE String
↓
[Tokenizer] ← ReDoS prevention (Scanner approach)
↓
[Parser] ← Allowlist validation
↓
[RRuleOptions] ← LRU cache
↓
[Iterator] ← Lazy evaluation (Generator)
↓
[SafetyController] ← Resource limits
↓
Output Date[]📊 Performance
- Bundle Size:
- Full bundle: 5.5 KB (gzipped) / 19 KB (minified)
- Core-only: 4.4 KB (gzipped) / 16 KB (minified) - 20% smaller
- Memory: O(1) - Generator pattern usage
- Parsing: LRU cache minimizes repeated parsing
- Computation: Early exit prevents unnecessary calculations
- Security: Input validation and timeout ensure safety
🧪 Testing
# Run all tests
bun test
# Specific test file
bun test tests/rrule.test.ts
# Build
bun run buildTest Coverage:
- ✅ Basic recurrence patterns
- ✅ Advanced filters (BYSETPOS, BYYEARDAY, BYWEEKNO)
- ✅ RRuleSet combinations
- ✅ Leap year logic
- ✅ Security features
- ✅ Natural language conversion
📝 Supported RFC 5545 Features
| Feature | Support | Description | |---------|---------|-------------| | FREQ | ✅ | 7 frequencies (YEARLY ~ SECONDLY) | | DTSTART | ✅ | Start date | | UNTIL | ✅ | End date | | COUNT | ✅ | Repetition count | | INTERVAL | ✅ | Interval | | BYDAY | ✅ | Day of week filter | | BYMONTH | ✅ | Month filter | | BYMONTHDAY | ✅ | Day of month filter | | BYHOUR | ✅ | Hour filter | | BYMINUTE | ✅ | Minute filter | | BYSECOND | ✅ | Second filter | | BYSETPOS | ✅ | Position selection | | BYYEARDAY | ✅ | Day of year | | BYWEEKNO | ✅ | ISO week number | | WKST | ✅ | Week start day | | TZID | ⚠️ | Basic support (storage/access only) | | RRULE | ✅ | Inclusion rule | | RDATE | ✅ | Inclusion date | | EXRULE | ✅ | Exclusion rule | | EXDATE | ✅ | Exclusion date |
🔧 Development
# Install dependencies
bun install
# Development mode
bun run build:watch
# Test watch mode
bun test --watch
# Type check
bunx tsc --noEmit
# Build
bun run build📄 License
MIT License - Feel free to use!
🤝 Contributing
Issues and PRs are welcome!
🙏 Credits
This project references features from jkbrzt/rrule with enhanced security and performance.
