npm package: 24x7
Description: A production-ready date/time library for JavaScript with first-class intervals, business-aware date math, timezone support, and deterministic time control
Author: Venkatesh Gandham
Latest version: 1.0.0

# 24x7

A production-ready date/time library for JavaScript with unique features for modern applications.

## Why 24x7 Exists

24x7 was created to provide a modern, production-ready date/time library with unique features:

1. **First-class intervals** - Work with time intervals as first-class objects with end-exclusive behavior
2. **First-class date ranges** - Work with date ranges as first-class objects, not just calculations
3. **Business-aware date math** - Handle business days, holidays, and custom weekends with ease
4. **Deterministic time control** - Freeze time for predictable testing without global side effects
5. **Timezone support** - Full timezone support with UTC default and IANA timezone support
6. **Global configuration** - Set defaults for formats, weekends, holidays, and more
7. **Flexible parsing** - Support for custom date formats with automatic ISO detection

## Installation

```bash
npm install 24x7
```

## Quick Start

```javascript
import _tx from '24x7';

// Create a date
const date = _tx('2025-01-15');

// Chain operations (all immutable)
const nextWeek = date.addDays(7).startOfDay();

// Format
console.log(nextWeek.format('YYYY-MM-DD')); // '2025-01-22'
```

## Configuration

24x7 supports comprehensive global configuration with a structured, hierarchical system. Configuration follows a priority system:

1. **Instance overrides** (highest priority)
2. **Global config** (set via `_tx.config()`)
3. **DEFAULT_CONFIG** (lowest priority)

### Basic Usage

```javascript
import _tx from '24x7';

// Get current configuration
const config = _tx.config();

// Set configuration options
_tx.config({
  immutable: true,
  parsing: {
    inputFormats: ['DD/MM/YYYY', 'YYYY-MM-DD'],
    strict: false,
    safe: true
  },
  formatting: {
    outputFormat: 'YYYY-MM-DD'
  },
  calendar: {
    weekends: [0, 6],
    holidays: ['2025-01-15']
  }
});

// Reset to defaults
_tx.resetConfig();
```

### Configuration Structure

#### Immutability

```javascript
_tx.config({
  immutable: true,          // Instances are immutable by default
  mutableFallback: false    // Allow mutation only when explicitly enabled
});
```

**Usage:**
```javascript
// Default: immutable (returns new instance)
const date1 = _tx('2025-01-15');
const date2 = date1.addDays(5); // date1 unchanged

// Explicitly enable mutation
const date3 = _tx('2025-01-15').immutable(false);
date3.addDays(5); // date3 is mutated in place
```

#### Parsing Configuration

```javascript
_tx.config({
  parsing: {
    inputFormats: ['DD/MM/YYYY', 'YYYY-MM-DD'], // Priority order
    strict: false,          // Reject invalid dates
    safe: true,             // Do not throw on invalid input
    overflow: 'reject',     // 'reject' | 'clamp'
    yearPivot: 50,          // Two-digit year pivot (50 = 1950-2049)
    monthNames: null,       // Custom month names
    allowNative: true,      // Fallback to native Date parsing
    trimInput: true         // Trim input strings
  }
});
```

**Examples:**
```javascript
// Use inputFormats for automatic parsing
_tx.config({
  parsing: {
    inputFormats: ['DD/MM/YYYY', 'MM/DD/YYYY']
  }
});

x('23/01/2025'); // Parsed as DD/MM/YYYY
x('01/23/2025'); // Parsed as MM/DD/YYYY

// Strict mode rejects invalid dates
_tx.config({
  parsing: {
    strict: true
  }
});

// Safe mode returns current date instead of throwing
_tx.config({
  parsing: {
    safe: true
  }
});
x('invalid'); // Returns current date instead of throwing
```

#### Formatting Configuration

```javascript
_tx.config({
  formatting: {
    outputFormat: 'YYYY-MM-DD',
    
    tokens: {},             // Custom format tokens
    
    ordinal: n => `${n}th`, // Custom ordinal function
    
    pad: {
      year: 4,
      month: 2,
      day: 2,
      hour: 2,
      minute: 2,
      second: 2
    },
    
    rules: {
      sameDay: 'HH:mm',     // Format for same day
      sameYear: 'MMM D',    // Format for same year
      default: 'YYYY-MM-DD' // Default format
    }
  }
});
```

**Examples:**
```javascript
// Set default output format
_tx.config({
  formatting: {
    outputFormat: 'MMMM D, YYYY'
  }
});

x('2025-01-15').format(); // "January 15, 2025"

// Custom tokens
_tx.config({
  formatting: {
    tokens: {
      'Q': (date) => Math.floor(date.month() / 3) + 1
    }
  }
});

x('2025-01-15').format('YYYY-Q'); // "2025-1"
```

#### Calendar / Business Logic

```javascript
_tx.config({
  calendar: {
    weekends: [0, 6],       // Sunday, Saturday
    weekStart: 1,           // Monday (0=Sunday, 1=Monday, etc.)
    holidays: ['2025-01-15', '2025-12-25'], // ISO date strings
    
    businessHours: {
      start: '09:00',
      end: '17:00'
    },
    
    includeHolidaysInRanges: false
  }
});
```

**Examples:**
```javascript
// Set global weekends and holidays
_tx.config({
  calendar: {
    weekends: [0, 6],
    holidays: ['2025-01-15', '2025-12-25']
  }
});

// Business day methods use config defaults
x('2025-01-10').addBusinessDays(5); // Uses config weekends/holidays

// Override per call
x('2025-01-10').addBusinessDays(5, {
  weekends: [5, 6], // Override config
  holidays: ['2025-01-20'] // Override config
});
```

#### Date Math Configuration

```javascript
_tx.config({
  math: {
    clampMode: 'end',       // 'end' | 'strict' | 'overflow'
    diffRounding: 'floor'   // 'floor' | 'ceil' | 'round'
  }
});
```

#### Time / Clock Control

```javascript
_tx.config({
  time: {
    timezone: 'UTC',        // 'UTC' | 'local' | IANA timezone (e.g., 'America/New_York')
    precision: 'ms',        // 'ms' | 's'
    freeze: false,          // false | Date | ISO string
    dst: 'preserve',        // 'preserve' | 'shift' | 'error'
    startOfDay: '00:00'
  }
});
```

**Examples:**
```javascript
// Set default timezone to UTC (default)
_tx.config({
  time: {
    timezone: 'UTC'
  }
});

// Set default timezone to local
_tx.config({
  time: {
    timezone: 'local'
  }
});

// Set default timezone to IANA timezone
_tx.config({
  time: {
    timezone: 'America/New_York'
  }
});

// Freeze time
_tx.config({
  time: {
    freeze: '2025-01-15' // Freeze to specific date
  }
});

// Or use the convenience method
_tx.freeze('2025-01-15');
```

#### Debugging Configuration

```javascript
_tx.config({
  debug: {
    warnings: false,
    onWarn: (message, context) => {
      console.warn(message, context);
    }
  }
});
```

#### Performance Configuration

```javascript
_tx.config({
  performance: {
    cacheFormats: true,     // Cache compiled format patterns
    fastParse: false        // Use faster but less strict parsing
  }
});
```

#### Policy / Guards

```javascript
_tx.config({
  policy: {
    maxFuture: '2y',        // Maximum future date (e.g., '2y', '1M')
    maxPast: '10y',         // Maximum past date
    forbidWeekends: false,  // Reject weekend dates
    forbidHolidays: false   // Reject holiday dates
  }
});
```

#### Intervals / Ranges Configuration

```javascript
_tx.config({
  intervals: {
    inclusive: false,       // End-exclusive by default
    allowZeroDuration: false,
    splitRemainder: true,   // Keep leftover when splitting
    defaultSplitUnit: 'minute',
    overlap: {
      inclusive: false      // End === start is NOT overlap
    },
    businessOnly: false     // Ignore non-business time by default
  }
});
```

