Getting Started
moment-less is a zero-dependency TypeScript library that brings Moment.js-style token formatting to the native TC39 Temporal API. If you already know YYYY-MM-DD format strings, you already know moment-less.
Installation
npm install moment-lesspnpm add moment-lessyarn add moment-lessTemporal availability: Node 22+, Chrome 127+, Firefox 139+, Safari 18.2+, and Deno 2.1+ all ship Temporal natively. For older environments, see Browser & Runtime Support.
Your First Format
import { format } from 'moment-less'
const date = Temporal.PlainDate.from('2026-04-09')
console.log(format(date, 'MMMM Do, YYYY'))
// → "April 9th, 2026"That's it. No configuration, no locale setup, no timezone wrangling.
All Five Functions
format(temporalObj, formatString, options?)
Token-based formatting for any Temporal type.
import { format } from 'moment-less'
const dt = Temporal.PlainDateTime.from('2026-04-09T14:05:30')
format(dt, 'YYYY-MM-DD') // → "2026-04-09"
format(dt, 'h:mm A') // → "2:05 PM"
format(dt, 'dddd, MMMM Do, YYYY') // → "Thursday, April 9th, 2026"
format(dt, 'ddd MMM D YYYY') // → "Thu Apr 9 2026"
// Escape literal text with square brackets
format(dt, '[Today is] dddd') // → "Today is Thursday"fromNow(temporalObj, reference?, locale?)
Human-readable relative time string.
The optional reference is a Temporal.Instant. Pass one for deterministic output (great for tests); omit it to measure against the real current time.
import { fromNow } from 'moment-less'
const now = Temporal.Instant.from('2026-04-09T14:00:00Z')
const posted = now.subtract({ hours: 3 })
fromNow(posted, now) // → "3 hours ago"
fromNow(now.add({ days: 2 }), now) // → "in 2 days"
// Omit the reference to measure against Temporal.Now
fromNow(Temporal.Now.instant().subtract({ minutes: 5 })) // → "5 minutes ago"
// Locale support
fromNow(posted, now, 'fr') // → "il y a 3 heures"
fromNow(posted, now, 'es') // → "hace 3 horas"calendar(temporalObj, reference?, options?)
Context-aware calendar label, like chat apps and file managers use.
The optional reference is a Temporal.PlainDate. A PlainDate input yields a date-only label; a datetime input appends the time.
import { calendar } from 'moment-less'
const today = Temporal.PlainDate.from('2026-04-09') // a Thursday
calendar(Temporal.PlainDateTime.from('2026-04-09T14:05'), today) // → "Today at 2:05 PM"
calendar(Temporal.PlainDateTime.from('2026-04-08T09:30'), today) // → "Yesterday at 9:30 AM"
calendar(Temporal.PlainDate.from('2026-04-06'), today) // → "Monday"
calendar(Temporal.PlainDate.from('2025-11-15'), today) // → "Nov 15, 2025"humanizeDuration(duration, locale?)
Converts a Temporal.Duration to a human-readable string, picking the most significant unit.
import { humanizeDuration } from 'moment-less'
humanizeDuration(Temporal.Duration.from({ minutes: 45 })) // → "45 minutes"
humanizeDuration(Temporal.Duration.from({ hours: 2, minutes: 30 })) // → "3 hours"
humanizeDuration(Temporal.Duration.from({ days: 3 })) // → "3 days"
humanizeDuration(Temporal.Duration.from({ years: 1, months: 2 })) // → "1 year"
// Localized
humanizeDuration(Temporal.Duration.from({ hours: 2 }), 'fr') // → "2 heures"
humanizeDuration(Temporal.Duration.from({ hours: 2 }), 'es') // → "2 horas"fromDate(date)
Bridges a legacy JavaScript Date object to a Temporal.Instant.
import { format, fromDate } from 'moment-less'
const inst = fromDate(new Date('2026-04-09T14:05:00Z'))
format(inst, 'YYYY-MM-DD HH:mm') // → "2026-04-09 14:05"
// Works with Date.now() too
const now = fromDate(new Date(Date.now()))Token Quick Reference
| Token | Output example | Description |
|---|---|---|
YYYY | 2026 | 4-digit year |
YY | 26 | 2-digit year |
MMMM | April | Full month name |
MMM | Apr | Short month name |
MM | 04 | Month, zero-padded |
M | 4 | Month, no padding |
Do | 9th | Day of month with ordinal suffix |
DD | 09 | Day, zero-padded |
D | 9 | Day, no padding |
dddd | Thursday | Full weekday name |
ddd | Thu | Short weekday name |
HH | 14 | 24-hour hour, zero-padded |
H | 14 | 24-hour hour, no padding |
hh | 02 | 12-hour hour, zero-padded |
h | 2 | 12-hour hour, no padding |
mm | 05 | Minutes, zero-padded |
ss | 30 | Seconds, zero-padded |
SSS | 042 | Milliseconds, zero-padded |
A | PM | AM/PM uppercase |
a | pm | am/pm lowercase |
Z | +05:30 | UTC offset, with colon (ZonedDateTime/Instant) |
ZZ | +0530 | UTC offset, no colon (ZonedDateTime/Instant) |
X | 1775723730 | Unix timestamp, seconds (ZonedDateTime/Instant) |
x | 1775723730000 | Unix timestamp, ms (ZonedDateTime/Instant) |
[…] | literal text | Escaped literal characters |
See the full format() API reference for details on which tokens are available for each Temporal type.
Next Steps
- Browser & Runtime Support — polyfill setup for older environments
- Migrating from Moment.js — side-by-side comparison
- format() API Reference — complete token documentation