Node, Deno and Bun
@taqwim/core is a conversion table and the arithmetic on top of it. It touches
no DOM, imports nothing from node:, and has one dependency — date-fns, for
the Gregorian side. All it needs is Date, Math and plain string work; it
never reaches for Intl. So it runs wherever JavaScript does.
That is easy to claim and easy to get wrong, so it is tested rather than asserted. CI runs the built package through a matrix of runtime × module format × time zone, and the same checks run in every cell:
| ESM | CommonJS | |
|---|---|---|
| Node 22 | ✓ | ✓ |
| Deno 2 | ✓ | — |
| Bun 1 | ✓ | ✓ |
Deno has no CommonJS column because a Deno consumer resolves the import
condition; there is no CommonJS path to reach. Node 20, 22 and 24 all run the
unit suite as well; the artifact matrix runs on 22. The whole thing lives in
packages/core/runtimes.
Installing
Section titled “Installing”# Node, with any package managerpnpm add @taqwim/core@alpha# Bunbun add @taqwim/core@alpha# Denodeno add npm:@taqwim/core@alphaThe import is the same in all three:
import { toHijri } from '@taqwim/core'
toHijri('2026-08-26')//=> { hy: 1448, hm: 3, hd: 13 }Deno can also skip the manifest entirely and reach for the package inline, which is the shortest path to a one-off script:
import { toHijri } from 'npm:@taqwim/core@alpha'What the checks cover
Section titled “What the checks cover”Not just the arithmetic. The failures worth catching here are the ones that appear between the source and the artifact, or between one runtime and another:
- The whole export list, by name and per module format. The ESM and CommonJS bundles are emitted separately, so an export can survive one and vanish from the other.
instanceof HijriRangeErroracross the module boundary. The class extendsRangeError, and whether that survives depends on the emitted output rather than the source.date-fns/locale, a deep import through another package’s export map — the one place in this library where module resolution can differ between runtimes rather than just arithmetic.- Every one of the ~56,000 days the Umm al-Qura table covers, round-tripped Gregorian → Hijri → Gregorian in each cell.
Time zones are part of the matrix, on purpose
Section titled “Time zones are part of the matrix, on purpose”This library works in local calendar parts deliberately: toGregorian
returns local midnight, and toHijri reads a Date in whatever zone the host
is in, so that “which day is it” matches the user’s clock rather than UTC’s.
That makes the host time zone load-bearing, and runtimes ship different ICU and
tzdata builds. Every cell of the matrix therefore runs three times — under
UTC, Asia/Riyadh and America/Los_Angeles — so an off-by-one that only
appears on one side of the meridian cannot pass.
The consequence for your own code is the caution on
Dates without a calendar: do not round-trip a
toGregorian result through .toISOString(). That is not a runtime
difference; it is true everywhere, and it is the mistake this design invites.
If your process and your users are in different zones — a server in UTC rendering for a reader in Riyadh — see Server rendering, which is about exactly that gap.
What is not in the matrix
Section titled “What is not in the matrix”Edge and worker runtimes — Cloudflare Workers, Deno Deploy, Vercel’s edge runtime — are not tested. Nothing in the package is known to be incompatible with them, and the properties that make it portable are the same ones they require, but “should work” is not “is tested” and this page will not pretend otherwise.
The framework adapters (@taqwim/vue, @taqwim/react and the rest) are not
in this matrix either. They run wherever their framework runs, and their
server-rendering behaviour is covered on
Server rendering instead.
engines.node in package.json names Node 20 as the floor. It is a
Node-specific field; Deno and Bun ignore it, and the versions those two are held
to are the ones in the table above.