# ts-time-utils

A comprehensive TypeScript utility library for time, dates, durations, and calendar operations. Zero dependencies, full tree-shaking support, 430+ functions across 32 public modules.

[![npm version](https://img.shields.io/npm/v/ts-time-utils.svg)](https://www.npmjs.com/package/ts-time-utils)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

**[Live Playground & Docs](https://ts-time-utils.h8frad.work)** | [GitHub](https://github.com/hatefrad/ts-time-utils)

## Features

- **Lightweight** — Import only what you need with tree-shaking support
- **Zero dependencies** — Pure TypeScript, no external packages
- **Type-safe** — Full TypeScript support with IntelliSense
- **Comprehensive** — 430+ functions across 32 public modules
- **Fluent API** — Chain operations with the `chain()` API
- **Extensible** — Plugin system for custom functionality

## When Not To Use This Library

- You only need a single built-in `Date` helper or one `Intl` formatter.
- You want the standardized Temporal API rather than a utility library.
- You need a mutable Moment-style wrapper with implicit global locale state.

## Installation

```bash
npm install ts-time-utils
```

## Quick Start

```ts
import { formatDuration, timeAgo, Duration } from 'ts-time-utils';

// Format milliseconds to readable duration
formatDuration(3661000); // "1 hour, 1 minute, 1 second"

// Get "time ago" strings
timeAgo(new Date(Date.now() - 3600000)); // "1 hour ago"

// Duration arithmetic
const meeting = Duration.fromMinutes(45);
const buffer = Duration.fromMinutes(15);
meeting.add(buffer).toString(); // "1h"
```

### Tree-shaking (recommended)

```ts
import { formatDuration } from 'ts-time-utils/format';
import { differenceInUnits } from 'ts-time-utils/calculate';
import { isValidDate } from 'ts-time-utils/validate';
```

Every public module is also available as a subpath import such as
`ts-time-utils/timezone`, `ts-time-utils/workingHours`, or
`ts-time-utils/naturalLanguage`.

### Module Selection

- Use `ts-time-utils` when you need a few core utilities from different areas.
- Use `ts-time-utils/format` or `ts-time-utils/calculate` when one module is enough.
- Use dedicated subpaths like `ts-time-utils/chain`, `ts-time-utils/plugins`, `ts-time-utils/locale`, or `ts-time-utils/workingHours` for those feature areas.

---

## Utility Categories

### Format

Format durations, time ago strings, and custom date formats.

```ts
import { formatDuration, timeAgo, parseDuration } from 'ts-time-utils/format';

formatDuration(65000);                    // "1 minute, 5 seconds"
formatDuration(65000, { short: true });   // "1m 5s"
timeAgo(new Date(Date.now() - 60000));    // "1 minute ago"
parseDuration('1h 30m');                  // 5400000 (ms)
```

### Calculate

Date arithmetic, differences, and business day calculations.

```ts
import { differenceInUnits, differenceInCalendarDays, addTime, startOf, endOf } from 'ts-time-utils/calculate';

differenceInUnits(date1, date2, 'days');  // 10
differenceInCalendarDays(date1, date2);   // Calendar date boundary count
addTime(new Date(), 5, 'hours');          // 5 hours from now
startOf(new Date(), 'day');               // 00:00:00 today
endOf(new Date(), 'month');               // Last moment of month
```

### Validate

Date validation, checks, and comparisons.

```ts
import { isValidDate, isLeapYear, isWeekend, isSameDay } from 'ts-time-utils/validate';

isValidDate(new Date('2025-13-01'));      // false
isLeapYear(2024);                         // true
isWeekend(new Date('2025-09-13'));        // true (Saturday)
isSameDay(date1, date2);                  // boolean
```

### Duration

Immutable Duration class with arithmetic operations.

```ts
import { Duration } from 'ts-time-utils/duration';

const d1 = Duration.fromHours(2.5);
const d2 = Duration.fromString('1h 30m 45s');
const d3 = Duration.between(startDate, endDate);

d1.add(d2).toString();        // "4h 0m 45s"
d1.greaterThan(d2);           // true
d1.multiply(2).hours;         // 5
```

### Chain API

Fluent chainable API for date operations.

```ts
import { chain } from 'ts-time-utils/chain';

chain(new Date())
  .startOf('day')
  .add(9, 'hours')
  .add(30, 'minutes')
  .toDate();  // Today at 9:30am

chain(new Date())
  .add(1, 'week')
  .startOf('week')
  .format('YYYY-MM-DD');  // Next week Monday
```

Plugin extensions are imported from `ts-time-utils/plugins`, and that module now loads the chain class directly. You can import `plugins` before or after `chain` without any hidden global setup.

### Timezone

Timezone conversions, DST handling, and zone comparisons.

```ts
import { formatInTimeZone, isDST, convertBetweenZones } from 'ts-time-utils/timezone';

formatInTimeZone(new Date(), 'America/New_York');
isDST(new Date('2025-07-14'), 'America/New_York');  // true
convertBetweenZones(new Date(), 'UTC', 'Asia/Tokyo');
```

`isDST()` uses a yearly-offset heuristic rather than authoritative transition metadata.

### Calendar

ISO weeks, quarters, holidays, and calendar grids.

```ts
import { getWeekNumber, getQuarter, getEaster, getUSHolidays } from 'ts-time-utils/calendar';

getWeekNumber(new Date('2025-09-14'));    // 37
getQuarter(new Date('2025-07-15'));       // 3
getEaster(2025);                          // Easter Sunday 2025
getUSHolidays(2025);                      // Array of US federal holidays
```

### Date Range

Date range operations: overlap, gaps, merge, split.

```ts
import { mergeDateRanges, findGaps, dateRangeOverlap } from 'ts-time-utils/dateRange';

const ranges = [
  { start: new Date('2024-01-01'), end: new Date('2024-01-10') },
  { start: new Date('2024-01-05'), end: new Date('2024-01-15') },
];

mergeDateRanges(ranges);  // Merged into single range
findGaps(busyTimes, workday);  // Available time slots
dateRangeOverlap(range1, range2);  // true/false
```

### Recurrence

RRULE-inspired recurring event patterns.

```ts
import { createRecurrence, recurrenceToString } from 'ts-time-utils/recurrence';

const weekly = createRecurrence({
  frequency: 'weekly',
  interval: 1,
  startDate: new Date('2025-01-01'),
  byWeekday: [1, 3, 5],  // Mon, Wed, Fri
});

weekly.getNextOccurrence(new Date());
weekly.getAllOccurrences();
recurrenceToString(weekly.rule);  // "Every week on Monday, Wednesday, Friday"
```

### Cron

Parse and match cron expressions.

```ts
import { parseCronExpression, matchesCron, getNextCronDate, describeCron } from 'ts-time-utils/cron';

const date = new Date();

parseCronExpression('0 9 * * 1-5');       // minute/hour/day/month/day-of-week parts
matchesCron(date, '0 9 * * 1-5');         // true if weekday 9am
getNextCronDate('0 9 * * *');             // Next 9am
describeCron('0 9 * * 1-5');              // "At 09:00 on Monday through Friday"
```

### Fiscal Year

Fiscal year utilities with configurable start month.

```ts
import { getFiscalYear, getFiscalQuarter } from 'ts-time-utils/fiscal';

const date = new Date();

getFiscalYear(date, { startMonth: 4 });          // April start
getFiscalYear(date, { startMonth: 7 });          // July start
getFiscalYear(date, { startMonth: 10 });         // October start
getFiscalQuarter(date, { startMonth: 4 });       // Q2 for UK fiscal
```

### Compare & Sort

Sort, group, and analyze date arrays.

```ts
import { sortDates, closestDate, groupDatesByMonth, snapDate } from 'ts-time-utils/compare';

sortDates(dates, 'desc');
closestDate(target, candidates);
groupDatesByMonth(dates);  // Map by YYYY-MM
snapDate(date, 15, 'minutes');  // Snap to 15-min grid
```

### Iterate

Iterate through date sequences and count dates.

```ts
import { eachDay, eachWeekday, countWeekdays, filterDays } from 'ts-time-utils/iterate';

eachDay(start, end);       // Array of each day
eachWeekday(start, end);   // Weekdays only (Mon-Fri)
countWeekdays(start, end); // Number of weekdays
filterDays(start, end, d => d.getDate() === 15);  // 15th of each month
```

### Natural Language

Parse human-friendly date strings.

```ts
import { parseNaturalDate, extractDatesFromText } from 'ts-time-utils/naturalLanguage';

parseNaturalDate('tomorrow');
parseNaturalDate('next Friday');
parseNaturalDate('in 2 weeks');
parseNaturalDate('end of month');

extractDatesFromText('Meeting tomorrow at 3pm');
// [{ date: Date, text: 'tomorrow at 3pm', confidence: 0.9 }]
```

`parseNaturalDate` accepts `referenceDate`, `defaultTime`, and `strict`. When `strict` is `true`, the parser only returns the library's recognized patterns and skips the permissive `Date` fallback.

### International Holidays

Public holidays for 20 countries.

```ts
import { getHolidays, isHoliday, getNextHoliday } from 'ts-time-utils/holidays';

const today = new Date();

getHolidays(2025, 'UK');      // UK bank holidays
getHolidays(2025, 'DE');      // German holidays
isHoliday(today, 'CA');       // Is Canadian holiday?
getNextHoliday(today, 'AU');   // Next Australian holiday

// Supported: UK, NL, DE, CA, AU, IT, ES, CN, IN, US,
//            JP, FR, BR, MX, KR, SG, PL, SE, BE, CH
```

### Locale

Multi-language formatting with 40+ locales.

```ts
import { formatRelativeTime, formatDateLocale, detectLocale } from 'ts-time-utils/locale';

const pastDate = new Date(Date.now() - 2 * 60 * 60 * 1000);
const date = new Date();

formatRelativeTime(pastDate, { locale: 'es' });  // "hace 2 horas"
formatRelativeTime(pastDate, { locale: 'de' });  // "vor 2 Stunden"
formatDateLocale(date, 'fr', 'long');            // "15 janvier 2024"
detectLocale();                                   // Auto-detect system locale
```

### Working Hours

Business hours calculations with break support. These helpers use the
`Date` values you pass in directly; if you need a different timezone's wall
clock, adapt the instant first with a timezone helper such as
`convertDateToZone()` or `reinterpretAsZone()`.

```ts
import { isWorkingTime, addWorkingDays, workingDaysBetween } from 'ts-time-utils/workingHours';

const config = {
  workingDays: [1, 2, 3, 4, 5],
  hours: { start: 9, end: 17 },
  breaks: [{ start: 12, end: 13 }]
};
const now = new Date();

isWorkingTime(now, config);
addWorkingDays(now, 5, config);
workingDaysBetween(now, new Date('2025-12-31'), config);
```

### Serialization

Safe JSON date serialization and deserialization.

```ts
import { serializeDate, parseJSONWithDates, stringifyWithDates } from 'ts-time-utils/serialize';

serializeDate(date, { format: 'iso' });    // "2025-09-14T12:30:45.123Z"
serializeDate(date, { format: 'epoch' });  // 1757853045123

const json = stringifyWithDates(data, ['createdAt']);
const parsed = parseJSONWithDates(json, ['createdAt']);
```

### Performance

Async utilities, benchmarking, and timing.

```ts
import { sleep, benchmark, Stopwatch, debounce } from 'ts-time-utils/performance';

await sleep(1000);
await benchmark(() => heavyOperation(), 10);

const stopwatch = new Stopwatch();
stopwatch.start();
// ... operations
stopwatch.getElapsed();

const debouncedFn = debounce(fn, 300);
```

### Age

Age calculations and birthday utilities.

```ts
import { calculateAge, getLifeStage, getNextBirthday } from 'ts-time-utils/age';

calculateAge(new Date('1990-05-15'));  // { years: 34, months: 4, days: 2 }
getLifeStage(25);                      // "adult"
getNextBirthday(birthDate);            // Next birthday date
```

### Countdown

Timer and countdown utilities.

```ts
import { createCountdown, getRemainingTime, formatCountdown } from 'ts-time-utils/countdown';

const countdown = createCountdown(targetDate, {
  onTick: (remaining) => console.log(remaining.days, 'd'),
  onComplete: () => console.log('Done!'),
});
countdown.start();

getRemainingTime(targetDate);  // { days, hours, minutes, seconds }
formatCountdown(targetDate, { units: ['days', 'hours'] });  // "45d 12h"
```

### Interval

Time interval operations.

```ts
import { createInterval, intervalsOverlap, mergeIntervals } from 'ts-time-utils/interval';

const a = createInterval('2025-01-01', '2025-01-05');
const b = createInterval('2025-01-04', '2025-01-10');
intervalsOverlap(a, b);  // true
mergeIntervals([a, b]);  // Single merged interval
```

### Range Presets

Common date range presets.

```ts
import { today, lastNDays, thisWeek, thisMonth } from 'ts-time-utils/rangePresets';

today();        // { start, end } for today
lastNDays(7);   // Last 7 days
thisWeek();     // Current week
thisMonth();    // Current month
```

### Non-Gregorian Calendars

Convert dates between calendar systems using Intl.DateTimeFormat.

```ts
import { toHebrewDate, toIslamicDate, toJapaneseDate, getChineseZodiac } from 'ts-time-utils/calendars';

toHebrewDate(new Date());        // { year: 5785, month: 4, day: 23, calendar: 'hebrew' }
toIslamicDate(new Date());       // { year: 1446, month: 7, day: 1, calendar: 'islamic-umalqura' }
toJapaneseDate(new Date());      // { year: 6, era: 'Reiwa', calendar: 'japanese' }
getChineseZodiac(2024);          // 'Dragon'

// Supported: Hebrew, Islamic, Buddhist, Japanese, Persian, Chinese
```

### Temporal API Compatibility

Future-proof with Temporal-like objects that work with native Date.

```ts
import { toPlainDate, toPlainDateTime, toZonedDateTime, toInstant } from 'ts-time-utils/temporal';

const date = toPlainDate(2024, 3, 25);
date.add({ days: 7 });           // PlainDate
date.until(otherDate);           // Duration
date.dayOfWeek;                  // 1 (Monday, ISO)

const zdt = toZonedDateTime(new Date(), 'America/New_York');
zdt.hour;                        // Hour in that timezone
zdt.toInstant();                 // Epoch-based Instant

const instant = toInstant(Date.now());
instant.toZonedDateTime('UTC');  // Convert to any timezone
```

### High-Precision Utilities

Nanosecond timestamps, BigInt support, DST handling, and leap seconds.

```ts
import {
  createNanosecondTimestamp, nowNanoseconds,
  toBigIntMs, ValidDate, isInDSTGap, leapSecondsBetween
} from 'ts-time-utils/precision';

// Nanosecond precision
const ts = createNanosecondTimestamp(Date.now(), 500000);
ts.totalNanoseconds;             // BigInt

// BigInt timestamps for large date ranges
toBigIntMs(new Date());          // BigInt milliseconds

// DST transition detection
isInDSTGap(new Date('2024-03-10T02:30:00'));

// Validated dates (never invalid)
const valid = ValidDate.from(date);  // Throws if invalid
const maybe = ValidDate.tryFrom(date);  // Returns null if invalid

// Leap second awareness
leapSecondsBetween(date1, date2);  // Number of leap seconds
```

### Parse

Date parsing from various formats.

```ts
import { parseDate, parseTime, guessDateFormat } from 'ts-time-utils/parse';

parseDate('Dec 25, 2025');
parseDate('25/12/2025', 'DD/MM/YYYY');
parseTime('2:30 PM');  // { hour: 14, minute: 30 }
guessDateFormat('2025-09-14');  // 'YYYY-MM-DD'
```

### Scheduling

Appointment slots, availability, and booking conflict detection.

```ts
import {
  generateSlots, getAvailableSlots, findNextAvailable,
  hasConflict, findConflicts, mergeBookings
} from 'ts-time-utils/scheduling';

// Generate 30-min slots for a day
const slots = generateSlots(new Date(), {
  slotDuration: 30,
  workingHours: {
    workingDays: [1, 2, 3, 4, 5],
    hours: { start: 9, end: 17 }
  }
});

// Find available slots (excluding existing bookings)
const available = getAvailableSlots(new Date(), existingBookings, config);

// Find next available slot of specific duration
findNextAvailable(new Date(), bookings, 60, config);  // 60-min slot

// Check for conflicts
hasConflict(bookings, proposedSlot);  // true/false
findConflicts(bookings, proposedSlot);  // Array of conflicting bookings

// Merge adjacent bookings
mergeBookings(bookings);
```

### Finance

Market hours, trading days, and settlement date calculations.

```ts
import {
  isMarketOpen, isTradingDay, getMarketHours,
  getNextMarketOpen, addTradingDays, getSettlementDate,
  eachTradingDay, getOptionsExpiration
} from 'ts-time-utils/finance';

// Check market status
isMarketOpen(new Date(), 'NYSE');      // Is NYSE open right now?
isTradingDay(new Date(), 'NASDAQ');    // Is today a trading day?
getMarketHours('NYSE');                // { open: {hour:9,minute:30}, close: {hour:16,minute:0} }

// Market timing
getNextMarketOpen(new Date());         // Next market open time
addTradingDays(new Date(), 5);         // 5 trading days from now

// Settlement (T+2, etc.)
getSettlementDate(tradeDate, 2);       // T+2 settlement date

// Iterate trading days
eachTradingDay(start, end);            // Array of trading days

// Options expiration (3rd Friday)
getOptionsExpiration(2025, 3);         // March 2025 expiration
```

### Healthcare

Medication scheduling, shift patterns, and compliance windows.

```ts
import {
  getMedicationTimes, getNextMedicationTime, parseMedicationFrequency,
  generateShiftSchedule, getShiftForTime, isOnShift,
  createOnCallRotation, getOnCallStaff,
  getComplianceDeadline, timeUntilDeadline
} from 'ts-time-utils/healthcare';

// Medication times (BID = twice daily, TID = 3x daily, etc.)
getMedicationTimes(new Date(), 'BID');   // [8am, 8pm] (default wake/sleep times)
getMedicationTimes(new Date(), 'q6h');   // Every 6 hours
getNextMedicationTime(now, 'TID');       // Next scheduled dose
parseMedicationFrequency('twice daily'); // 'BID'

// Shift scheduling
generateShiftSchedule(start, end, { pattern: '12hr', startTime: { hour: 7, minute: 0 } });
getShiftForTime(now, shiftConfig);       // Current shift
isOnShift(now, shiftStart, config);      // Is within shift?

// On-call rotations
const rotation = createOnCallRotation(start, end, ['Dr. Smith', 'Dr. Jones'], 24);
getOnCallStaff(now, rotation);           // Who's on call?

// Compliance windows
getComplianceDeadline(eventDate, 72);    // 72-hour window
timeUntilDeadline(eventDate, deadline);  // Duration remaining
```

---

## Plugin System

Extend ChainedDate with custom functionality.

```ts
import { chain, ChainedDate } from 'ts-time-utils/chain';
import { extend } from 'ts-time-utils/plugins';

extend('business', {
  addBusinessDays(this: ChainedDate, days: number) {
    // Implementation
    return this;
  },
  isBusinessDay(this: ChainedDate) {
    const day = this.toDate().getDay();
    return day !== 0 && day !== 6;
  }
});

chain(new Date())
  .addBusinessDays(5)
  .isBusinessDay();  // true/false
```

---

## API Reference

For complete API documentation, see the [Playground & Docs](https://ts-time-utils.h8frad.work).

### All Modules

| Module | Description |
|--------|-------------|
| `format` | Duration formatting, time ago, date patterns |
| `calculate` | Date arithmetic, differences, rounding |
| `validate` | Date validation, comparisons, type checks |
| `duration` | Immutable Duration class with arithmetic |
| `chain` | Fluent chainable API |
| `timezone` | Timezone conversions, DST handling |
| `calendar` | ISO weeks, quarters, holidays, grids |
| `calendars` | Non-Gregorian calendars (Hebrew, Islamic, etc.) |
| `temporal` | Temporal API compatibility layer |
| `precision` | Nanoseconds, BigInt, DST, leap seconds |
| `dateRange` | Range operations: overlap, gaps, merge |
| `recurrence` | RRULE-inspired recurring patterns |
| `cron` | Cron expression parsing and matching |
| `fiscal` | Fiscal year utilities |
| `compare` | Date sorting, grouping, statistics |
| `iterate` | Date iteration and counting |
| `naturalLanguage` | Natural language date parsing |
| `holidays` | International holidays (20 countries) |
| `locale` | Multi-language formatting (40+ locales) |
| `workingHours` | Business hours calculations |
| `serialize` | JSON date serialization |
| `performance` | Async utilities, benchmarking |
| `age` | Age calculations, birthdays |
| `countdown` | Timer and countdown utilities |
| `interval` | Time interval operations |
| `rangePresets` | Common date range presets |
| `parse` | Date parsing from various formats |
| `scheduling` | Appointment slots, availability, booking |
| `finance` | Market hours, trading days, settlement |
| `healthcare` | Medication schedules, shifts, on-call |
| `plugins` | Plugin system for extensions |
| `constants` | Time constants and types |

---

## Development

```bash
npm install      # Install dependencies
npm run build    # Build both CJS and ESM
npm test         # Run tests
npm run test:package  # Verify built package exports after build
npm run lint     # Lint code
```

## Releasing

Releases are automated via GitHub Actions with npm trusted publishing (OIDC).

**To release a new version:**

```bash
git tag vX.Y.Z        # Create a semantic version tag for the release
git push --tags       # Push tag -> triggers publish workflow
```

The workflow automatically:
1. Sets `package.json` version from tag
2. Runs `npm run release:verify`
3. Publishes to npm with provenance

**Version format:** Tags must match `v*` pattern (e.g., `v1.2.3`, `v1.2.3-beta.1`)

Before tagging, run:

```bash
npm run release:verify
```

## License

MIT
