A fast, modern, dependency-free datetime engine for JavaScript —
with strict UTC parsing, timezone support, locale formatting, durations,
relative time, calendar outputs, and flexible tokens, date range, and age calculation.
See Release notes
Ideal for applications needing reliable, predictable, and readable date manipulation without heavy dependencies.
Modern JavaScript apps need predictable and readable date operations — not massive libraries. Waktoo focuses on the essentials:
No overhead. No polyfills. No bloated parsing engine.
Your code runs the same on every machine — CI, server, client, container.
No surprise timezone shifts.
Full timezone formatting + abbreviations & offsets.
waktoo().tz("Asia/Tokyo").add(1, "day").format("DD MMM YYYY");- Introduction
- Why Waktoo
- Installation
- Importing
- Quick Usage
- UTC Default Behavior
- Timezone-Aware Getters
- Date Arithmetic
- Diff
- Range
- Age
- Duration
- Duration.format()
- Relative Time
- Calendar Output
- Token-Based Date Parsing
- Timezone Formatting
- Supported Format Tokens
- Supported Timezone Abbreviations
- API Reference Index
- License
npm install waktooconst waktoo = require("waktoo");import waktoo from "waktoo";waktoo().format();
// → "2025-12-01T00:00:00.000Z"
waktoo().format("DD MMM YYYY");
// → "16 Nov 2025"
waktoo("2025-12-01").tz("Asia/Jakarta").format("DD MMM YYYY HH:mm Z");
// → "02 Des 2025 07:00 +07:00"
waktoo().locale("id-ID").format("DD MMMM YYYY");
// → "16 November 2025"
waktoo("2025-12-31").fromNow();
// → "in 1 month"Waktoo stores everything in strict UTC.
This means:
waktoo().hour()→ UTC hourwaktoo().date()→ UTC datewaktoo().day()→ UTC weekday
This guarantees reproducibility across all machines.
waktoo("2025-12-01T00:00:00Z").tz("Asia/Jakarta").hour();
// → 7
waktoo("2025-12-01T00:00:00Z").tz("America/New_York").date();
// → 30 (previous day)Any of these reflect the chosen timezone:
year()month()date()day()hour()minute()second()
waktoo("2025-01-01").add(3, "days");
waktoo("2025-01-01").add({ months: 2, hours: 5 });
waktoo("2025-01-10").subtract(1, "month");const a = waktoo("2025-12-25");
const b = waktoo("2025-12-10");
a.diff(b, "days"); // 15
a.diff(b, "days", true); // 15.0 (float)Human-readable, localized difference between two dates.
waktoo("2023-01-01").range("2025-03-05");
// → "2 years 2 months 4 days"waktoo("2023-01-01").range("2025-01-01", "day");
// → "731 days"
waktoo("2023-01-01").range("2023-01-03", "minute");
// → "2880 minutes"waktoo("2023-01-01").locale("id-ID").range("2025-01-01");
// → "2 tahun"Age is always:
- absolute
- localized
- humanized by default
- still convertible numerically with
.asDays()etc
waktoo("2023-01-01").age();
// → "2 years"waktoo("2024-12-01").age();
// → "1 month"waktoo("2023-01-01").locale("id-ID").age();
// → "2 tahun"waktoo("2023-01-01").age().asDays();
// → 731
waktoo("2023-01-01").age().asMonths();
// → 24waktoo.duration(3, "hours").asMinutes();
// → 180
waktoo.duration({ hours: 2, minutes: 30 }).humanize();
// → "in 2 hours"Durations support:
- numeric conversions (
.asDays(),.asHours(), etc.) - human-readable output (
.humanize()) - suffixless output (
.humanize(false)) - localization (
.locale("id-ID")) - deterministic normalized formatting (
.format())
.format() always returns a fully normalized breakdown, using localized unit names.
Object-based durations:
waktoo
.duration({
years: 2,
months: 3,
days: 5,
hours: 4,
minutes: 30,
seconds: 10,
})
.format();
// → "2 years 3 months 5 days 4 hours 30 minutes 10 seconds"Numeric or mixed durations normalize automatically:
waktoo.duration({ minutes: 90 }).format();
// → "1 hour 30 minutes"Localized:
waktoo.duration({ minutes: 90 }).locale("id-ID").format();
// → "1 jam 30 menit"waktoo("2025-12-31").fromNow();
// → "in 1 month"
waktoo("2025-12-01").from("2025-11-25");
// → "in 6 days"Localized:
waktoo().add(1, "hour").locale("id-ID").fromNow();
// → "dalam 1 jam"
waktoo().subtract(2, "hours").locale("id-ID").fromNow();
// → "2 jam yang lalu"waktoo("2025-12-31").calendar();
// → "Today at 15:00"
// → "Tomorrow at 11:00"
// → "Yesterday at 22:00"
// → "Sunday at 09:00"
// → "31/12/2025"Localized:
waktoo("2025-12-31").locale("id-ID").calendar();
// → "Hari ini pukul 15:00"Supports patterns similar to Moment:
Example:
waktoo("25/12/2025", "DD/MM/YYYY").format("YYYY-MM-DD");
// → "2025-12-25"
waktoo().format("D MMM YYYY HH:mm");
// → "1 January 2025 12:30"Localized:
waktoo().locale("id-ID").format("D MMM YYYY HH:mm");
// → "1 Januari 2025 12:30"Strict mode rejects wrong formats:
waktoo("999/99/9999", "DD/MM/YYYY");
// Throws invalid date errorWaktoo supports:
Z→ "+07:00"ZZ→ "+0700"z→ "WIB"zzz→ "WIB" (long/variant)
waktoo().tz("Asia/Jakarta").format("DD MMM YYYY HH:mm:ss Z z");
// → "16 Nov 2025 23:00:00 +07:00 WIB"| Token | Description |
|---|---|
| YYYY | 4-digit year |
| YY | 2-digit year |
| MMMM | Month long |
| MMM | Month short |
| MM | 01–12 |
| M | 1–12 |
| DD | 01–31 |
| D | 1–31 |
| dddd | Weekday long |
| ddd | Weekday short |
| HH | 00–23 |
| H | 0–23 |
| hh | 01–12 |
| h | 1–12 |
| mm | 00–59 |
| m | 0–59 |
| ss | 00–59 |
| s | 0–59 |
| A | AM/PM |
| a | am/pm |
| Z | +07:00 |
| ZZ | +0700 |
| z | timezone abbr |
| zzz | long/display TZ abbr |
Waktoo includes an expanded global TZ abbreviation map:
| Timezone | Abbrev |
|---|---|
| Asia/Jakarta | WIB |
| Asia/Makassar | WITA |
| Asia/Jayapura | WIT |
| America/New_York | EST/EDT |
| America/Chicago | CST/CDT |
| America/Denver | MST/MDT |
| America/Los_Angeles | PST/PDT |
| America/Phoenix | MST |
| America/Toronto | EST |
| America/Vancouver | PST |
| America/Sao_Paulo | BRT |
| Europe/London | GMT/BST |
| Europe/Dublin | GMT/IST |
| Europe/Paris | CET/CEST |
| Europe/Berlin | CET/CEST |
| Europe/Madrid | CET/CEST |
| Europe/Rome | CET/CEST |
| Europe/Moscow | MSK |
| Africa/Johannesburg | SAST |
| Africa/Cairo | EET |
| Africa/Nairobi | EAT |
| Asia/Tokyo | JST |
| Asia/Seoul | KST |
| Asia/Shanghai | CST |
| Asia/Hong_Kong | HKT |
| Asia/Singapore | SGT |
| Asia/Kuala_Lumpur | MYT |
| Asia/Bangkok | ICT |
| Australia/Sydney | AEST/AEDT |
| Australia/Perth | AWST |
| Pacific/Auckland | NZST/NZDT |
Fallback behavior:
If a timezone is missing an abbreviation, waktoo uses the last segment of the TZ name (e.g., "Asia/Colombo" → "Colombo").
waktoo(input?)waktoo().format(pattern)waktoo().locale(localeCode)waktoo().tz(timezone)
year()month()date()day()hour()minute()second()
add(value, unit)add({ parts })subtract(value, unit)subtract({ parts })diff(otherDate, unit?, float?)
range(date2)range(date2, unit)
age().asSeconds().asMinutes().asHours().asDays().asWeeks().asMonths().asYears()
fromNow()from(otherWaktooInstance)
calendar()
waktoo.duration(number, unit)waktoo.duration({ parts }).locale(localeCode).asMilliseconds().asSeconds().asMinutes().asHours().asDays().asWeeks().asMonths().asYears().humanize(withSuffix = true).format()