**Examples:**
```javascript
// End-exclusive intervals (default)
_tx.config({
  intervals: {
    inclusive: false
  }
});
const interval = _tx('2025-01-01').interval('2025-01-10');
interval.contains('2025-01-10'); // false (end-exclusive)

// End-inclusive intervals
_tx.config({
  intervals: {
    inclusive: true
  }
});
const inclusiveInterval =_tx('2025-01-01').interval('2025-01-10');
inclusiveInterval.contains('2025-01-10'); // true (end-inclusive)

// Allow zero-duration intervals
_tx.config({
  intervals: {
    allowZeroDuration: true
  }
});
const zeroInterval =_tx('2025-01-01').interval('2025-01-01'); // Allowed
```

### Instance-Level Configuration

You can override configuration per instance:

```javascript
// Create instance with custom config
const date =_tx('2025-01-15', {
  immutable: false,
  calendar: {
    weekends: [5, 6] // Friday-Saturday weekend
  }
});

// Or modify immutability after creation
const mutableDate =_tx('2025-01-15').immutable(false);
mutableDate.addDays(5); // Mutates in place
```

### Configuration Resolution

Configuration is resolved with the following priority:

1. **Instance overrides** - Passed when creating DateTime or via `.immutable()`
2. **Global config** - Set via `_tx.config()`
3. **DEFAULT_CONFIG** - Built-in defaults

```javascript
// Global config
_tx.config({
  calendar: {
    weekends: [0, 6]
  }
});

// Instance override
const date =_tx('2025-01-15', {
  calendar: {
    weekends: [5, 6] // This instance uses Friday-Saturday
  }
});

// Method-level override (highest priority)
date.addBusinessDays(5, {
  weekends: [1, 2] // This call uses Monday-Tuesday
});
```

## Core Features

### Immutable API (Default)

By default, all operations return new instances. The original date is never modified:

```javascript
const date1 =_tx('2025-01-15');
const date2 = date1.addDays(5);

console.log(date1.format('YYYY-MM-DD')); // '2025-01-15' (unchanged)
console.log(date2.format('YYYY-MM-DD')); // '2025-01-20'
```

### Mutable Mode (Explicit)

You can enable mutation for specific instances:

```javascript
// Enable mutation explicitly
const date =_tx('2025-01-15').immutable(false);
date.addDays(5); // Mutates date in place

// Check immutability
date.isImmutable(); // false

// Create mutable instance from config
const mutableDate =_tx('2025-01-15', { immutable: false });
mutableDate.addDays(5); // Mutates in place
```

**Note:** Immutability is the default and recommended mode. Use mutable mode only when explicitly needed for performance or specific use cases.

### Chainable Methods

Methods can be chained for fluent APIs:

```javascript
const result =_tx('2025-01-15')
  .addMonths(1)
  .startOfMonth()
  .addDays(2)
  .format('YYYY-MM-DD');
```

## Unique Features

### 1️⃣ Intervals (First-class)

Work with time intervals as first-class objects with end-exclusive behavior by default:

#### Creating Intervals

```javascript
import _tx from '24x7';

// Create an interval (end-exclusive by default)
const interval =_tx('2025-01-01').interval('2025-01-10');

// Static method (alternative)
const staticInterval = _tx.interval('2025-01-01', '2025-01-10');

// End-exclusive: [start, end) - end is NOT included
const exclusive =_tx('2025-01-01').interval('2025-01-10');
exclusive.contains('2025-01-01'); // true
exclusive.contains('2025-01-10'); // false (end is exclusive)
```

#### Interval Configuration

```javascript
_tx.config({
  intervals: {
    inclusive: false,       // End-exclusive by default
    allowZeroDuration: false,
    splitRemainder: true,   // Keep leftover when splitting
    defaultSplitUnit: 'minute',
    overlap: {
      inclusive: false      // End === start is NOT overlap
    },
    businessOnly: false     // Ignore non-business time by default
  }
});
```

#### Checking if Date or Interval is Contained

```javascript
import _tx from '24x7';

const interval =_tx('2025-01-01').interval('2025-01-10');

// Check if date is in interval
interval.contains('2025-01-05'); // true
interval.contains('2025-01-10'); // false (end-exclusive)

// Check if another interval is contained
const large =_tx('2025-01-01').interval('2025-01-31');
const small =_tx('2025-01-10').interval('2025-01-20');
large.contains(small); // true
```

#### Overlap Detection

```javascript
import _tx from '24x7';

const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');
const interval3 =_tx('2025-01-20').interval('2025-01-25');

interval1.overlaps(interval2); // true
interval1.overlaps(interval3); // false

// With inclusive overlap option
interval1.overlaps(interval2, { inclusive: true });
```

#### Intersection

```javascript
import _tx from '24x7';

const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');

const intersection = interval1.intersect(interval2);
if (intersection) {
  console.log(intersection.start().format('YYYY-MM-DD')); // '2025-01-05'
  console.log(intersection.end().format('YYYY-MM-DD'));   // '2025-01-10'
}
```

#### Clamping

```javascript
import _tx from '24x7';

const container =_tx('2025-01-01').interval('2025-01-31');
const toClamp =_tx('2024-12-25').interval('2025-02-05');

const clamped = container.clamp(toClamp);
// Clamped to fit within container: 2025-01-01 to 2025-01-31
```

#### Splitting Intervals

```javascript
import _tx from '24x7';

const interval =_tx('2025-01-01T00:00:00').interval('2025-01-01T02:30:00');

// Split by unit
const hourIntervals = interval.splitBy('hour');
// Returns array of 1-hour intervals

// Split by duration (milliseconds)
const customIntervals = interval.splitBy(30 * 60 * 1000); // 30 minutes
```

#### Iterating Over Intervals

```javascript
import _tx from '24x7';

const interval =_tx('2025-01-01').interval('2025-01-05');

// Iterate by day
for (const day of interval.iterate('day')) {
  console.log(day.format('YYYY-MM-DD'));
}
// Output:
// 2025-01-01
// 2025-01-02
// 2025-01-03
// 2025-01-04
// (2025-01-05 is excluded - end-exclusive)
```

#### Duration Calculations

```javascript
import _tx from '24x7';

const interval =_tx('2025-01-01T10:00:00').interval('2025-01-01T14:30:00');

interval.duration();        // Duration in milliseconds (default)
interval.duration('s');     // Duration in seconds
interval.duration('minute'); // Duration in minutes
interval.duration('hour');  // Duration in hours
interval.duration('day');   // Duration in days
```

#### Merging Intervals

```javascript
import _tx from '24x7';

const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');
const interval3 =_tx('2025-01-20').interval('2025-01-25');

const merged = _tx.merge([interval1, interval2, interval3]);
// Returns interval from earliest start to latest end
// 2025-01-01 to 2025-01-25
```

#### Real-World Use Cases

```javascript
import _tx from '24x7';

// Use case 1: Time slot booking
function isTimeSlotAvailable(slot, bookedSlots) {
  return !bookedSlots.some(booked => slot.overlaps(booked));
}

// Use case 2: Calculate working hours in interval
function getWorkingHours(interval, businessHours) {
  // Filter to business hours only
  return interval.splitBy('hour').filter(hour => {
    const hourStart = hour.start().hour();
    return hourStart >= 9 && hourStart < 17;
  });
}

// Use case 3: Find gaps between intervals
function findGaps(intervals) {
  // Sort by start time
  const sorted = [...intervals].sort((a, b) => 
    a.start().getTime() - b.start().getTime()
  );
  
  const gaps = [];
  for (let i = 0; i < sorted.length - 1; i++) {
    const gap = sorted[i].end().interval(sorted[i + 1].start());
    if (gap.duration() > 0) {
      gaps.push(gap);
    }
  }
  return gaps;
}
```

