npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@volt-package/rrule

v0.1.0

Published

A secure and optimized RRULE (RFC 5545) parser with leap year correction support

Readme

@volt-package/rrule

License: MIT

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 input

2. Input Validation

// Input length limit
const rrule = new RRule('FREQ=DAILY...', {
  maxInputLength: 2000 // default
});

// Allowlist for RFC 5545 standard keywords only

3. 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 build

Test 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.

📚 References