A tiny, fast, dependency‑free JavaScript library for converting dates between Gregorian and Jalali (Persian) calendars without going through Julian Day Numbers (JDN).
By avoiding the JDN bridge, JalCal sidesteps common off‑by‑one and leap‑year edge cases that affect many libraries, especially around Esfand 30 and Gregorian leap days.
- ✅ Direct Gregorian ⇄ Jalali conversion (no JDN)
- 🧪 Correct handling of leap years (both calendars)
- 🔁 Deterministic round‑trip conversions
- 🧩 Zero dependencies, tiny footprint
- 🕒 Works in Node.js, browsers, and modern bundlers
- 🔒 Pure functions, immutable results
Note: Function names follow the library’s public API:
gregorianToJalaali(gy, gm, gd)andjalaaliToGregorian(jy, jm, jd).
// Convert a Gregorian date (year, month, day) to Jalali parts
const j = gregorianToJalaali(2025, 3, 21);
// => [ 1404, 1, 1 ] // Farvardin 1, 1404 (Nowruz)
// Convert Jalali parts to Gregorian parts
const g = jalaaliToGregorian(1403, 12, 30); // Esfand 30 (leap year in Jalali)
// => [2025, 3, 20 ]
// Build a JS Date from the Gregorian result
const date = new Date(g[0], g[1] - 1, g[2]);Converts a Gregorian date to Jalali (Persian) date parts.
Parameters
gy: Gregorian yeargm: Gregorian month1..12gd: Gregorian day1..31
Returns
[ jy, jm, jd ]
Examples
gregorianToJalaali(2024, 2, 29); // 2024‑02‑29 (Gregorian leap day)
// => [1402, 12, 10 ]
gregorianToJalaali(2016, 3, 20);
// => [ 1395, 1, 1 ]Converts a Jalali date to Gregorian date parts.
Parameters
jy: Jalali year (e.g., 1403)jm: Jalali month1..12jd: Jalali day1..31(validated per month & leap year)
Returns
[ gy, gm, gd ](1‑based month)
Examples
jalaaliToGregorian(1399, 12, 30); // Esfand 30 (Jalali leap year)
// => [2021, 3, 20 ]
// End‑of‑month safety
const g = jalaaliToGregorian(1402, 6, 31);
const native = new Date(g[0], g[1] - 1, g[2]);Returns true if jy is a leap year in the Jalali calendar.
isJalaliLeapYear(1399); // true
isJalaliLeapYear(1400); // falseReturns true if gy is a leap year in the Gregorian calendar.
isGregorianLeapYear(2024); // true
isGregorianLeapYear(2100); // falseJalCal implements the Behrooz–Birashk civil Jalali leap‑year rule using a mod‑128 remainder method (no Julian Day bridge). In practice, this yields a stable 33‑year leap cadence with periodic adjustments encoded by the mod‑128 remainder, matching the modern civil calendar in software and government systems. The implementation is purely integer arithmetic and is deterministic.
Proven window: The algorithm and implementation have been validated for all Jalali years 1300..1700 via exhaustive tests and anchor‑date checks (see Testing).
Many converters map Gregorian → JDN → Jalali. This introduces rounding and epoch‑offset pitfalls that show up on boundary dates:
- Gregorian Feb 29 in leap years
- Jalali Esfand 30 (leap day)
- Cross‑year edges (e.g., Farvardin 1)
JalCal uses direct arithmetic in both calendars:
- Gregorian: 400‑year cycles (exact leap rule: divisible by 4, not by 100 unless by 400)
- Jalali: modern 33‑year leap cycle pattern used in civil software systems
This yields stable, fast conversions without JDN rounding drift.
If your use case requires astronomical historical precision centuries back, consider cross‑checking with astronomical tables.
- ✅ Gregorian leap day 2024‑02‑29 ⇄ 1402‑12‑10
- ✅ Jalali leap day 1403‑12‑30 ⇄ 2025‑03‑20
- ✅ End‑of‑month dates (Shahrivar 31, Dey/Esfand boundaries)
- ✅ Round‑trip stability:
G → J → GandJ → G → J
- Months are 1‑based; days are validated against month length and leap years.
- Validated window: Implementation is tested and verified for Jalali years 1300–1700. Outside this window it should still work (same rule), but if your application depends on strict historical accuracy, add table‑based tests for the years you need.
- Historical ranges: Civil Jalali calendar rules before modern standardization can be ambiguous; JalCal targets the modern civil Jalali.
- Time zones: Conversions operate on calendar dates, not times; your JS
Datemay shift a day if created in a different time zone around midnight. - Input range: Practical range ±4,000 years is supported.
The library has been exhaustively tested for all Jalali years 1300–1700, including round‑trip invariants and leap‑day boundaries.
MIT © 2025 Ali HM