### 2️⃣ Date Ranges (First-class)

Work with date ranges as first-class objects:

#### Creating Ranges

```javascript
import _tx from '24x7';

// Create a range from a date to another date
const range =_tx('2025-01-01').range('2025-01-10');

// Static method (alternative)
const staticRange = _tx.range('2025-01-01', '2025-01-10');

// Range with different input types
const range2 =_tx('2025-01-01').range(new Date('2025-01-10'));
const range3 =_tx(new Date('2025-01-01')).range('2025-01-10');

// Single day range
const singleDay =_tx('2025-01-15').range('2025-01-15');
```

#### Checking if Date is in Range

```javascript
import _tx from '24x7';

const range =_tx('2025-01-01').range('2025-01-10');

// Check with DateTime instance
range.contains(_tx('2025-01-05')); // true
range.contains(_tx('2025-01-15')); // false

// Check with string
range.contains('2025-01-05'); // true
range.contains('2025-01-01'); // true (inclusive start)
range.contains('2025-01-10'); // true (inclusive end)

// Check with Date object
range.contains(new Date('2025-01-05')); // true

// Real-world example: Check if today is in a promotion period
const promotionStart =_tx('2025-01-01');
const promotionEnd =_tx('2025-01-31');
const promotionRange = promotionStart.range(promotionEnd);
const isPromotionActive = promotionRange.contains(_tx()); // true if today is in range
```

#### Getting Range Information

```javascript
import _tx from '24x7';

const range =_tx('2025-01-01').range('2025-01-10');

// Get start and end dates
const start = range.start(); // DateTime instance
const end = range.end();     // DateTime instance

console.log(start.format('YYYY-MM-DD')); // '2025-01-01'
console.log(end.format('YYYY-MM-DD'));   // '2025-01-10'

// Get number of days (inclusive)
range.days(); // 10 (includes both start and end)

// Calculate duration in other units
const days = range.days();
const hours = days * 24;
const minutes = hours * 60;
```

#### Iterating Over Range

```javascript
import _tx from '24x7';

const range =_tx('2025-01-01').range('2025-01-05');

// Iterate with for...of
for (const day of range) {
  console.log(day.format('YYYY-MM-DD'));
}
// Output:
// 2025-01-01
// 2025-01-02
// 2025-01-03
// 2025-01-04
// 2025-01-05

// Convert to array
const daysArray = range.toArray();
console.log(daysArray.length); // 5

// Use array methods
const weekdays = range.toArray().filter(day => {
  const dayOfWeek = day.day();
  return dayOfWeek !== 0 && dayOfWeek !== 6; // Exclude weekends
});

// Map to formatted strings
const formattedDays = range.toArray().map(day => day.format('MMM D'));
console.log(formattedDays); // ['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5']

// Real-world example: Get all business days in a range
const businessDays = range.toArray().filter(day => {
  return day.isBusinessDay({ weekends: [0, 6] });
});
```

#### Range Overlap Detection

```javascript
import _tx from '24x7';

const range1 =_tx('2025-01-01').range('2025-01-10');
const range2 =_tx('2025-01-05').range('2025-01-15');
const range3 =_tx('2025-01-20').range('2025-01-25');

// Check if ranges overlap
range1.overlaps(range2); // true (overlaps from Jan 5-10)
range1.overlaps(range3); // false (no overlap)

// Real-world example: Check if booking periods conflict
const booking1 =_tx('2025-01-10').range('2025-01-15');
const booking2 =_tx('2025-01-12').range('2025-01-18');
const hasConflict = booking1.overlaps(booking2); // true

// Find overlapping period
if (booking1.overlaps(booking2)) {
  const overlapStart = booking1.start().isAfter(booking2.start()) 
    ? booking1.start() 
    : booking2.start();
  const overlapEnd = booking1.end().isBefore(booking2.end()) 
    ? booking1.end() 
    : booking2.end();
  const overlapRange = overlapStart.range(overlapEnd);
  console.log(`Overlap: ${overlapRange.days()} days`);
}
```

#### Real-World Use Cases

```javascript
import _tx from '24x7';

// Use case 1: Event date range validation
function isEventDateValid(eventStart, eventEnd, today = _tx()) {
  const eventRange = eventStart.range(eventEnd);
  return eventRange.contains(today) || eventRange.start().isAfter(today);
}

// Use case 2: Calculate working days in a project timeline
function getWorkingDays(startDate, endDate, holidays = []) {
  const range =_tx(startDate).range(endDate);
  return range.toArray().filter(day => {
    return day.isBusinessDay({ weekends: [0, 6], holidays });
  }).length;
}

// Use case 3: Check if two schedules conflict
function schedulesConflict(schedule1Start, schedule1End, schedule2Start, schedule2End) {
  const schedule1 =_tx(schedule1Start).range(schedule1End);
  const schedule2 =_tx(schedule2Start).range(schedule2End);
  return schedule1.overlaps(schedule2);
}
```

### 3️⃣ Business-Aware Date Math

Handle business days with custom weekends and holidays:

#### Adding Business Days

```javascript
import _tx from '24x7';

// Basic usage: Add 5 business days (skips weekends)
const result =_tx('2025-01-10') // Friday
  .addBusinessDays(5, {
    weekends: [0, 6] // Sunday and Saturday
  });
// Result: 2025-01-17 (Friday) - skips weekend

// With holidays
const result2 =_tx('2025-01-10')
  .addBusinessDays(5, {
    weekends: [0, 6],
    holidays: ['2025-01-15', '2025-01-20'] // Wednesday and Monday
  });
// Skips weekends AND holidays

// Custom weekends (e.g., Friday-Saturday weekend)
const result3 =_tx('2025-01-08') // Wednesday
  .addBusinessDays(3, {
    weekends: [5, 6] // Friday and Saturday
  });

// Real-world example: Calculate delivery date (5 business days)
const orderDate =_tx('2025-01-10');
const holidays = ['2025-01-15', '2025-01-20']; // Company holidays
const deliveryDate = orderDate.addBusinessDays(5, {
  weekends: [0, 6],
  holidays
});
console.log(`Order placed: ${orderDate.format('MMM D')}`);
console.log(`Expected delivery: ${deliveryDate.format('MMM D')}`);
```

#### Subtracting Business Days

```javascript
import _tx from '24x7';

// Subtract business days
const past =_tx('2025-01-20')
  .subtractBusinessDays(3, {
    weekends: [0, 6]
  });

// Real-world example: Calculate deadline (10 business days ago)
const deadline =_tx('2025-01-31')
  .subtractBusinessDays(10, {
    weekends: [0, 6],
    holidays: ['2025-01-15']
  });
console.log(`Deadline: ${deadline.format('MMM D')}`);
```

#### Checking Business Days

```javascript
import _tx from '24x7';

// Check if a date is a business day
x('2025-01-15').isBusinessDay({
  weekends: [0, 6],
  holidays: ['2025-01-15']
}); // false (it's a holiday)

x('2025-01-16').isBusinessDay({
  weekends: [0, 6],
  holidays: ['2025-01-15']
}); // true (Thursday, not a holiday)

x('2025-01-18').isBusinessDay({
  weekends: [0, 6]
}); // false (Saturday)

// Real-world example: Validate business day
function canProcessOrder(date, holidays = []) {
  return _tx(date).isBusinessDay({
    weekends: [0, 6],
    holidays
  });
}

if (canProcessOrder('2025-01-15', ['2025-01-15'])) {
  console.log('Order can be processed today');
} else {
  console.log('Order will be processed next business day');
}
```

#### Counting Business Days Between Dates

