The Moment.js-style date formatting library built for the native JavaScript Temporal API.
Format Temporal.PlainDate, Temporal.ZonedDateTime, Temporal.Instant, and more using the familiar YYYY-MM-DD token syntax β with zero dependencies and a ~936B gzipped footprint.
import { format, fromNow } from 'moment-less';
// Format any Temporal object with Moment.js tokens
const date = Temporal.Now.plainDateISO();
format(date, 'YYYY-MM-DD'); // "2026-04-09"
format(date, 'DD/MM/YY'); // "09/04/26"
// Relative time without any extra libraries
const past = Temporal.Now.instant().subtract({ hours: 3 });
fromNow(past); // "3 hours ago"- Moment.js-compatible token syntax β
YYYY,MMMM,MMM,MM,DD,Do,dddd,ddd,HH,hh,H,h,mm,ss,A/a - Full Temporal API support β
ZonedDateTime,PlainDateTime,PlainDate,PlainTime(format only),Instant - Relative time β
fromNow()powered by nativeIntl.RelativeTimeFormat, with optional locale - Zero production dependencies β nothing to audit, nothing to update
- First-class TypeScript β strict types, no
@types/*package needed - Tree-shakeable β import only
formator onlyfromNow, pay for what you use - Universal runtime β browser, Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge
The TC39 Temporal API fixes JavaScript's broken Date object with immutable, time-zone-aware, calendar-correct primitives. But Temporal intentionally ships without a formatting method β the spec delegates that to Intl.DateTimeFormat, which is powerful but verbose for everyday UI tasks.
Developers migrating away from Moment.js (now officially in maintenance mode) still need a way to write format(date, 'MMM D, YYYY') without pulling in a 13β72KB library that doesn't even understand Temporal objects.
moment-less is the missing bridge: the token syntax you already know, wired directly to Temporal's native properties. No parsing engine, no mutation, no timezone layer β Temporal already handles all of that.
npm install moment-less
# or
pnpm add moment-less
# or
yarn add moment-lessmoment-less is a pure formatting utility β no DOM APIs, no Node.js built-ins. It runs anywhere Temporal is available natively or via polyfill.
Native Temporal (no polyfill needed)
| Runtime | Minimum version |
|---|---|
| Node.js | 22.0+ |
| Chrome / Edge | 127+ |
| Firefox | 139+ |
| Safari | 18.2+ |
| Deno | 2.1+ |
| Bun | 1.2+ |
Older environments β use with a Temporal polyfill
npm install temporal-polyfillimport 'temporal-polyfill/global'; // installs Temporal on globalThis
import { format } from 'moment-less';
format(Temporal.Now.plainDateISO(), 'YYYY-MM-DD');moment-less has no opinion on the runtime source of Temporal β native or polyfilled, it works the same.
Edge & serverless runtimes
Works out of the box on Cloudflare Workers, Vercel Edge, and other V8-based edge runtimes that ship native Temporal support.
Converts any Temporal object into a formatted string using Moment.js-style tokens.
import { format } from 'moment-less';
// PlainDate
format(Temporal.PlainDate.from('2026-04-09'), 'YYYY-MM-DD'); // "2026-04-09"
format(Temporal.PlainDate.from('2026-04-09'), 'MMMM Do, YYYY'); // "April 9th, 2026"
format(Temporal.PlainDate.from('2026-04-09'), 'dddd, MMM D'); // "Thursday, Apr 9"
// PlainDateTime
format(Temporal.PlainDateTime.from('2026-04-09T14:05:59'), 'HH:mm:ss'); // "14:05:59"
format(Temporal.PlainDateTime.from('2026-04-09T14:05:59'), 'hh:mm A'); // "02:05 PM"
// ZonedDateTime
format(Temporal.ZonedDateTime.from('2026-04-09T14:05:59[America/New_York]'), 'YYYY-MM-DD HH:mm');
// Instant (normalized to UTC before formatting)
format(Temporal.Now.instant(), 'YYYY-MM-DD HH:mm:ss');Supported Temporal types: ZonedDateTime, PlainDateTime, PlainDate, PlainTime, Instant
Using a token that requires a field not present on the type (e.g. HH on a PlainDate) throws a descriptive error.
Returns a human-readable relative time string. Uses the native Intl.RelativeTimeFormat API β no manual duration math.
import { fromNow } from 'moment-less';
fromNow(Temporal.Now.instant().subtract({ hours: 3 })); // "3 hours ago"
fromNow(Temporal.Now.plainDateISO().add({ days: 1 })); // "tomorrow"
fromNow(Temporal.Now.instant().subtract({ years: 1 })); // "last year"
// Pass an explicit reference point instead of Temporal.Now
fromNow(somePastInstant, someOtherInstant);
// Pass a BCP 47 locale tag for localised output
fromNow(Temporal.Now.instant().subtract({ hours: 3 }), undefined, 'fr'); // "il y a 3 heures"
fromNow(Temporal.Now.instant().subtract({ hours: 3 }), undefined, 'es'); // "hace 3 horas"PlainTime is not supported (no date context to compute a diff from). Omitting locale uses the system locale.
| Token | Description | Example output |
|---|---|---|
YYYY |
4-digit year | 2026 |
YY |
2-digit year | 26 |
MMMM |
Full month name | April |
MMM |
Short month name | Apr |
MM |
Month, zero-padded | 04 |
M |
Month, no padding | 4 |
Do |
Day of month, ordinal | 9th |
DD |
Day of month, zero-padded | 09 |
D |
Day of month, no padding | 9 |
dddd |
Full weekday name | Thursday |
ddd |
Short weekday name | Thu |
HH |
Hour (24h), zero-padded | 14 |
H |
Hour (24h), no padding | 14 |
hh |
Hour (12h), zero-padded | 02 |
h |
Hour (12h), no padding | 2 |
mm |
Minute, zero-padded | 05 |
ss |
Second, zero-padded | 59 |
A |
AM / PM (uppercase) | PM |
a |
am / pm (lowercase) | pm |
Note: Tokens are matched longest-first (
MMMMbeforeMMMbeforeMMbeforeM, etc.), so there is no ambiguity. Use only non-alphabetic separators (:,/,-, spaces,,) as literal characters. Bracket escape syntax ([literal text]) is planned for v1.1.
Moment.js is in maintenance mode and recommends migrating to native platform APIs. moment-less + Temporal makes that migration nearly token-transparent:
// Before β Moment.js (72KB gzipped, mutable, legacy Date)
import moment from 'moment';
moment().format('YYYY-MM-DD');
moment().add(1, 'days').format('DD/MM/YYYY');
moment(someDate).fromNow();
// After β moment-less + Temporal (< 1KB gzipped, immutable, no legacy Date)
import { format, fromNow } from 'moment-less';
format(Temporal.Now.plainDateISO(), 'YYYY-MM-DD');
format(Temporal.Now.plainDateISO().add({ days: 1 }), 'DD/MM/YYYY');
fromNow(Temporal.Now.instant());Key differences:
- No parsing β use
Temporal.PlainDate.from('2026-04-09')instead ofmoment('2026-04-09') - No manipulation on this library β use Temporal's built-in
.add()/.subtract()/.until() - No timezone layer needed β use
Temporal.ZonedDateTimedirectly - Immutable by design β every Temporal operation returns a new object
| Library | Zero deps | Works with Temporal | Gzipped | Token format (YYYY-MM-DD) |
|---|---|---|---|---|
| moment-less | β | β native | ~936 B | β |
| moment | β | β legacy Date only | ~72 KB | β |
| date-fns | β | β legacy Date only | ~13 KB (tree-shaken) | β |
| dayjs | β | β legacy Date only | ~2.9 KB | β |
| Intl.DateTimeFormat | β | β via Instant | 0 B | β verbose |
moment-less is intentionally scoped: format Temporal objects, nothing else. Date arithmetic, parsing, and timezone conversion are Temporal's job β it already does them better than any library can.
How do I format a date as YYYY-MM-DD in JavaScript with Temporal?
Use format(Temporal.Now.plainDateISO(), 'YYYY-MM-DD') from moment-less. No setup needed beyond the import.
Is this a Moment.js replacement?
For formatting and relative time, yes β the token syntax is intentionally compatible. For date math and parsing, use the Temporal API directly (.add(), .subtract(), .from()). Temporal replaces those parts of Moment natively.
Does this work in the browser?
Yes. It also works in Node.js, Deno, Bun, and V8-based edge runtimes (Cloudflare Workers, Vercel Edge). moment-less uses no platform-specific APIs. See Runtime & browser support for version details.
Does this polyfill the Temporal API?
No. moment-less is a formatting utility only. Use temporal-polyfill or @js-temporal/polyfill for environments that don't have native Temporal yet.
Does it support TypeScript?
Yes, fully. Types are bundled β no separate @types/moment-less package needed. All Temporal types are inferred correctly.
Is it tree-shakeable?
Yes. format and fromNow are independent named exports. Importing only format produces a bundle under 600B gzipped.
Why not just use Intl.DateTimeFormat?
Intl.DateTimeFormat is great for locale-sensitive display strings but unwieldy for structural formats like YYYY-MM-DD HH:mm:ss. You'd need to call .formatToParts(), reduce the parts array, and handle zero-padding yourself. moment-less makes that a one-liner.
| Export | Raw | Gzipped |
|---|---|---|
Full (format + fromNow) ESM |
~2.9 KB | ~936 B |
format only (tree-shaken) |
~1.5 KB | ~550 B |
Zero production dependencies.
Issues and pull requests are welcome. Run the test suite with:
pnpm test
pnpm test:coverageMIT