# Schedule Date Calculator

[![npm version](https://img.shields.io/npm/v/@laxmandarji/schedule-date-calculator.svg)](https://www.npmjs.com/package/@laxmandarji/schedule-date-calculator)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A powerful JavaScript/TypeScript library for calculating eligible run dates based on complex scheduling rules. Perfect for job schedulers, task automation, and business calendar management.

## What's New in v4

### Version 4.0.0
- Added "WORKDAYS" keyword support in MONTHDAYS rules to easily include or exclude all working days
- Improved exclusion rule handling - exclusions now take precedence and are evaluated first
- Enhanced performance by processing exclusions before inclusions
- Fixed edge cases in weekday and monthday rule evaluation
- Breaking change: Changed behavior of exclusion rules (-) to take precedence over inclusion rules

## Features

- 📅 Define custom business calendars with working days and holidays
- 🔄 Flexible scheduling patterns (daily, weekly, monthly)
- 🎯 Advanced modifiers for precise scheduling control
- 📊 Support for complex business logic
- ⚡ Efficient date calculations with caching
- 🛡️ Comprehensive validation and error handling
- 📘 Full TypeScript support
- 🚀 Optimized performance
- 🧪 Modular architecture

## Installation

```bash
npm install @laxmandarji/schedule-date-calculator
```

## Usage

The library supports both CommonJS and ES Modules:

```javascript
// CommonJS
const { ScheduleConfig } = require('@laxmandarji/schedule-date-calculator');

// ES Modules
import { ScheduleConfig } from '@laxmandarji/schedule-date-calculator';
```

## Configuration

Create a new schedule configuration:

```javascript
const config = new ScheduleConfig({
    CALENDARS: {
        WORKWEEK: {
            WORKDAYS: [1, 2, 3, 4, 5], // Mon-Fri
            HOLIDAYS: ["2024-12-25", "2024-01-01"]
        }
    },
    WEEKDAYS_CALENDAR: "WORKWEEK",
    WEEKDAYS: ["D1", "D2"], // First and second working days
    MONTHS: ["JAN", "FEB", "MAR"] // First quarter
});
```

### Configuration Options

- `CALENDARS`: Object containing named calendars with workdays and holidays
  - `WORKDAYS`: Array of working days (0-6, where 0 is Sunday)
  - `HOLIDAYS`: Array of holiday dates in "YYYY-MM-DD" format
- `WEEKDAYS_CALENDAR`: Name of the calendar to use for weekday calculations
- `WEEKDAYS`: Array of weekday specifications
- `WEEK_MONTH_RELATION`: Relationship between week and month rules ("AND" or "OR", defaults to "OR")
- `MONTH_CALENDAR`: Name of the calendar to use for monthday calculations
- `MONTHDAYS`: Array of monthday specifications
- `MONTHS`: Array of month names (e.g., ["JAN", "FEB", "MAR"])

## Methods

### change(config)

Completely replaces the current configuration with a new one. All properties are reset to their default values if not specified in the new configuration.

```javascript
config.change({
    CALENDARS: {
        NEWCAL: {
            WORKDAYS: [1, 2, 3], // Mon-Wed
            HOLIDAYS: ["2024-12-25"]
        }
    },
    WEEKDAYS: ["D1"], // First working day only
    MONTHS: ["JAN"] // January only
});
```

### update(newConfig)

Updates specific properties of the configuration while keeping others unchanged.

```javascript
config.update({
    WEEKDAYS: ["D1", "D2"], // Only updates WEEKDAYS
    MONTHS: ["JAN", "FEB"] // Only updates MONTHS
});
```

### addCalendar(calendarName, workdays, holidays)

Adds or updates a calendar in the configuration.

```javascript
config.addCalendar(
    "CUSTOM_CAL",
    [1, 2, 3, 4, 5], // Mon-Fri
    ["2024-12-25", "2024-01-01"]
);
```

### isDateEligible(date)

Checks if a specific date is eligible according to the configuration.

```javascript
const date = new Date('2024-01-02');
const isEligible = config.isDateEligible(date);
```

### getEligibleDates(year)

Gets all eligible dates for a specific year.

```javascript
const dates = config.getEligibleDates(2024);
```

### validate()

Validates the current configuration.

```javascript
const validation = config.validate();
if (!validation.isValid) {
    console.error('Configuration errors:', validation.errors);
}
```

## Examples

### Basic Usage

```javascript
const config = new ScheduleConfig({
    CALENDARS: {
        WORKWEEK: {
            WORKDAYS: [1, 2, 3, 4, 5], // Mon-Fri
            HOLIDAYS: ["2024-12-25"]
        }
    },
    WEEKDAYS_CALENDAR: "WORKWEEK",
    WEEKDAYS: ["D1"], // First working day
    MONTHS: ["JAN", "FEB"] // Jan-Feb only
});

// Get eligible dates for 2024
const dates = config.getEligibleDates(2024);
console.log('Eligible dates:', dates);
```

### Changing Configuration

```javascript
// Initial configuration
const config = new ScheduleConfig({
    CALENDARS: {
        WORKWEEK: {
            WORKDAYS: [1, 2, 3, 4, 5],
            HOLIDAYS: ["2024-12-25"]
        }
    }
});

// Complete configuration change
config.change({
    CALENDARS: {
        CUSTOM: {
            WORKDAYS: [1, 2, 3],
            HOLIDAYS: ["2024-01-01"]
        }
    },
    WEEKDAYS: ["D1"],
    MONTHS: ["JAN"]
});

// Partial update
config.update({
    WEEKDAYS: ["D1", "D2"]
});
```

## Configuration Guide

### Calendar Configuration

Define business calendars with working days and holidays:

```typescript
const config = new ScheduleConfig({
    CALENDARS: {
        US_BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5], // Monday-Friday
            HOLIDAYS: ["2025-01-01", "2025-12-25"]
        },
        ASIA_BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5, 6], // Monday-Saturday
            HOLIDAYS: ["2025-01-01"]
        }
    }
});
```

### Weekday Rules

Multiple formats for weekday scheduling:

```typescript
{
    WEEKDAYS_CALENDAR: "US_BUSINESS",
    WEEKDAYS: [
        "1",     // Every Monday
        ">3",    // Run on next working day if Wednesday is not a working day
        "<3",    // Run on previous working day if Wednesday is not a working day
        "+3",    // Force run on Wednesday (even if holiday)
        "-3",    // Never run on Wednesday
        "D2",    // 2nd working day of each week
        "D3W2"   // Wednesday of 2nd week
    ]
}
```

### Monthday Rules

Flexible monthly scheduling:

```typescript
{
    MONTH_CALENDAR: "US_BUSINESS",
    MONTHDAYS: [
        "WORKDAYS", // All working days
        "-WORKDAYS", // No working days
        "1",     // 1st day of month
        ">15",   // Run on next working day if 15th is not a working day
        "<15",   // Run on previous working day if 15th is not a working day
        "+15",   // Force run on 15th (even if holiday)
        "-15",   // Skip 15th
        "D5",    // 5th working day
        "L1",    // Last working day
        "L5"     // 5th to last working day
    ]
}
```

### Month Selection

Specify months to run:

```typescript
{
    MONTHS: ["JAN", "APR", "JUL", "OCT"], // Quarterly
    // or
    MONTHS: ["ALL"]  // Run every month
}
```

### Week-Month Relation

Control how weekday and monthday rules combine:

```typescript
{
    WEEK_MONTH_RELATION: "AND", // Must satisfy both conditions
    // or
    WEEK_MONTH_RELATION: "OR"   // Must satisfy either condition (default)
}
```

## Advanced Examples

### Working Days Only Schedule

```typescript
const config = new ScheduleConfig({
    CALENDARS: {
        BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5],
            HOLIDAYS: ["2025-01-01"]
        }
    },
    MONTH_CALENDAR: "BUSINESS",
    MONTHDAYS: ["WORKDAYS"],  // Run on all working days
    MONTHS: ["ALL"]
});
```

### Non-Working Days Only Schedule

```typescript
const config = new ScheduleConfig({
    CALENDARS: {
        BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5],
            HOLIDAYS: ["2025-01-01"]
        }
    },
    MONTH_CALENDAR: "BUSINESS",
    MONTHDAYS: ["-WORKDAYS", "15"],  // Run on non-working days and 15th
    MONTHS: ["ALL"]
});
```

### Complex Weekly Pattern

```typescript
const config = new ScheduleConfig({
    CALENDARS: {
        BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5],
            HOLIDAYS: ["2025-04-01"]
        }
    },
    WEEKDAYS_CALENDAR: "BUSINESS",
    WEEKDAYS: [
        "D2W1",  // Tuesday of first week
        "D4W3",  // Thursday of third week
        ">3"     // Next working day after Wednesday
    ],
    MONTHS: ["ALL"]
});
```

### Holiday-Aware Monthly Schedule

```typescript
const config = new ScheduleConfig({
    CALENDARS: {
        BUSINESS: {
            WORKDAYS: [1, 2, 3, 4, 5],
            HOLIDAYS: ["2025-04-01"]
        }
    },
    MONTH_CALENDAR: "BUSINESS",
    MONTHDAYS: [
        "WORKDAYS",  // All working days
        "-15",      // Except the 15th
        "L1"        // And include the last working day
    ],
    MONTHS: ["APR"]
});
```

## API Reference

### Namespaces

The library is organized into logical namespaces:

#### utils
```typescript
import { utils } from '@laxmandarji/schedule-date-calculator';

utils.validateConfig(config);
utils.formatDate(date);
utils.getWeekOfMonth(date);
```

#### dateCalculators
```typescript
import { dateCalculators } from '@laxmandarji/schedule-date-calculator';

dateCalculators.isWorkingDay(date, calendar);
dateCalculators.getNthWorkdayOfMonth(date, n, calendar);
dateCalculators.getLastNthWorkdayOfMonth(date, n, calendar);
```

#### evaluators
```typescript
import { evaluators } from '@laxmandarji/schedule-date-calculator';

evaluators.evaluateWeekdays(date, config, calendars);
evaluators.evaluateMonthdays(date, config, calendars);
```

### ScheduleConfig Class

#### Constructor
```typescript
const config = new ScheduleConfig(configObject);
```

#### Methods

##### isDateEligible(date)
```typescript
const date = new Date(2025, 3, 16);
const isEligible = config.isDateEligible(date);
```

##### getEligibleDates(year)
```typescript
const dates = config.getEligibleDates(2025);
```

##### validate()
```typescript
const validation = config.validate();
if (!validation.isValid) {
    console.error(validation.errors);
}
```

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## Support

For support, please create an issue in the [GitHub repository](https://github.com/laxmandarji/schedule-date-calculator/issues).

## Author

Laxman Darji 