```javascript
import _tx from '24x7';

// Count business days between two dates
const count =_tx('2025-01-01')
  .businessDaysUntil('2025-01-31', {
    weekends: [0, 6],
    holidays: ['2025-01-15']
  });
// Returns 21 (excluding weekends and holidays)

// Forward count (positive)
x('2025-01-10').businessDaysUntil('2025-01-20', {
  weekends: [0, 6]
}); // 7 business days

// Backward count (negative)
x('2025-01-20').businessDaysUntil('2025-01-10', {
  weekends: [0, 6]
}); // -7 business days

// Real-world example: Calculate SLA remaining days
function getSLARemainingDays(startDate, endDate, holidays = []) {
  const today = _tx();
  const remaining = today.businessDaysUntil(endDate, {
    weekends: [0, 6],
    holidays
  });
  return Math.max(0, remaining); // Don't return negative
}

const startDate =_tx('2025-01-01');
const endDate =_tx('2025-01-31');
const remaining = getSLARemainingDays(startDate, endDate, ['2025-01-15']);
console.log(`${remaining} business days remaining`);
```

#### Custom Weekend Configurations

```javascript
import _tx from '24x7';

// Middle Eastern weekend (Friday-Saturday)
const result =_tx('2025-01-08') // Wednesday
  .addBusinessDays(3, {
    weekends: [5, 6] // Friday and Saturday
  });

// No weekends (7-day work week)
const result2 =_tx('2025-01-10')
  .addBusinessDays(5, {
    weekends: [] // No weekends
  });

// Single day weekend
const result3 =_tx('2025-01-10')
  .addBusinessDays(5, {
    weekends: [0] // Only Sunday
  });
```

#### Holiday Management

```javascript
import _tx from '24x7';

// Holidays as strings (ISO format preferred)
const holidays = [
  '2025-01-01', // New Year
  '2025-01-15', // Custom holiday
  '2025-12-25'  // Christmas
];

// Holidays as Date objects
const holidayDates = [
  new Date('2025-01-01'),
  new Date('2025-01-15'),
  new Date('2025-12-25')
];

// Holidays as DateTime instances
const holidayDateTimes = [
_tx('2025-01-01'),
_tx('2025-01-15'),
_tx('2025-12-25')
];

// All work the same
const result = _tx('2025-01-10')
  .addBusinessDays(5, {
    weekends: [0, 6],
    holidays: holidays // or holidayDates or holidayDateTimes
  });

// Real-world example: Company holiday calendar
class HolidayCalendar {
  constructor() {
    this.holidays = [];
  }

  addHoliday(date) {
    this.holidays.push(_tx(date).format('YYYY-MM-DD'));
    return this;
  }

  getBusinessDays(startDate, endDate) {
    return _tx(startDate).businessDaysUntil(endDate, {
      weekends: [0, 6],
      holidays: this.holidays
    });
  }
}

const calendar = new HolidayCalendar();
calendar
  .addHoliday('2025-01-01')
  .addHoliday('2025-07-04')
  .addHoliday('2025-12-25');

const businessDays = calendar.getBusinessDays('2025-01-01', '2025-01-31');
console.log(`${businessDays} business days in January`);
```

#### Real-World Use Cases

```javascript
import _tx from '24x7';

// Use case 1: Calculate invoice due date (Net 30 business days)
function getInvoiceDueDate(invoiceDate, holidays = []) {
  return _tx(invoiceDate).addBusinessDays(30, {
    weekends: [0, 6],
    holidays
  });
}

// Use case 2: Project timeline with business days only
function calculateProjectEndDate(startDate, businessDaysNeeded, holidays = []) {
  return _tx(startDate).addBusinessDays(businessDaysNeeded, {
    weekends: [0, 6],
    holidays
  });
}

// Use case 3: SLA monitoring
function checkSLACompliance(createdDate, slaBusinessDays, holidays = []) {
  const today = _tx();
  const deadline =_tx(createdDate).addBusinessDays(slaBusinessDays, {
    weekends: [0, 6],
    holidays
  });
  
  return {
    deadline: deadline.format('YYYY-MM-DD'),
    isOverdue: today.isAfter(deadline),
    daysRemaining: today.businessDaysUntil(deadline, {
      weekends: [0, 6],
      holidays
    })
  };
}

// Use case 4: Work schedule validation
function isValidWorkSchedule(startDate, endDate, maxConsecutiveDays, holidays = []) {
  const range =_tx(startDate).range(endDate);
  const businessDays = range.toArray().filter(day => 
    day.isBusinessDay({ weekends: [0, 6], holidays })
  );
  
  // Check for consecutive days exceeding limit
  let consecutive = 1;
  let maxConsecutive = 1;
  
  for (let i = 1; i < businessDays.length; i++) {
    const daysDiff = businessDays[i].getTime() - businessDays[i-1].getTime();
    if (daysDiff === 24 * 60 * 60 * 1000) { // Exactly 1 day
      consecutive++;
      maxConsecutive = Math.max(maxConsecutive, consecutive);
    } else {
      consecutive = 1;
    }
  }
  
  return maxConsecutive <= maxConsecutiveDays;
}
```

### 4️⃣ Deterministic Time Control (Testing-Friendly)

Freeze time for predictable tests:

#### Basic Freeze/Unfreeze

```javascript
import _tx from '24x7';

// Freeze time to a specific date
_tx.freeze('2025-01-15');

// Now _tx() always returns the frozen time
const now = _tx(); // Always '2025-01-15'
const later = _tx().addDays(1); // Always '2025-01-16'

// Unfreeze to return to normal behavior
_tx.unfreeze();

// Check if time is frozen
_tx.isFrozen(); // false
```

#### Freezing with Different Input Types

```javascript
import _tx from '24x7';

// Freeze with string
_tx.freeze('2025-01-15');

// Freeze with Date object
_tx.freeze(new Date('2025-01-15'));

// Freeze with timestamp
_tx.freeze(1705334400000);

// Freeze to current time (useful for snapshot testing)
_tx.freeze(); // Freezes to current moment
```

#### Testing Examples

```javascript
import _tx from '24x7';

// Example 1: Testing date-dependent logic
describe('Order Processing', () => {
  beforeEach(() => {
    // Freeze time to a known date
    _tx.freeze('2025-01-15T10:00:00');
  });

  afterEach(() => {
    // Always unfreeze in teardown
    _tx.unfreeze();
  });

  it('should calculate delivery date correctly', () => {
    const orderDate = _tx(); // Always '2025-01-15'
    const deliveryDate = orderDate.addBusinessDays(5, {
      weekends: [0, 6]
    });
    
    expect(deliveryDate.format('YYYY-MM-DD')).toBe('2025-01-22');
  });

  it('should handle same-day orders', () => {
    const orderDate = _tx();
    const isSameDay = orderDate.format('YYYY-MM-DD') === _tx().format('YYYY-MM-DD');
    expect(isSameDay).toBe(true);
  });
});

// Example 2: Testing time-sensitive features
describe('Promotion System', () => {
  beforeEach(() => {
    _tx.freeze('2025-01-10');
  });

  afterEach(() => {
    _tx.unfreeze();
  });

  it('should check if promotion is active', () => {
    const promotionStart =_tx('2025-01-01');
    const promotionEnd =_tx('2025-01-31');
    const range = promotionStart.range(promotionEnd);
    
    expect(range.contains(_tx())).toBe(true); // Today is in range
  });

  it('should detect expired promotions', () => {
    _tx.freeze('2025-02-01'); // After promotion ends
    
    const promotionStart =_tx('2025-01-01');
    const promotionEnd =_tx('2025-01-31');
    const range = promotionStart.range(promotionEnd);
    
    expect(range.contains(_tx())).toBe(false); // Today is not in range
  });
});

// Example 3: Testing with multiple freeze points
describe('Time Progression', () => {
  afterEach(() => {
    _tx.unfreeze();
  });

  it('should simulate time progression', () => {
    // Start at day 1
    _tx.freeze('2025-01-01');
    const day1 = _tx();
    expect(day1.format('YYYY-MM-DD')).toBe('2025-01-01');

    // Move to day 2
    _tx.freeze('2025-01-02');
    const day2 = _tx();
    expect(day2.format('YYYY-MM-DD')).toBe('2025-01-02');

    // Move to day 3
    _tx.freeze('2025-01-03');
    const day3 = _tx();
    expect(day3.format('YYYY-MM-DD')).toBe('2025-01-03');
  });
});
```

