https://github.com/didrod205/tempora
Human-friendly time in ~1KB — relative time ('3 minutes ago'), duration format & parse. Zero-dependency, localized via Intl.
https://github.com/didrod205/tempora
duration format-duration humanize i18n intl parse-duration relative-time time timeago typescript
Last synced: about 1 month ago
JSON representation
Human-friendly time in ~1KB — relative time ('3 minutes ago'), duration format & parse. Zero-dependency, localized via Intl.
- Host: GitHub
- URL: https://github.com/didrod205/tempora
- Owner: didrod205
- License: mit
- Created: 2026-05-29T06:29:18.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-29T07:13:59.000Z (about 2 months ago)
- Last Synced: 2026-05-29T08:26:35.310Z (about 2 months ago)
- Topics: duration, format-duration, humanize, i18n, intl, parse-duration, relative-time, time, timeago, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/tempora-js
- Size: 37.1 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# tempora
**Human-friendly time, in ~1 KB. Relative time, duration formatting & parsing — zero dependencies, localized via `Intl`.**
[](https://www.npmjs.com/package/tempora-js)
[](https://bundlephobia.com/package/tempora-js)
[](https://github.com/didrod205/tempora/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/tempora-js)
[](./LICENSE)
Three tiny functions for the time formatting every app ends up needing — without
pulling in a date library or shipping locale data.
```ts
import { timeAgo, formatDuration, parseDuration } from "tempora-js";
timeAgo(Date.now() - 3 * 60_000); // "3 minutes ago"
timeAgo(then, { locale: "ko" }); // "3분 전"
formatDuration(90_061_000); // "1d 1h 1m 1s"
parseDuration("1h30m"); // 5_400_000
```
---
## Why tempora?
- 🪶 **Zero dependencies.** ~1 KB gzipped. No locale files to ship.
- 🌍 **Localized for free.** `timeAgo` uses the built-in `Intl.RelativeTimeFormat`, so every language the runtime knows just works.
- 🔁 **Round-trips.** `parseDuration` and `formatDuration` are two ends of the same pipe.
- 🧩 **Three focused functions.** No giant API surface, no moment-style mutable objects.
- ⚙️ **Runs everywhere.** Node 18+, Deno, Bun, Cloudflare Workers and the browser.
- 🛡️ **Type-safe.** Written in TypeScript, ships full declarations.
## Install
```bash
npm install tempora-js
# or: pnpm add tempora-js / yarn add tempora-js / bun add tempora-js
```
> The npm package is published as **`tempora-js`** (the name `tempora` was already reserved on npm). Everything you import stays the same.
Ships ESM **and** CommonJS:
```ts
import { timeAgo } from "tempora-js"; // ESM / TypeScript
const { timeAgo } = require("tempora-js"); // CommonJS
```
## CLI
```bash
npx tempora-js ago 2024-01-01 # "2 years ago"
npx tempora-js duration 5400000 # "1h 30m" (--long → "1 hour, 30 minutes")
npx tempora-js parse 1h30m # 5400000 (--human → "1m 30s")
npx tempora-js ago "2030-01-01" --locale ko # "5년 후"
```
The bundled command is **`tempora`** (run `npx tempora-js `). Commands:
`ago`, `duration`, `parse`. Options: `--locale`, `--long`, `--human`.
## Usage
### `timeAgo` — relative time
```ts
timeAgo(Date.now() - 3 * 60_000); // "3 minutes ago"
timeAgo(Date.now() + 2 * 86_400_000); // "in 2 days"
timeAgo("2026-01-01T00:00:00Z"); // "last year" (relative to now)
// Localized — any BCP-47 locale the runtime supports
timeAgo(then, { locale: "ko" }); // "3분 전"
timeAgo(then, { locale: "es" }); // "hace 3 minutos"
// Tweak the wording
timeAgo(yesterday, { numeric: "always" }); // "1 day ago" (instead of "yesterday")
timeAgo(then, { style: "short" }); // "3 min. ago"
// Test deterministically by pinning "now"
timeAgo(t, { now: fixedTimestamp });
```
Accepts a `Date`, a millisecond timestamp, or any string `Date.parse` understands.
### `formatDuration` — milliseconds → readable
```ts
formatDuration(90_061_000); // "1d 1h 1m 1s"
formatDuration(5_400_000); // "1h 30m"
formatDuration(90_061_000, { largest: 2 }); // "1d 1h"
formatDuration(5_400_000, { long: true }); // "1 hour, 30 minutes"
formatDuration(elapsed, { units: ["m", "s"] }); // "75m 30s"
```
### `parseDuration` — readable → milliseconds
```ts
parseDuration("1h30m"); // 5_400_000
parseDuration("2.5h"); // 9_000_000
parseDuration("1h 30m 5s"); // 5_405_000
parseDuration("-90s"); // -90_000
parseDuration("500"); // 500 (bare number = milliseconds)
parseDuration("1 hour"); // 3_600_000
```
Throws a `TypeError` for input it can't understand, so misconfiguration fails loudly.
## API
### `timeAgo(input, options?) => string`
| Option | Type | Default | Description |
| --------- | ----------------------------- | ---------------- | ------------------------------------------------------ |
| `now` | `Date \| number` | `Date.now()` | Reference point for the comparison. |
| `locale` | `string \| string[]` | runtime locale | BCP-47 locale(s) for `Intl.RelativeTimeFormat`. |
| `numeric` | `"auto" \| "always"` | `"auto"` | `"auto"` allows "yesterday"/"tomorrow". |
| `style` | `"long" \| "short" \| "narrow"` | `"long"` | Width of the output. |
### `formatDuration(ms, options?) => string`
| Option | Type | Default | Description |
| ----------- | ---------------- | -------------------- | ------------------------------------------------- |
| `units` | `DurationUnit[]` | `["d","h","m","s"]` | Units to break into, largest → smallest. |
| `largest` | `number` | all | Show at most N largest non-zero units. |
| `long` | `boolean` | `false` | Long labels ("1 hour") vs short ("1h"). |
| `round` | `boolean` | `true` | Round the smallest displayed unit. |
| `separator` | `string` | `" "` / `", "` | Separator between parts. |
`DurationUnit` is one of `"w" | "d" | "h" | "m" | "s" | "ms"`.
### `parseDuration(input) => number`
Parses a duration string into milliseconds. A bare number is treated as
milliseconds. Throws `TypeError` on unrecognized input.
## Comparison
| | `tempora` | `moment` / `dayjs` | `humanize-duration` + `timeago.js` |
| ------------------------ | :-------: | :----------------: | :--------------------------------: |
| Zero dependencies | ✅ | ⚠️ | ✅ |
| Relative time | ✅ | ✅ | ✅ |
| Duration format + parse | ✅ | ⚠️ | ⚠️ (two libs) |
| Localized without bundle | ✅ | ❌ | ❌ |
| ~1 KB gzipped | ✅ | ❌ | ❌ |
## Contributing
Contributions are very welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md)
and our [Code of Conduct](./CODE_OF_CONDUCT.md).
```bash
git clone https://github.com/didrod205/tempora.git
cd tempora
npm install
npm test
```
## 💖 Sponsor
`tempora` is free and MIT-licensed, built and maintained in spare time. If it
saved you from reaching for a heavy date library, please consider supporting it —
every bit helps keep the project healthy.
- ⭐ **Star this repo** — the simplest, free way to help others discover it.
- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring support.
> Sponsoring? Open an issue and we'll add your name/logo here. Thank you! 🙏
## License
[MIT](./LICENSE) © tempora contributors