Everything you reach for, none of the gotchas
Immutable & chainable
Every method returns a new instance, so shared dates never mutate under your components.
Truly timezone-aware
Reading, setting, add, startOf and week math all resolve through any IANA
zone — DST-safe, no plugins.
Token formatting & parsing
formatPattern() and parse() cover the full core, advanced and localized
token tables.
Durations
duration() with locale-aware humanize(), as*() totals and ISO
8601 parsing & output.
Live time-ago
timeAgoLive() is a dependency-free self-rescheduling stream for any framework.
Tiny & typed
~8 kB gzipped, zero framework deps, ships with .d.ts declarations.
Quick start
Format & manipulate
import { DateTick } from 'datetick'; const d = new DateTick('en', 'UTC', '2026-01-31T12:00:00Z'); d.add(1, 'month').toISOString(); // 2026-02-28T12:00:00.000Z d.formatPattern('DD/MM/YYYY'); // '31/01/2026' d.startOf('week').formatPattern('ddd'); // 'Sun'
Parse, diff & durations
const a = DateTick.parse('25/06/2026', 'DD/MM/YYYY'); const span = a.diff('2026-01-01'); // ms DateTick.duration(span).humanize(); // '5 months' DateTick.duration(-3, 'day').humanize(true); // '3 days ago' // Live updates (Angular/React/Vue): const stop = d.timeAgoSubscribe(console.log); stop();
Documentation
Installation
npm install datetick
Both ESM and a UMD build (browser global datetick) are shipped, with bundled TypeScript
types and no runtime dependencies.
Creating a DateTick
Use the lowercase datetick() factory, or the DateTick class directly —
they’re interchangeable and both immutable.
import datetick, { DateTick } from 'datetick'; // factory(date?, { locale?, timezone?, weekStartsOn? }) datetick('2026-06-25T19:23Z', { timezone: 'Asia/Tokyo' }); datetick(); // now datetick.utc('2026-06-25'); // timezone fixed to UTC datetick.unix(1782760980); // from Unix seconds datetick.guessTimezone(); // runtime IANA timezone // new DateTick(locale?, timezone?, date?, weekStartsOn?) new DateTick('en', 'UTC', '2026-06-25T19:23Z', 1);
Inputs may be a Date, an ISO/parsable string, another DateTick,
a parts object, or a parts array. Every calendar operation reflects the wall clock in the configured
timezone, while the underlying instant stays absolute and DST-safe.
Getters & setters
Call with no argument to get a number; pass a value to set, returning a new instance.
year(value?) |
Full year (setting clamps the day, e.g. Feb 29 → non-leap year → Feb 28) |
month(value?) |
Month, 0–11 (setting clamps the day, e.g. Mar 31 → Feb → Feb 28)
|
date(value?) |
Day of the month, 1–31 |
day(value?) |
Day of week, 0 (Sun) – 6 (Sat) |
hour(value?) · minute(value?) |
Hours / minutes |
second(value?) · millisecond(value?) |
Seconds / milliseconds |
get(unit) |
Read any unit, incl. dayOfYear, week, isoWeek,
quarter, daysInMonth
|
set(unit, value) |
Set any settable unit |
Day.js-style plural aliases are also available: years,
months, dates, days, hours, minutes,
seconds, milliseconds (get/set) and weeks() (get). Each simply
forwards to its singular counterpart.
Manipulation
add(amount, unit) |
Add time (millisecond→year, incl. week,
quarter). Negative subtracts; month/year additions clamp the day.
|
subtract(amount, unit) |
Inverse of add |
startOf(unit) · endOf(unit) |
Snap to the start/end of
year/quarter/month/week/day/hour/minute/second
|
clone() |
Copy the instance |
withDate · withLocale · withTimezone ·
withWeekStart
|
Derive a new instance changing one aspect |
Comparison
diff(date, unit?, precise?) |
Difference (default millisecond), truncated toward zero — for
month/quarter/year only whole elapsed units count (Jun 15
→ next Jun 14 is 0 years). Pass precise = true for the fractional
value
|
diffCalendar(date, unit?) |
Calendar-boundary difference for day/week/month |
isBefore(date, unit?) · isAfter(date, unit?) |
Strict ordering, optionally truncated to a unit |
isSame(date, unit?) |
Equality, optionally truncated to a unit |
isSameOrBefore · isSameOrAfter |
Inclusive ordering |
isBetween(start, end, inclusivity?) |
Range check. inclusivity is a boolean or bracket notation '()' /
'[]' / '[)' / '(]' ([ = inclusive edge,
default exclusive)
|
isToday() / isTomorrow() / isYesterday() |
Relative calendar-day checks |
isValid() |
Whether the wrapped date is valid |
All date inputs accept a Date, a string, another DateTick, a
parts object, or a parts array.
Relative time
Locale-aware “time ago” strings via Intl.RelativeTimeFormat, plus a dependency-free live
stream.
d.timeAgo(); // '3 minutes ago' d.fromNow(); // same, Day.js-style d.from(other); d.to(other); d.toNow(); d.calendar(); // 'Today at 7:23 PM' // Live, self-rescheduling stream — works in any framework const stop = d.timeAgoSubscribe((label) => render(label)); // Tune when it steps up from one unit to the next d.timeAgo(undefined, { second: 120 }); // keep seconds up to 119
timeAgo(options?, thresholds?) accepts an optional
{ second, minute, hour, day, month } threshold override (defaults reproduce
60 / 60 / 24 / 30 / 12); the same thresholds flow through
timeAgoLive() and timeAgoSubscribe().
Calendar info
dayOfYear(), week(), weekYear(), weeksInYear(),
isoDay(), isoWeek(), isoWeekYear(),
isoWeeksInYear(), quarter(), daysInMonth(),
isLeapYear(), isToday(), isTomorrow(),
isYesterday(), ordinal(), calendarMonth(). Week numbering and
startOf('week') honour the configured weekStartsOn.
calendarMonth({ selected, dateFormat }) returns localized weekday labels plus padded date
cells for building date pickers and month views. Each cell keeps a stable isoDate plus a
locale-aware display formatted value.
Formatting
Two complementary ways to format, both resolved in the configured timezone:
// 1) Intl-powered presets / options (locale-aware) d.format('short'); d.format({ dateStyle: 'long' }); // 2) Token patterns d.formatPattern('dddd, MMMM D, YYYY h:mm A'); d.formatPattern('YYYY-MM-DD[T]HH:mm:ssZ');
Presets: short, medium, long, full,
dateOnly, timeOnly, weekdayTime, isoStyle12h,
isoStyle24h.
Format tokens
Wrap literal text in [brackets]. Names, meridiem and the localized L-family
all honour the instance locale — including its field order and separators.
| Token | Output |
|---|---|
YYYY · YY |
Year (4-digit / 2-digit) |
MMMM · MMM · MM · M |
Month name (full/short) / number (padded/plain) |
DD · D · Do |
Day of month (padded / plain / ordinal — English by default, see ordinal below)
|
dddd · ddd · dd · d |
Weekday name (full/short/narrow) / number |
HH · H · hh · h |
Hour, 24h / 12h (padded / plain) |
kk · k |
Hour 1–24 (padded / plain) |
mm · m · ss · s · SSS ·
SS · S
|
Minutes / seconds / fractional seconds (SSS = milliseconds, SS =
hundredths, S = tenths)
|
A · a |
Meridiem, locale-aware (AM/PM, π.μ./μ.μ., 上午/下午)
|
Z · ZZ |
UTC offset +HH:mm / +HHmm |
Q · w/ww/wo ·
W/WW/Wo
|
Quarter · week of year (+ ordinal) · ISO week (+ ordinal) |
X · x |
Unix seconds · Unix milliseconds |
L LL LLL LLLL · l ll lll llll · LT LTS |
Localized date/time — field order & separators follow the locale |
datetick('2026-06-25').formatPattern('L'); // '06/25/2026' (en) datetick('2026-06-25', { locale: 'en-GB' }).formatPattern('L'); // '25/06/2026' datetick('2026-06-25', { locale: 'de' }).formatPattern('LL'); // '25. Juni 2026'
Parsing
DateTick.parse(input, pattern, locale?, timezone?) (also
datetick.parse) reads the wall-clock values in the given timezone and returns a
DateTick. Throws if the input doesn’t match. Parsing mirrors the
formatPattern token set, including fractional-second tokens
(S/SS/SSS) and UTC-offset tokens
(Z/ZZ).
datetick.parse('25/06/2026 22:23', 'DD/MM/YYYY HH:mm', 'en', 'UTC'); datetick.parse('Jun 25, 2026 10:23 PM', 'MMM D, YYYY h:mm A'); // Fractional seconds are scaled by width: .9 = 900ms, .09 = 90ms datetick.parse('22:23:04.9', 'HH:mm:ss.S').millisecond(); // 900 // A parsed Z/ZZ offset fixes the absolute instant, overriding the timezone argument datetick.parse('2026-06-25 12:00 +05:30', 'YYYY-MM-DD HH:mm Z', 'en', 'UTC').toISOString(); // '2026-06-25T06:30:00.000Z' // Localized month names, meridiem and L-family layouts round-trip datetick.parse('25/06/2026', 'L', 'fr', 'UTC'); datetick.parse('2026-06-25 下午 07:23', 'YYYY-MM-DD A hh:mm', 'zh', 'UTC');
Durations
datetick.duration(2, 'hour').humanize(); // '2 hours' datetick.duration(-3, 'day').humanize(true); // '3 days ago' datetick.duration({ days: 1, hours: 2 }).asMinutes(); // 1560 datetick.duration('P1Y2M10DT2H30M').asDays(); // parse an ISO-8601 duration datetick.duration(span).toISOString(); // 'P1Y2M3DT4H5M6S'
Create a Duration from a number + unit, a components object, or an
ISO-8601 string (e.g. 'PT1H30M', fractional and signed accepted). It’s
immutable: as{Milliseconds…Years}() totals, component getters
years()…milliseconds(), add/subtract,
humanize(withSuffix?), format(pattern?),
toISOString()/toJSON(), valueOf(). Fixed ratios: 1 month = 30
days, 1 year = 365 days.
Conversion
toDate(), toArray(), toObject(), toISOString(),
toJSON() (so JSON.stringify works), toString(),
unix() (seconds), valueOf() (ms — enables +instance and
</>).
Timezone conversion keeps the same instant, changing only the zone the wall clock resolves through:
utc() and local() (runtime zone) return a new DateTick, while
utcOffset() returns the configured zone's offset in minutes (positive east of UTC).
Locale data
localeData() exposes locale metadata resolved through Intl — the
Day.js localeData() equivalent, with no locale files to import.
const ld = datetick('2026-06-25', { locale: 'fr' }).localeData(); ld.months(); // ['janvier', 'février', …] ld.weekdays(); // Sunday-based: ['dimanche', 'lundi', …] ld.weekdaysShort(); ld.weekdaysMin(); ld.firstDayOfWeek(); // configured weekStartsOn ld.meridiems(); // { am, pm } ld.meridiem(15); // 'PM' (locale-aware) ld.ordinal(1); // '1st' (or your ordinal override)
Also: months()/monthsShort(). Because everything derives from
Intl, coverage matches the runtime’s CLDR data — hundreds of locales, no bundles.
Static helpers
DateTick.guessTimezone(), DateTick.isValid(date),
DateTick.isDateTick(value), DateTick.min(...dates),
DateTick.max(...dates), DateTick.parse(...),
DateTick.duration(...) — all also available on the datetick factory (plus
datetick.utc, datetick.unix and the
datetick.isDuration(value) type guard, also exported as
Duration.isDuration).
App-wide defaults
datetick.withDefaults(options) returns a new, independent factory pre-bound
to the given { locale, timezone, weekStartsOn, ordinal }. It mutates nothing global —
`datetick` itself is unaffected, so it's safe under SSR and concurrent requests.
const frDateTick = datetick.withDefaults({ locale: 'fr', timezone: 'Europe/Paris' });
frDateTick('2026-06-25').format('long'); // French, Paris
frDateTick.duration(2, 'day').humanize(); // '2 jours'
datetick('2026-06-25').format('long'); // unaffected, still 'en'
Month/weekday names, the L-family layout and meridiem are already localized through
Intl. The one thing Intl can’t supply is ordinals, so pass
an ordinal function to localize ordinal() and the
Do/wo/Wo tokens (no global updateLocale needed):
const el = datetick.withDefaults({ locale: 'el', ordinal: (n) => `${n}η` });
el('2026-06-01').formatPattern('Do'); // '1η' (meridiem already localized by Intl)
Defaults merge progressively when chained, and a per-call option still wins over the factory's defaults:
const frParisMonday = frDateTick.withDefaults({ weekStartsOn: 1 });
frParisMonday('2026-06-25', { locale: 'de' }).locale(); // 'de' — explicit override wins
"App-wide" just means creating the scoped factory once and importing that instance
everywhere instead of the base datetick — nothing global to coordinate:
// src/app-datetick.ts — one shared module, identical in any framework import datetick from 'datetick'; export const appDateTick = datetick.withDefaults({ locale: 'fr', timezone: 'Europe/Paris' }); // anywhere else import { appDateTick } from './app-datetick'; appDateTick('2026-06-25').format('long');
If the locale can change at runtime (a language switcher) and components need to react to it, hold the current factory in your framework's own reactivity primitive instead — see the Angular, React and Vue sections below.
Vanilla JS / TypeScript
import datetick from 'datetick'; document.querySelector('#published').textContent = datetick('2024-01-01').format('long');
Via CDN (UMD global datetick):
<script src="https://unpkg.com/datetick/dist/index.umd.min.js"></script> <script> // The UMD global exposes the namespace: datetick.datetick(), datetick.DateTick, datetick.Duration console.log(datetick.datetick('2024-01-01').format('medium')); </script>
Angular
An impure pipe that subscribes to timeAgoLive() and updates itself:
import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core'; import datetick from 'datetick'; @Pipe({ name: 'timeAgo', standalone: true, pure: false }) export class TimeAgoPipe implements PipeTransform, OnDestroy { private label = ''; private lastValue?: Date | string; private stop?: () => void; constructor(private cdr: ChangeDetectorRef) {} transform(value: Date | string): string { if (value !== this.lastValue) { this.stop?.(); this.lastValue = value; this.stop = datetick(value).timeAgoSubscribe((label) => { this.label = label; this.cdr.markForCheck(); }); } return this.label; } ngOnDestroy(): void { this.stop?.(); } }
<span>{{ post.createdAt | timeAgo }}</span>
App-wide, switchable locale — a service exposing a signal<DateTickFactory>:
import { Injectable, Signal, signal } from '@angular/core'; import datetick, { DateTickFactory } from 'datetick'; @Injectable({ providedIn: 'root' }) export class AppDateTickService { private readonly _datetick = signal<DateTickFactory>(datetick.withDefaults({ locale: 'en' })); readonly datetick: Signal<DateTickFactory> = this._datetick.asReadonly(); setLocale(locale: string): void { this._datetick.set(datetick.withDefaults({ locale })); } }
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-post', template: `<time>{{ appDateTick.datetick()(createdAt).format('long') }}</time>` }) export class PostComponent { @Input({ required: true }) createdAt!: string; constructor(readonly appDateTick: AppDateTickService) {} }
React
A hook that subscribes to timeAgoLive():
import { useEffect, useState } from 'react'; import datetick from 'datetick'; export const TimeAgo = ({ date }: { date: string | Date }) => { const [label, setLabel] = useState(''); useEffect(() => { return datetick(date).timeAgoSubscribe(setLabel); }, [date]); return <time>{label}</time>; };
App-wide, switchable locale — a Context provider + hook:
import { createContext, useContext, useMemo, type ReactNode } from 'react'; import datetick, { DateTickFactory } from 'datetick'; const DateTickContext = createContext<DateTickFactory>(datetick); export const DateTickProvider = ({ locale, children }: { locale: string; children: ReactNode }) => { const value = useMemo(() => datetick.withDefaults({ locale }), [locale]); return <DateTickContext.Provider value={value}>{children}</DateTickContext.Provider>; }; export const useAppDateTick = (): DateTickFactory => useContext(DateTickContext); // Switching language: render <DateTickProvider locale={locale}> with new state from useState/useReducer
const PublishedDate = ({ date }: { date: string }) => { const appDateTick = useAppDateTick(); return <time>{appDateTick(date).format('long')}</time>; };
Vue 3
A composable that subscribes to timeAgoLive():
<script setup lang="ts"> import { ref, onMounted, onUnmounted } from 'vue'; import datetick from 'datetick'; const props = defineProps<{ date: string | Date }>(); const label = ref(''); let stop: (() => void) | undefined; onMounted(() => { stop = datetick(props.date).timeAgoSubscribe((v) => (label.value = v)); }); onUnmounted(() => stop?.()); </script> <template> <time>{{ label }}</time> </template>
App-wide, switchable locale — provide/inject + a composable:
// app-datetick.ts import { inject, ref, type App, type Ref } from 'vue'; import datetick, { DateTickFactory } from 'datetick'; const DATETICK_KEY = Symbol('appDateTick'); export const provideAppDateTick = (app: App, initialLocale: string): Ref<DateTickFactory> => { const current = ref(datetick.withDefaults({ locale: initialLocale })) as Ref<DateTickFactory>; app.provide(DATETICK_KEY, current); return current; // current.value = datetick.withDefaults({ locale: next }) to switch language }; export const useAppDateTick = (): Ref<DateTickFactory> => inject(DATETICK_KEY) as Ref<DateTickFactory>;
<script setup lang="ts"> import { useAppDateTick } from './app-datetick'; const { date } = defineProps<{ date: string }>(); const appDateTick = useAppDateTick(); </script> <template> <time>{{ appDateTick(date).format('long') }}</time> </template>
Live playground
Everything below runs the real library in your browser. Pick a date, locale, timezone and week start.