#### Integration with Test Frameworks

```javascript
import _tx from '24x7';

// Jest example
describe('My Feature', () => {
  beforeAll(() => {
    _tx.freeze('2025-01-15');
  });

  afterAll(() => {
    _tx.unfreeze();
  });

  test('feature works with frozen time', () => {
    // Your test code
  });
});

// Mocha example
describe('My Feature', function() {
  beforeEach(function() {
    _tx.freeze('2025-01-15');
  });

  afterEach(function() {
    _tx.unfreeze();
  });

  it('should work with frozen time', function() {
    // Your test code
  });
});

// Vitest example
import { beforeEach, afterEach, test } from 'vitest';

beforeEach(() => {
  _tx.freeze('2025-01-15');
});

afterEach(() => {
  _tx.unfreeze();
});

test('feature works', () => {
  // Your test code
});
```

#### Real-World Testing Scenarios

```javascript
import _tx from '24x7';

// Scenario 1: Testing subscription renewal
describe('Subscription Service', () => {
  beforeEach(() => {
    _tx.freeze('2025-01-15');
  });

  afterEach(() => {
    _tx.unfreeze();
  });

  it('should calculate next billing date', () => {
    const subscriptionDate =_tx('2025-01-01');
    const nextBilling = subscriptionDate.addMonths(1);
    
    expect(nextBilling.format('YYYY-MM-DD')).toBe('2025-02-01');
  });

  it('should detect expired subscriptions', () => {
    const expiryDate =_tx('2025-01-10');
    const isExpired = _tx().isAfter(expiryDate);
    
    expect(isExpired).toBe(true); // Today is after expiry
  });
});

// Scenario 2: Testing business day calculations
describe('Business Day Calculator', () => {
  beforeEach(() => {
    _tx.freeze('2025-01-15'); // Wednesday
  });

  afterEach(() => {
    _tx.unfreeze();
  });

  it('should skip weekends', () => {
    const start =_tx('2025-01-15'); // Wednesday
    const result = start.addBusinessDays(2, { weekends: [0, 6] });
    
    // Should be Friday (skips weekend if it falls on one)
    expect(result.format('YYYY-MM-DD')).toBe('2025-01-17');
  });

  it('should skip holidays', () => {
    const start =_tx('2025-01-14'); // Tuesday
    const result = start.addBusinessDays(1, {
      weekends: [0, 6],
      holidays: ['2025-01-15'] // Wednesday is a holiday
    });
    
    // Should skip to Thursday
    expect(result.format('YYYY-MM-DD')).toBe('2025-01-16');
  });
});

// Scenario 3: Testing date range operations
describe('Date Range Operations', () => {
  beforeEach(() => {
    _tx.freeze('2025-01-15');
  });

  afterEach(() => {
    _tx.unfreeze();
  });

  it('should check if current date is in range', () => {
    const range =_tx('2025-01-01').range('2025-01-31');
    expect(range.contains(_tx())).toBe(true);
  });

  it('should handle edge cases', () => {
    const range =_tx('2025-01-15').range('2025-01-15'); // Single day
    expect(range.contains(_tx())).toBe(true);
    expect(range.days()).toBe(1);
  });
});
```

**Important Notes:**
- Freezing time is **global** - affects all `_tx()` calls
- Always call `_tx.unfreeze()` in test teardown (afterEach/afterAll)
- Freezing affects only `_tx()` without arguments - explicit dates are unaffected
- Safe to use in parallel test runners (each test should manage its own freeze state)

## Examples

### Factory Function

Create dates from various inputs:

```javascript
import _tx from '24x7';

// Current time
const now = _tx();
console.log(now.format('YYYY-MM-DD HH:mm:ss')); // Current date/time

// From ISO string
const date1 =_tx('2025-01-15');
const date2 =_tx('2025-01-15T14:30:00Z');
const date3 =_tx('2025-01-15T14:30:00.000Z');

// From Date object
const nativeDate = new Date('2025-01-15');
const date4 =_tx(nativeDate);

// From timestamp (milliseconds)
const date5 =_tx(1705334400000); // Unix timestamp

// From timestamp (seconds)
const date6 =_tx(1705334400 * 1000);

// Undefined uses current time
const date7 =_tx(undefined); // Same as _tx()
```

### Date Manipulation

Add or subtract time units:

```javascript
import _tx from '24x7';

const date =_tx('2025-01-15T10:30:00');

// Add milliseconds
date.add(1000); // Add 1 second

// Add time units
date.addYears(1);    // '2026-01-15T10:30:00'
date.addMonths(2);   // '2025-03-15T10:30:00'
date.addDays(7);     // '2025-01-22T10:30:00'
date.addHours(5);    // '2025-01-15T15:30:00'
date.addMinutes(30); // '2025-01-15T11:00:00'
date.addSeconds(45);  // '2025-01-15T10:30:45'

// Subtract time units
date.subtract(1000);      // Subtract 1 second
date.addDays(-7);         // Subtract 7 days (same as subtractDays)
date.subtract(7 * 24 * 60 * 60 * 1000); // Subtract 7 days in milliseconds

// Chain operations
const result =_tx('2025-01-15')
  .addMonths(1)
  .addDays(5)
  .addHours(2)
  .format('YYYY-MM-DD HH:mm'); // '2025-02-20 02:00'
```

### Date Comparison

Compare dates in various ways:

```javascript
import _tx from '24x7';

const date1 =_tx('2025-01-15');
const date2 =_tx('2025-01-20');
const date3 =_tx('2025-01-15');

// Before/After checks
date1.isBefore(date2);  // true
date1.isAfter(date2);   // false
date1.isSame(date3);    // true

// Compare with different input types
date1.isBefore('2025-01-20');        // true (string)
date1.isBefore(new Date('2025-01-20')); // true (Date object)
date1.isBefore(1705334400000);        // true (timestamp)

// Between check (inclusive)
const start =_tx('2025-01-10');
const end =_tx('2025-01-20');
const middle =_tx('2025-01-15');

middle.isBetween(start, end);  // true
start.isBetween(start, end);   // true (inclusive)
end.isBetween(start, end);     // true (inclusive)
x('2025-01-25').isBetween(start, end); // false

// Real-world example: Check if date is in current month
const today = _tx();
const monthStart = today.startOfMonth();
const monthEnd = today.endOfMonth();
const isInCurrentMonth = today.isBetween(monthStart, monthEnd); // Always true
```

### Date Information

Extract date components:

```javascript
import _tx from '24x7';

const date =_tx('2025-01-15T14:30:45.123');

// Get date parts
date.year();        // 2025
date.month();       // 0 (January, 0-indexed)
date.date();        // 15 (day of month, 1-indexed)
date.day();         // 3 (Wednesday, 0=Sunday)
date.hour();        // 14
date.minute();      // 30
date.second();      // 45
date.millisecond(); // 123

// Use in calculations
const isWeekend = date.day() === 0 || date.day() === 6;
const isJanuary = date.month() === 0;
const isLeapYear = date.year() % 4 === 0 && (date.year() % 100 !== 0 || date.year() % 400 === 0);

// Format with extracted values
const customFormat = `${date.year()}-${String(date.month() + 1).padStart(2, '0')}-${String(date.date()).padStart(2, '0')}`;
```

