A comprehensive TypeScript utility library for time, dates, durations, and calendar operations. Zero dependencies, full tree-shaking support, 430+ functions across 32 public modules.
Live Playground & Docs | GitHub
- 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
- You only need a single built-in
Datehelper or oneIntlformatter. - You want the standardized Temporal API rather than a utility library.
- You need a mutable Moment-style wrapper with implicit global locale state.
npm install ts-time-utilsimport { 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"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.
- Use
ts-time-utilswhen you need a few core utilities from different areas. - Use
ts-time-utils/formatorts-time-utils/calculatewhen one module is enough. - Use dedicated subpaths like
ts-time-utils/chain,ts-time-utils/plugins,ts-time-utils/locale, orts-time-utils/workingHoursfor those feature areas.
Format durations, time ago strings, and custom date formats.
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)Date arithmetic, differences, and business day calculations.
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 monthDate validation, checks, and comparisons.
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); // booleanImmutable Duration class with arithmetic operations.
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; // 5Fluent chainable API for date operations.
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 MondayPlugin 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 conversions, DST handling, and zone comparisons.
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.
ISO weeks, quarters, holidays, and calendar grids.
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 holidaysDate range operations: overlap, gaps, merge, split.
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/falseRRULE-inspired recurring event patterns.
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"Parse and match cron expressions.
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 utilities with configurable start month.
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 fiscalSort, group, and analyze date arrays.
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 gridIterate through date sequences and count dates.
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 monthParse human-friendly date strings.
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.
Public holidays for 20 countries.
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, CHMulti-language formatting with 40+ locales.
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 localeBusiness 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().
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);Safe JSON date serialization and deserialization.
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']);Async utilities, benchmarking, and timing.
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 calculations and birthday utilities.
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 dateTimer and countdown utilities.
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"Time interval operations.
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 intervalCommon date range presets.
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 monthConvert dates between calendar systems using Intl.DateTimeFormat.
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, ChineseFuture-proof with Temporal-like objects that work with native Date.
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 timezoneNanosecond timestamps, BigInt support, DST handling, and leap seconds.
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 secondsDate parsing from various formats.
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'Appointment slots, availability, and booking conflict detection.
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);Market hours, trading days, and settlement date calculations.
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 expirationMedication scheduling, shift patterns, and compliance windows.
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 remainingExtend ChainedDate with custom functionality.
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/falseFor complete API documentation, see the Playground & Docs.
| 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 |
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 codeReleases are automated via GitHub Actions with npm trusted publishing (OIDC).
To release a new version:
git tag vX.Y.Z # Create a semantic version tag for the release
git push --tags # Push tag -> triggers publish workflowThe workflow automatically:
- Sets
package.jsonversion from tag - Runs
npm run release:verify - 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:
npm run release:verifyMIT