npm package: @finds-days/rrule-recurrence
Description: A library for generating and validating recurrence rules using rrule and plugins for different versions
Author: Elias Leyton
Latest version: 1.0.0

# Recurrence Rules Schema

This project implements a plugin-based recurrence rule system based on [rrule.js](https://github.com/jakubroztocil/rrule) 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

```typescript
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

```typescript
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

```typescript
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)

```typescript
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

```typescript
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

```typescript
setExcludeDays(days: Date[]): void
```

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

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

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

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

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

#### Utility Methods

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

Prints all generated dates to the console for debugging purposes.

```typescript
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:**
```typescript
// 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

```typescript
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

```bash
npm install
```

## Basic Usage

```typescript
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:
   ```bash
   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:

```typescript
import { getSupportedVersions } from './index';

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

## Testing

Run the test suite:

```bash
npm test
```

For watch mode during development:

```bash
npm run test:watch
```

## Dependencies

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

## License

MIT