### Start/End of Periods

Get boundaries of time periods:

```javascript
import _tx from '24x7';

const date =_tx('2025-01-15T14:30:45');

// Day boundaries
date.startOfDay();  // '2025-01-15T00:00:00.000'
date.endOfDay();    // '2025-01-15T23:59:59.999'

// Month boundaries
date.startOfMonth(); // '2025-01-01T00:00:00.000'
date.endOfMonth();   // '2025-01-31T23:59:59.999'

// Year boundaries
date.startOfYear();  // '2025-01-01T00:00:00.000'
date.endOfYear();    // '2025-12-31T23:59:59.999'

// Common use case: Get all dates in current month
const monthStart = _tx().startOfMonth();
const monthEnd = _tx().endOfMonth();
const daysInMonth = monthEnd.date(); // 28-31 depending on month

// Get first Monday of month
const firstDay =_tx('2025-01-15').startOfMonth();
const firstMonday = firstDay.addDays((8 - firstDay.day()) % 7 || 7);
```

### Conversion

Convert to different formats:

```javascript
import _tx from '24x7';

const date =_tx('2025-01-15T14:30:45.123');

// To native Date object (cloned for safety)
const nativeDate = date.toDate();
console.log(nativeDate instanceof Date); // true

// To timestamp
const timestamp = date.valueOf(); // or date.getTime()
console.log(timestamp); // 1705334445123

// To ISO string
const isoString = date.toISOString();
console.log(isoString); // '2025-01-15T14:30:45.123Z'

// Implicit conversion (uses valueOf)
const num = +date; // Same as date.valueOf()
const str = String(date); // Uses toString()

// Use with native Date methods
const utcString = date.toDate().toUTCString();
```

### Timezone

Work with different timezones (default is UTC):

```javascript
import _tx from '24x7';

// Default timezone is UTC
const date =_tx('2025-01-15T10:00:00Z');
console.log(date.tz()); // 'UTC'
console.log(date.isUTC()); // true

// Convert to local timezone
const local = date.toLocal();
console.log(local.tz()); // 'local'
console.log(local.isLocal()); // true

// Convert to specific IANA timezone (chaining works!)
const kolkata = date.tz('Asia/Kolkata');
console.log(kolkata.tz()); // 'Asia/Kolkata'
console.log(kolkata.format('YYYY-MM-DD HH:mm:ss')); // Shows time in Kolkata timezone
console.log(kolkata.ianaTimezone()); // 'Asia/Kolkata'

// Convert to another timezone
const nyDate = date.toTimezone('America/New_York');
console.log(nyDate.tz()); // 'America/New_York'

// Convert back to UTC
const backToUTC = nyDate.toUTC();
console.log(backToUTC.isUTC()); // true

// Get timezone offset
console.log(date.timezoneOffset()); // 0 (minutes)
console.log(date.timezoneOffsetString()); // '+00:00'
console.log(kolkata.timezoneOffsetString()); // '+05:30'

// Set global default timezone
_tx.config({
  time: {
    timezone: 'America/Los_Angeles'
  }
});
const pacificDate =_tx('2025-01-15T10:00:00Z');
console.log(pacificDate.tz()); // 'America/Los_Angeles'
```

#### Supported Timezones

- **UTC** - Coordinated Universal Time (default)
- **local** - System's local timezone
- **IANA timezones** - Any valid IANA timezone identifier (e.g., `'America/New_York'`, `'Asia/Kolkata'`, `'Europe/London'`)

#### Timezone Methods

```javascript
date.tz()                    // Get current timezone
date.tz(timezone)            // Set timezone (returns DateTime for chaining)
date.toTimezone(timezone)    // Convert to timezone (alternative method)
date.toUTC()                 // Convert to UTC
date.toLocal()                // Convert to local timezone
date.timezoneOffset()         // Get offset in minutes
date.timezoneOffsetString()   // Get offset as string (e.g., '+05:30')
date.isUTC()                 // Check if UTC
date.isLocal()               // Check if local
date.ianaTimezone()          // Get IANA timezone name or null
```

#### Timezone-Aware Date Components

All date component methods (`year()`, `month()`, `date()`, `hour()`, `minute()`, `second()`, etc.) automatically respect the timezone:

```javascript
const utc =_tx('2025-01-15T10:00:00Z');
console.log(utc.hour()); // 10 (UTC)

const kolkata = utc.tz('Asia/Kolkata');
console.log(kolkata.hour()); // 15 (IST, UTC+5:30)

// Formatting also respects timezone
console.log(utc.format('YYYY-MM-DD HH:mm:ss')); // '2025-01-15 10:00:00'
console.log(kolkata.format('YYYY-MM-DD HH:mm:ss')); // '2025-01-15 15:30:00'
```

#### Real-World Timezone Examples

```javascript
import _tx from '24x7';

// Example 1: Convert meeting time across timezones
const meetingUTC =_tx('2025-01-15T14:00:00Z');
const meetingNY = meetingUTC.tz('America/New_York');
const meetingLA = meetingUTC.tz('America/Los_Angeles');
const meetingLondon = meetingUTC.tz('Europe/London');

console.log('Meeting times:');
console.log('UTC:', meetingUTC.format('YYYY-MM-DD HH:mm'));
console.log('New York:', meetingNY.format('YYYY-MM-DD HH:mm'));
console.log('Los Angeles:', meetingLA.format('YYYY-MM-DD HH:mm'));
console.log('London:', meetingLondon.format('YYYY-MM-DD HH:mm'));

// Example 2: Calculate time difference between timezones
const now = _tx();
const nyTime = now.tz('America/New_York');
const offset = nyTime.timezoneOffset() - now.timezoneOffset();
console.log(`Time difference: ${offset} minutes`);

// Example 3: Display user's local time
function displayUserTime(userTimezone) {
  const now = _tx().tz(userTimezone);
  return now.format('YYYY-MM-DD HH:mm:ss');
}

console.log(displayUserTime('Asia/Kolkata')); // User's local time in Kolkata
console.log(displayUserTime('America/New_York')); // User's local time in NY
```

### Format Patterns

Format dates with custom patterns:

```javascript
import _tx from '24x7';

const date =_tx('2025-01-15T14:30:45');

// Common formats
date.format('YYYY-MM-DD');           // '2025-01-15'
date.format('MM/DD/YYYY');           // '01/15/2025'
date.format('DD-MM-YYYY');           // '15-01-2025'
date.format('YYYY-MM-DD HH:mm:ss');  // '2025-01-15 14:30:45'

// Human-readable formats
date.format('MMMM D, YYYY');         // 'January 15, 2025'
date.format('MMM D, YYYY');          // 'Jan 15, 2025'
date.format('dddd, MMMM D, YYYY');   // 'Wednesday, January 15, 2025'
date.format('ddd, MMM D');          // 'Wed, Jan 15'

// Time formats
date.format('HH:mm');                // '14:30'
date.format('HH:mm:ss');             // '14:30:45'
date.format('hh:mm A');              // '02:30 PM'
date.format('hh:mm a');              // '02:30 pm'

// Combined formats
date.format('YYYY-MM-DD HH:mm');     // '2025-01-15 14:30'
date.format('MMM D, YYYY [at] hh:mm A'); // 'Jan 15, 2025 at 02:30 PM'

// File-safe format
date.format('YYYY-MM-DD_HH-mm-ss');  // '2025-01-15_14-30-45'
```

## API Reference

### Factory Function

```javascript
_t_tx()                                    // Current time (or frozen time)
_tx('2025-01-15')                        // Parse string (ISO format)
_tx('23/01/2025', 'DD/MM/YYYY')          // Parse with format
_tx('2025-01-15', { immutable: false })  // With config object
_tx(new Date())                          // From Date object
_tx(1234567890)                          // From timestamp
```

