@ffgflash/time
v0.1.0
Published
A zero-dependency, high-performance calendar/time library using pure mathematical algorithms instead of the Date API
Downloads
163
Maintainers
Readme
@ffgflash/time
A zero-dependency, high-performance date/time library for TypeScript. Every field is computed
with closed-form calendar math instead of the built-in Date API.
Features
- Zero runtime dependencies, fully tree-shakeable ESM
- O(1) everywhere — pure math, no loops or lookup tables
- Works in Node, Deno, Bun, and browsers, no bundler required
Date-compatible conventions (0-indexed months,Sun=0weekdays)- Two APIs: an all-in-one
Timeclass, and allocation-free standalone functions
Install
npm install @ffgflash/time
pnpm add @ffgflash/time
yarn add @ffgflash/time
bun add @ffgflash/time
deno add @ffgflash/timeQuick start
import { Time } from '@ffgflash/time'
const t = Time.fromEpoch(Date.now())
t.year
t.month
t.day
t.weekday
t.hour
t.minute
t.second
t.millisecond
t.dayOfYear
Time.fromCalendar(2024, 1, 29, 12, 30) // month is 0-indexed
Time.now()
t.addMonths(1) // mutates in place, returns the new epoch msAPI
Time
An eagerly-computed calendar/time snapshot.
Time.fromEpoch(ms);
Time.fromEpochSeconds(seconds);
Time.fromCalendar(year, month, day, hour?, minute?, second?, millisecond?);
Time.now();
t.toEpoch();
t.toEpochSeconds();year, month, day, weekday, hour, minute, second, millisecond, and dayOfYear are
read-only getters backed by private state — assigning to them throws a TypeError.
Mutate with Date-style set*/add* methods, which update the instance in place and return the
new epoch ms:
t.setYear(2025)
t.setMonth(1)
t.setDay(1)
t.setHour(12)
t.addYears(1)
t.addMonths(1)
t.addWeeks(1)
t.addDays(1)Out-of-range values roll over rather than clamp (e.g. Jan 31 + 1 month → Mar 2/3), matching Date.
Standalone functions
For hot paths that don't need a full Time instance:
import {
getYear,
getMonth,
getDay,
getWeekday,
getDayOfYear,
getHour,
getMinute,
getSecond,
getMillisecond,
isLeapYear,
} from '@ffgflash/time'
import {
addYears,
addMonths,
addWeeks,
addDays,
addHours,
addMinutes,
addSeconds,
addMilliseconds,
} from '@ffgflash/time'
getYear(ms)
getWeekday(ms)
isLeapYear(ms)
addMonths(ms, 1) // returns a new epoch ms, doesn't mutate anythingThese take epoch ms directly and never construct a Time or a Date. If you need several fields
from the same timestamp, use Time.fromEpoch(ms) instead — each standalone get* call
independently re-derives the decomposition.
Conventions
- Month is 0-indexed (
Jan=0..Dec=11), matchingDate.getMonth(). - Weekday is
Sun=0..Sat=6, matchingDate.getDay(). - All calculations are UTC / proleptic Gregorian — no timezones, no calendar cutover.
- Safe range is roughly ±285,616 years from 1970 (
Number.MAX_SAFE_INTEGERms).
How it works
- Calendar decomposition uses Howard Hinnant's
civil_from_days/days_from_civilalgorithm, which exploits the 400-year Gregorian leap cycle to convert between a day count and a calendar date with no loops or lookup tables. - Weekday falls out of the day count for free (
z mod 7). Zeller's Congruence is also included as a documented, independently cross-checked alternative. - Day-of-year uses a linear curve-fit formula instead of a month-length table.
See the source comments in src/calendar/ and src/clock/ for the full derivations and edge
cases (negative years, century leap-year exceptions, etc).
src/
index.ts, Time.ts, fields.ts, arithmetic.ts # public API
shared/ constants.ts, math.ts # shared utilities
calendar/ leapYear.ts, civil.ts, dayOfYear.ts, weekday.ts
clock/ timeOfDay.ts, epochDays.tsDevelopment
pnpm install
pnpm test # vitest
pnpm typecheck # tsc --noEmit
pnpm build # emit dist/ (ESM + .d.ts)License
MIT
