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

@finds-days/rrule-recurrence

v1.0.0

Published

A library for generating and validating recurrence rules using rrule and plugins for different versions

Readme

Recurrence Rules Schema

This project implements a plugin-based recurrence rule system based on rrule.js with schema versioning support. The system uses a plugin architecture to handle different versions of recurrence rules, making it easy to add new versions and maintain backward compatibility.

Features

  • Plugin-based architecture for version management
  • Generation of recurring dates (daily, weekly, monthly, etc.)
  • Timezone and local time adjustment
  • Specific date exclusion
  • Specific date replacement
  • Time offset application
  • Automatic version detection and processing
  • Schema validation and migration
  • Comprehensive date manipulation capabilities

CustomRecurrenceRule Class - Complete Functionality

The CustomRecurrenceRule class is the core component that provides advanced recurrence rule functionality with extensive customization options.

Constructor and Initialization

constructor(options: RecurrenceOptions)

The constructor accepts a RecurrenceOptions object with the following properties:

  • freq (required): Frequency of recurrence (DAILY, WEEKLY, MONTHLY, YEARLY)
  • interval (optional): Interval between recurrences (default: 1)
  • dtstart (required): Start date for the recurrence
  • until (optional): End date for the recurrence. If not specified, automatically sets to one year from dtstart
  • tzid (optional): Timezone identifier
  • byweekday (optional): Array of weekdays for weekly recurrence
  • excludeDates (optional): Specific dates to exclude
  • excludeDays (optional): Days to exclude (date only, no time)
  • keepLocalTime (optional): Whether to keep local time when changing timezones (default: true)
  • targetTimezone (optional): Target timezone for date generation
  • setTime (optional): Time options to set for all generated dates
  • replaceDates (optional): Array of date replacements
  • timeOffset (optional): Time offset to apply to all dates

Core Methods

Date Generation

getAllDates(targetTimezone?: string, keepLocalTime?: boolean): Date[]

Generates all recurring dates according to the rule configuration. This method:

  1. Applies timezone conversion to all generated dates
  2. Sets specific times if configured via setTime
  3. Applies time offsets (hours, minutes, seconds)
  4. Filters out excluded days (comparing by date only)
  5. Performs date replacements for specific dates
  6. Returns JavaScript Date objects in the target timezone

Time Manipulation

setTimeForDates(timeOptions: TimeOptions): void

Sets a specific time for all generated dates. TimeOptions includes:

  • hour: Hour (0-23)
  • minute: Minute (0-59)
  • second: Second (0-59)
applyTimeOffset(offset: TimeOffset): void

Applies a time offset to all generated dates. TimeOffset includes:

  • hours: Hours to add/subtract
  • minutes: Minutes to add/subtract
  • seconds: Seconds to add/subtract

Date Replacement

replaceDates(replacements: DateReplacement[]): void

Replaces specific dates with new dates. DateReplacement includes:

  • date: Original date to replace
  • newDate: New date to use instead

Exclusion Management

setExcludeDays(days: Date[]): void

Sets days to exclude from the recurrence (date only, no time consideration).

addExcludeDay(year: number, month: number, day: number): void

Adds a single day to exclude, creating the date in the target timezone.

addExcludeDays(days: Array<{year: number, month: number, day: number}>): void

Adds multiple days to exclude using year/month/day objects.

Utility Methods

printAllDates(targetTimezone?: string, keepLocalTime?: boolean): void

Prints all generated dates to the console for debugging purposes.

getConfig(): any

Returns the current configuration for debugging, including:

  • Rule options from the underlying RRule
  • Excluded dates
  • Timezone settings
  • Time adjustments
  • Date replacements
  • Time offsets
  • Excluded days

Advanced Features

Until Parameter Behavior

The until parameter has intelligent default behavior:

  • When specified: Uses the exact date provided as the end date
  • When not specified: Automatically sets to one year from the start date (dtstart)

This prevents the generation of excessive dates (previously up to year 9999) and provides a reasonable default range.

Example:

// Without until - automatically limits to 1 year
const recurrence1 = new CustomRecurrenceRule({
    freq: RRule.DAILY,
    dtstart: datetime(2025, 4, 1, 10, 0, 0)
    // until automatically set to 2026-04-01
});

// With explicit until
const recurrence2 = new CustomRecurrenceRule({
    freq: RRule.DAILY,
    dtstart: datetime(2025, 4, 1, 10, 0, 0),
    until: datetime(2025, 4, 10, 10, 0, 0) // Only 10 days
});

Schema Compatibility and Migration

The class automatically handles schema compatibility:

  • Version Detection: Checks if the provided options are compatible with the current schema
  • Automatic Migration: Migrates incompatible schemas to the current version
  • Validation: Validates all options before processing

Timezone Handling

  • Target Timezone: All dates are generated in the specified target timezone
  • Local Time Preservation: Option to preserve local time when changing timezones
  • Timezone-Aware Date Creation: Helper methods create dates directly in the target timezone

Date Processing Pipeline