### Configuration Methods

```javascript
_tx.config()              // Get current configuration
_tx.config(options)       // Set configuration (deep merge)
_tx.resetConfig()          // Reset to DEFAULT_CONFIG
```

### Immutability Control

```javascript
date.immutable(true)    // Enable immutability (default)
date.immutable(false)   // Disable immutability (mutable mode)
date.isImmutable()      // Check if instance is immutable
```

### Date Manipulation

```javascript
date.add(ms)           // Add milliseconds (returns new instance if immutable)
date.subtract(ms)      // Subtract milliseconds (returns new instance if immutable)
date.addYears(n)       // Add years
date.addMonths(n)      // Add months
date.addDays(n)        // Add days
date.addHours(n)       // Add hours
date.addMinutes(n)     // Add minutes
date.addSeconds(n)     // Add seconds
```

### Interval Methods

```javascript
date.interval(end)     // Create interval from date to end
_tx.interval(start, end)  // Static method to create interval

interval.contains(date | interval)  // Check if contains date or interval
interval.overlaps(other, options)   // Check if overlaps with another interval
interval.intersect(other)           // Get intersection interval or null
interval.clamp(other)               // Clamp interval to fit within this
interval.splitBy(unit | duration)   // Split into array of intervals
interval.iterate(unit)              // Iterator over interval
interval.duration(unit?)            // Get duration in specified unit
_tx.merge(intervals[])                // Merge multiple intervals
```

### Date Comparison

```javascript
date.isBefore(other)   // Check if before
date.isAfter(other)    // Check if after
date.isSame(other)     // Check if equal
date.isBetween(start, end) // Check if between
```

### Date Information

```javascript
date.year()            // Get year
date.month()           // Get month (0-11)
date.date()            // Get day of month (1-31)
date.day()             // Get day of week (0-6)
date.hour()            // Get hour (0-23)
date.minute()          // Get minute (0-59)
date.second()          // Get second (0-59)
date.millisecond()     // Get millisecond (0-999)
```

### Start/End of Periods

```javascript
date.startOfDay()      // Start of day (00:00:00.000)
date.endOfDay()        // End of day (23:59:59.999)
date.startOfMonth()    // Start of month
date.endOfMonth()      // End of month
date.startOfYear()     // Start of year
date.endOfYear()       // End of year
```

### Conversion

```javascript
date.toDate()          // Get native Date object
date.valueOf()         // Get timestamp
date.getTime()         // Get timestamp
date.toISOString()     // Get ISO string
date.format(pattern)   // Format with pattern (requires format plugin)

// Timezone methods
date.tz()              // Get current timezone (or set with date.tz('Asia/Kolkata'))
date.tz(timezone)      // Set timezone and return DateTime (for chaining)
date.timezone()        // Alias for tz() (backward compatibility)
date.toTimezone(tz)    // Convert to timezone
date.toUTC()           // Convert to UTC
date.toLocal()         // Convert to local
date.timezoneOffset()  // Get offset in minutes
date.timezoneOffsetString() // Get offset as string (e.g., '+05:30')
date.isUTC()           // Check if UTC
date.isLocal()         // Check if local
date.ianaTimezone()    // Get IANA timezone name or null
```

### Format Patterns

```javascript
date.format('YYYY-MM-DD')        // '2025-01-15'
date.format('MMM D, YYYY')       // 'Jan 15, 2025'
date.format('YYYY-MM-DD HH:mm')  // '2025-01-15 14:30'
date.format('dddd, MMMM D, YYYY') // 'Wednesday, January 15, 2025'
```

**Pattern tokens:**
- `YYYY` - 4-digit year
- `YY` - 2-digit year
- `MMMM` - Full month name
- `MMM` - Short month name
- `MM` - 2-digit month
- `M` - Month number
- `DD` - 2-digit day
- `D` - Day number
- `dddd` - Full day name
- `ddd` - Short day name
- `HH` - 24-hour format (2-digit)
- `hh` - 12-hour format (2-digit)
- `mm` - Minutes (2-digit)
- `ss` - Seconds (2-digit)
- `A` - AM/PM
- `a` - am/pm

## Plugin System

Plugins extend the DateTime prototype. All plugins are included by default, but you can create custom plugins:

#### Basic Plugin Example

```javascript
import _tx, { DateTime } from '24x7';

// Simplest form: Named function becomes the method
function myMethod() {
  // Your custom method
  return this;
}

_tx.extend(myMethod);

// Now you can use it
_tx().myMethod();

// Alternative 1: Explicit method name
_tx.extend('myMethod2', function() {
  return this;
});

// Alternative 2: Install function (for multiple methods)
_tx.extend((DateTime) => {
  DateTime.prototype.method1 = function() { return this; };
  DateTime.prototype.method2 = function() { return this; };
});

// Alternative 3: Object with install method (also supported)
const myPlugin = {
  install(DateTime) {
    DateTime.prototype.myMethod3 = function() {
      return this;
    };
  }
};

_tx.extend(myPlugin);
```

#### Advanced Plugin Examples

```javascript
import _tx, { DateTime } from '24x7';

// Example 1: Add timezone offset methods (install function for multiple methods)
_tx.extend((DateTime) => {
  DateTime.prototype.getTimezoneOffset = function() {
    return this.toDate().getTimezoneOffset();
  };

  DateTime.prototype.toUTC = function() {
    const date = this.toDate();
    const utcDate = new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
    return new DateTime(utcDate);
  };
});

_tx.extend(timezonePlugin);

// Usage
const date =_tx('2025-01-15T14:30:00');
console.log(date.getTimezoneOffset()); // -480 (PST)
const utc = date.toUTC();

// Example 2: Add age calculation (direct method)
function age(referenceDate = _tx()) {
  const ref = referenceDate instanceof DateTime 
    ? referenceDate 
    : new DateTime(new Date(referenceDate));
  
  let years = ref.year() - this.year();
  const monthDiff = ref.month() - this.month();
  const dayDiff = ref.date() - this.date();

  if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
    years--;
  }

  return years;
}

_tx.extend(age);

// Usage
const birthDate =_tx('1990-05-15');
const age = birthDate.age(); // Age as of today
const ageOnDate = birthDate.age(_tx('2025-05-15')); // Age on specific date

// Example 3: Add quarter methods (install function for multiple methods)
_tx.extend((DateTime) => {
  DateTime.prototype.quarter = function() {
    return Math.floor(this.month() / 3) + 1;
  };

  DateTime.prototype.startOfQuarter = function() {
    const quarter = this.quarter();
    const month = (quarter - 1) * 3;
    const newDate = new Date(this._date);
    newDate.setMonth(month, 1);
    newDate.setHours(0, 0, 0, 0);
    return new DateTime(newDate);
  };

  DateTime.prototype.endOfQuarter = function() {
    const quarter = this.quarter();
    const month = quarter * 3 - 1;
    const newDate = new Date(this._date);
    newDate.setMonth(month + 1, 0);
    newDate.setHours(23, 59, 59, 999);
    return new DateTime(newDate);
  };
});

// Usage
const date =_tx('2025-02-15');
console.log(date.quarter()); // 1 (Q1)
const quarterStart = date.startOfQuarter(); // 2025-01-01
const quarterEnd = date.endOfQuarter(); // 2025-03-31

// Example 4: Add relative time formatting (direct method)
function fromNow() {
  const now = _tx();
  const diffMs = now.getTime() - this.getTime();
  const diffSeconds = Math.floor(diffMs / 1000);
  const diffMinutes = Math.floor(diffSeconds / 60);
  const diffHours = Math.floor(diffMinutes / 60);
  const diffDays = Math.floor(diffHours / 24);

  if (Math.abs(diffSeconds) < 60) {
    return diffSeconds >= 0 ? 'just now' : 'in a few seconds';
  } else if (Math.abs(diffMinutes) < 60) {
    const mins = Math.abs(diffMinutes);
    return diffMinutes >= 0 ? `${mins} minute${mins > 1 ? 's' : ''} ago` : `in ${mins} minute${mins > 1 ? 's' : ''}`;
  } else if (Math.abs(diffHours) < 24) {
    const hrs = Math.abs(diffHours);
    return diffHours >= 0 ? `${hrs} hour${hrs > 1 ? 's' : ''} ago` : `in ${hrs} hour${hrs > 1 ? 's' : ''}`;
  } else if (Math.abs(diffDays) < 7) {
    const days = Math.abs(diffDays);
    return diffDays >= 0 ? `${days} day${days > 1 ? 's' : ''} ago` : `in ${days} day${days > 1 ? 's' : ''}`;
  } else {
    return this.format('MMM D, YYYY');
  }
}

_tx.extend(fromNow);

// Usage
const past = _tx().subtractHours(2);
console.log(past.fromNow()); // "2 hours ago"

const future = _tx().addDays(3);
console.log(future.fromNow()); // "in 3 days"
```

