@finds-days/rrule-recurrence
v1.0.0
Published
A library for generating and validating recurrence rules using rrule and plugins for different versions
Maintainers
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 recurrenceuntil(optional): End date for the recurrence. If not specified, automatically sets to one year fromdtstarttzid(optional): Timezone identifierbyweekday(optional): Array of weekdays for weekly recurrenceexcludeDates(optional): Specific dates to excludeexcludeDays(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 generationsetTime(optional): Time options to set for all generated datesreplaceDates(optional): Array of date replacementstimeOffset(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:
- Applies timezone conversion to all generated dates
- Sets specific times if configured via
setTime - Applies time offsets (hours, minutes, seconds)
- Filters out excluded days (comparing by date only)
- Performs date replacements for specific dates
- Returns JavaScript Date objects in the target timezone
Time Manipulation
setTimeForDates(timeOptions: TimeOptions): voidSets a specific time for all generated dates. TimeOptions includes:
hour: Hour (0-23)minute: Minute (0-59)second: Second (0-59)
applyTimeOffset(offset: TimeOffset): voidApplies a time offset to all generated dates. TimeOffset includes:
hours: Hours to add/subtractminutes: Minutes to add/subtractseconds: Seconds to add/subtract
Date Replacement
replaceDates(replacements: DateReplacement[]): voidReplaces specific dates with new dates. DateReplacement includes:
date: Original date to replacenewDate: New date to use instead
Exclusion Management
setExcludeDays(days: Date[]): voidSets days to exclude from the recurrence (date only, no time consideration).
addExcludeDay(year: number, month: number, day: number): voidAdds a single day to exclude, creating the date in the target timezone.
addExcludeDays(days: Array<{year: number, month: number, day: number}>): voidAdds multiple days to exclude using year/month/day objects.
Utility Methods
printAllDates(targetTimezone?: string, keepLocalTime?: boolean): voidPrints all generated dates to the console for debugging purposes.
getConfig(): anyReturns 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:
- Base Generation: Uses rrule.js to generate base recurring dates
- Timezone Conversion: Converts all dates to the target timezone
- Time Adjustment: Applies specific time settings if configured
- Offset Application: Adds/subtracts time offsets
- Exclusion Filtering: Removes excluded days
- Date Replacement: Replaces specific dates with new ones
- 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 dispatchplugins/: Directory containing version-specific implementations1.0.0/: Version-specific plugin implementation1.1.0/: Version-specific plugin implementation with enhanced features
example.ts: Example usage of the recurrence systemschema-cli.ts: CLI for managing the schema from the terminaljest.config.js: Jest configuration for testingtsconfig.json: TypeScript configurationscripts/: Directory containing utility scriptsnew-plugin.js: Script for creating new plugin versions
Installation
npm installBasic 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.tsAdding a New Version
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 changesThis will create a new directory under
plugins/with the next version number and copy all files from the latest version.Implement the version-specific logic in
CRRule.tsandRecurrenceSchema.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 testFor watch mode during development:
npm run test:watchDependencies
- rrule: For recurrence rule generation
- luxon: For timezone handling
- jest: For testing
License
MIT