The getAllDates() method processes dates through a sophisticated pipeline:

  1. Base Generation: Uses rrule.js to generate base recurring dates
  2. Timezone Conversion: Converts all dates to the target timezone
  3. Time Adjustment: Applies specific time settings if configured
  4. Offset Application: Adds/subtracts time offsets
  5. Exclusion Filtering: Removes excluded days
  6. Date Replacement: Replaces specific dates with new ones
  7. Final Conversion: Returns JavaScript Date objects

Example Usage

import { CustomRecurrenceRule } from './plugins/1.1.0/CRRule';
import { datetime, RRule } from 'rrule';

// Create a daily recurrence rule
const options = {
    freq: RRule.DAILY,
    interval: 2,
    dtstart: datetime(2025, 4, 1, 10, 0, 0),
    // until: datetime(2025, 4, 30, 10, 0, 0), // Optional: if not specified, defaults to 1 year from dtstart
    tzid: "America/Santiago",
    targetTimezone: "America/Santiago",
    keepLocalTime: true
};

const recurrence = new CustomRecurrenceRule(options);

// Set a specific time for all dates
recurrence.setTimeForDates({ hour: 14, minute: 30 });

// Apply a time offset
recurrence.applyTimeOffset({ hours: 2 });

// Exclude specific days
recurrence.addExcludeDay(2025, 3, 15); // April 15th (month is 0-based)
recurrence.addExcludeDay(2025, 3, 22); // April 22nd

// Replace a specific date
recurrence.replaceDates([
    {
        date: datetime(2025, 4, 13, 10, 0, 0),
        newDate: datetime(2025, 4, 13, 16, 0, 0)
    }
]);

// Get all dates
const dates = recurrence.getAllDates();
console.log('Generated dates:', dates);

// Print for debugging
recurrence.printAllDates();

// Get configuration for debugging
console.log('Configuration:', recurrence.getConfig());

Project Structure

  • index.ts: Main entry point with plugin management and version dispatch
  • plugins/: Directory containing version-specific implementations
    • 1.0.0/: Version-specific plugin implementation
    • 1.1.0/: Version-specific plugin implementation with enhanced features
  • example.ts: Example usage of the recurrence system
  • schema-cli.ts: CLI for managing the schema from the terminal
  • jest.config.js: Jest configuration for testing
  • tsconfig.json: TypeScript configuration
  • scripts/: Directory containing utility scripts
    • new-plugin.js: Script for creating new plugin versions

Installation

npm install

Basic Usage

import { processData } from './index';
import { datetime, RRule } from 'rrule';

const config = {
    freq: RRule.DAILY,
    interval: 2,
    dtstart: datetime(2025, 4, 1, 10, 0, 0),
    until: datetime(2025, 4, 30, 10, 0, 0),
    tzid: "America/Santiago",
    excludeDates: [
        datetime(2025, 4, 19, 10, 0, 0)
    ],
    keepLocalTime: true,
    targetTimezone: "America/Santiago",
    timeOffset: { hours: 2 },
    replaceDates: [
        {
            date: datetime(2025, 4, 13, 10, 0, 0),
            newDate: datetime(2025, 4, 13, 14, 20, 0)
        }
    ]
};

try {
    const recurrence = processData("1.0.1", config);
    recurrence.printAllDates();

    /*
    2025-04-01T15:00:00.000Z
    2025-04-03T15:00:00.000Z
    2025-04-05T15:00:00.000Z
    2025-04-07T16:00:00.000Z
    2025-04-09T16:00:00.000Z
    2025-04-11T16:00:00.000Z
    2025-04-13T14:20:00.000Z
    2025-04-15T16:20:00.000Z
    2025-04-17T16:00:00.000Z
    2025-04-21T16:00:00.000Z
    2025-04-23T16:00:00.000Z
    2025-04-25T16:00:00.000Z
    2025-04-27T16:00:00.000Z
    2025-04-29T16:00:00.000Z
    */
    
} catch (error) {
    console.error(error);
}

Plugin System

The project uses a plugin-based architecture to handle different versions of recurrence rules. Each version is implemented as a separate plugin in the plugins directory. The system automatically discovers and loads plugins at runtime.

Plugin Structure

Each plugin version should be placed in its own directory under plugins/ with the following structure:

plugins/
  └── 1.0.1/
      ├── CRRule.ts
      └── RecurrenceSchema.ts

Adding a New Version

  1. Create a new version using one of the following commands:

    npm run new-plugin:major  # For major version changes
    npm run new-plugin:minor  # For minor version changes
    npm run new-plugin:patch  # For patch version changes

    This will create a new directory under plugins/ with the next version number and copy all files from the latest version.

  2. Implement the version-specific logic in CRRule.ts and RecurrenceSchema.ts

Version Management

The system automatically handles version management through the plugin system. To get all supported versions:

import { getSupportedVersions } from './index';

const versions = getSupportedVersions();
console.log('Supported versions:', versions);

Testing

Run the test suite:

npm test

For watch mode during development:

npm run test:watch

Dependencies

  • rrule: For recurrence rule generation
  • luxon: For timezone handling
  • jest: For testing

License

MIT