#### Creating Reusable Plugins

```javascript
// plugins/custom.js - Single method
export default function myMethod() {
  return this;
}

// plugins/custom.js - Multiple methods (install function)
export default (DateTime) => {
  DateTime.prototype.method1 = function() { return this; };
  DateTime.prototype.method2 = function() { return this; };
};

// In your application
import _tx from '24x7';
import customPlugin from './plugins/custom.js';

_tx.extend(customPlugin);
```

#### Plugin Best Practices

```javascript
import _tx, { DateTime } from '24x7';

// Single method - use named function
function chainableMethod() {
  // Do something
  return this;
}
_tx.extend(chainableMethod);

// Multiple methods - use install function
_tx.extend((DateTime) => {
  // 1. Always return this for chainability (when appropriate)
  DateTime.prototype.chainableMethod2 = function() {
    return this;
  };

  // 2. Create new DateTime instances for immutable operations
  DateTime.prototype.immutableOperation = function() {
    const newDate = new Date(this._date);
    // Modify newDate
    return new DateTime(newDate);
  };

  // 3. Validate inputs
  DateTime.prototype.safeMethod = function(input) {
    if (typeof input !== 'number') {
      throw new TypeError('Expected a number');
    }
    // Your logic
    return this;
  };

  // 4. Use descriptive method names
  DateTime.prototype.calculateBusinessHours = function() {
    // Better than calculateBH()
    return this;
  };
});
```

## Error Handling

24x7 throws explicit errors for invalid operations:

#### Parsing Errors

```javascript
import _tx from '24x7';

// Invalid date string
try {
_tx('invalid-date');
} catch (error) {
  console.error(error); // TypeError: Cannot parse date from: invalid-date
  console.error(error.message); // "Cannot parse date from: invalid-date"
}

// Invalid date object
try {
_tx(new Date('invalid'));
} catch (error) {
  console.error(error); // TypeError: Invalid Date object provided
}

// Invalid input type
try {
_tx({ not: 'a date' });
} catch (error) {
  console.error(error); // TypeError: Cannot parse date from type: object
}
```

#### Range Errors

```javascript
import _tx from '24x7';

// Start date after end date
try {
_tx('2025-01-10').range('2025-01-05'); // start > end
} catch (error) {
  console.error(error); // RangeError: Start date must be before or equal to end date
}

// Valid range (no error)
const validRange =_tx('2025-01-05').range('2025-01-10'); // OK
const singleDayRange =_tx('2025-01-05').range('2025-01-05'); // OK (same day)
```

#### Business Day Errors

```javascript
import _tx from '24x7';

// Invalid weekends array
try {
_tx('2025-01-10').addBusinessDays(5, {
    weekends: 'not an array' // Should be array
  });
} catch (error) {
  console.error(error); // TypeError: weekends must be an array
}

// Invalid weekend values
try {
_tx('2025-01-10').addBusinessDays(5, {
    weekends: [0, 7] // 7 is invalid (0-6 only)
  });
} catch (error) {
  console.error(error); // TypeError: weekends must be an array of numbers 0-6
}

// Invalid days parameter
try {
_tx('2025-01-10').addBusinessDays('five'); // Should be number
} catch (error) {
  console.error(error); // TypeError: addBusinessDays() expects a number
}
```

#### Comparison Errors

```javascript
import _tx from '24x7';

// Invalid comparison value
try {
_tx('2025-01-10').isBefore('not-a-date');
} catch (error) {
  console.error(error); // TypeError: Cannot compare with invalid date: not-a-date
}

// Valid comparisons
x('2025-01-10').isBefore('2025-01-15'); // true (no error)
x('2025-01-10').isBefore(new Date('2025-01-15')); // true (no error)
```

#### Format Errors

```javascript
import _tx from '24x7';

// Invalid pattern type
try {
_tx('2025-01-15').format(123); // Should be string
} catch (error) {
  console.error(error); // TypeError: format() expects a string pattern
}

// Valid format
x('2025-01-15').format('YYYY-MM-DD'); // '2025-01-15' (no error)
```

#### Manipulation Errors

```javascript
import _tx from '24x7';

// Invalid add/subtract values
try {
_tx('2025-01-15').add('not a number');
} catch (error) {
  console.error(error); // TypeError: add() expects a number (milliseconds)
}

try {
_tx('2025-01-15').addDays('five');
} catch (error) {
  console.error(error); // TypeError: addDays() expects a number
}

// Valid operations
x('2025-01-15').add(1000); // OK
x('2025-01-15').addDays(5); // OK
```

#### Error Handling Best Practices

```javascript
import _tx from '24x7';

// Pattern 1: Try-catch for user input
function parseUserDate(input) {
  try {
    return _tx(input);
  } catch (error) {
    console.error('Invalid date input:', input);
    return null; // or return a default date
  }
}

// Pattern 2: Validate before operations
function safeAddBusinessDays(date, days, options) {
  if (typeof days !== 'number') {
    throw new TypeError('Days must be a number');
  }
  
  try {
    return _tx(date).addBusinessDays(days, options);
  } catch (error) {
    console.error('Error adding business days:', error);
    throw error; // Re-throw or handle as needed
  }
}

// Pattern 3: Graceful degradation
function getDateOrNow(input) {
  try {
    return _tx(input);
  } catch {
    return _tx(); // Fallback to current time
  }
}

// Pattern 4: Type checking
function isValidDateInput(input) {
  try {
  _tx(input);
    return true;
  } catch {
    return false;
  }
}

// Usage
if (isValidDateInput(userInput)) {
  const date =_tx(userInput);
  // Process date
} else {
  // Show error to user
  console.error('Please enter a valid date');
}
```

## Browser and Node.js Support

- **ES2020+** required
- **Node.js**: 14+ (or any environment with ES modules)
- **Browsers**: Modern browsers with ES module support

## Design Principles

1. **Immutability**: All operations return new instances
2. **Explicit APIs**: No magic, no surprises
3. **Predictable behavior**: DST changes don't silently corrupt data
4. **Small core**: Extensible via plugins
5. **No dependencies**: Self-contained
6. **Tree-shakable**: Import only what you need

## License

See [LICENSE](LICENSE) file for details.

## Contributing

This is a production-ready library. Contributions should maintain:
- Immutability guarantees
- Explicit error handling
- Comprehensive documentation
- Test coverage

## Non-Goals

24x7 explicitly does **not** provide:
- Moment.js compatibility layer
- Large locale databases
- Full timezone engine
- Implicit magic behavior

These are intentional design decisions to keep the library focused and maintainable.