Parse time, naturally

Human-friendly time expressions → JavaScript Dates

A few examples of what you can parse. See the docs below for details.

Dates
today, tomorrow, yesterday
next monday at 3pm
march 15th, the 20th
day after tomorrow
third Thursday of November
last day of march
Durations
2 days, 3 weeks, 1 month
1d, 2w, 3mo, 1y, 2h, 30m
half a day, 1.5 weeks
1 week and 2 days
two and a half hours
a couple of days
Spans
jan 5 to jan 20
last 30 days, next 2 weeks
july 3rd for 10 days
this week, this month, ytd
jan through mar
monday through friday
Fuzzy Periods
Q1, Q1 2025, first quarter
early january, mid Q1
H1 2025, H2 2025
spring, summer, winter
beginning of month
end of year, start of week
Business
next business day
in 5 business days
EOD, COB, EOD Friday
close of business Monday
end of day tomorrow
2 business days ago
Natural Time
half past 4, quarter to 5
noon on Friday, midnight
in a fortnight
week 12, the week of March 15
10 to noon, 5 past 3pm
tomorrow half past 9
npm install @timelang/parse

Usage

Use parse() for automatic type detection, or one of the specific functions if you know what to expect.

import { parse } from '@timelang/parse'
// Dates
parse('next friday')
parse('tomorrow at 3pm')
parse('march 15th 2025')
// → { type: 'date', date: Date, title: null }
// Durations
parse('2 weeks')
parse('1h 30m')
// → { type: 'duration', duration: 1209600000, title: null }
// Time spans
parse('jan 5 to jan 20')
parse('last 30 days')
parse('next monday for 2 weeks')
// → { type: 'span', start: Date, end: Date, duration: number, title: null }
// With titles
parse('Team offsite - March 10 to March 14')
parse('Sprint 1: jan 5 to jan 19')
// → { type: 'span', ..., title: 'Team offsite' }
// Fuzzy periods
parse('Q1 2025')
parse('early january')
parse('mid Q1')
// → { type: 'fuzzy', start: Date, end: Date, approximate: true }

Helpers

import { parseDate, parseDuration, parseSpan, scan } from '@timelang/parse'
// Single dates
parseDate('tomorrow') // Date
parseDate('next friday at 3pm') // Date
parseDate('in 2 hours') // Date
// Durations in milliseconds
parseDuration('2 weeks') // 1209600000
parseDuration('2h 30m') // 9000000
// Time spans
parseSpan('jan 5 to jan 20') // { start, end, duration }
parseSpan('last 30 days') // { start, end, duration }
parseSpan('this week') // { start, end, duration }
// Scan for dates in prose text
scan('can we meet tomorrow at 5pm?')
// → [{ result: { type: 'date', ... }, match: 'tomorrow at 5pm', start: 12, end: 27 }]

Titles

Automatically extracts titles from expressions using common separators.

parse('Team offsite - March 10 to March 14')
// → { type: 'span', title: 'Team offsite', ... }
parse('Sprint 1: jan 5 to jan 19')
// → { type: 'span', title: 'Sprint 1', ... }
parse('Meeting with Team (Jan 15 at 2pm)')
// → { type: 'date', title: 'Meeting with Team', ... }
parse('Project Kickoff [March 1st]')
// → { type: 'date', title: 'Project Kickoff', ... }

Options

parse('Q1', {
referenceDate: new Date('2025-06-01'),
fiscalYearStart: 'april', // 'january' | 'april' | 'july' | 'october'
weekStartsOn: 'monday', // 'sunday' | 'monday'
dateFormat: 'intl' // 'us' | 'intl' | 'auto'
})

Types

type ParseResult = DateResult | DurationResult | SpanResult | FuzzyResult | null
interface DateResult {
type: 'date'
date: Date
title: string | null
}
interface DurationResult {
type: 'duration'
duration: number
title: string | null
}
interface SpanResult {
type: 'span'
start: Date
end: Date
duration: number
title: string | null
}
interface FuzzyResult {
type: 'fuzzy'
start: Date
end: Date
approximate: true
title: string | null
}
interface ScanMatch {
result: ParseResult
match: string
start: number
end: number
}

Playground

Test each function individually.

parse()returns discriminated union
parseDate()returns Date | null
parseDuration()returns ms | null
parseSpan()returns { start, end, duration } | null
scan()returns array of matches with positions