Skip to content
/ JalCal Public

yet another javascript library to convert dates between Jalali and Gregorian calendars without using Julian day

License

Notifications You must be signed in to change notification settings

ali-hm/JalCal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JalCal

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.


✨ Features

  • 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) and jalaaliToGregorian(jy, jm, jd).


Installation

Option A) Use directly from source (GitHub clone or single-file copy)

Place the JS file in your project and import it locally


Quick Start

// 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]);

API

gregorianToJalaali(gy, gm, gd)

Converts a Gregorian date to Jalali (Persian) date parts.

Parameters

  • gy: Gregorian year
  • gm: Gregorian month 1..12
  • gd: Gregorian day 1..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 ]

jalaaliToGregorian(jy, jm, jd)

Converts a Jalali date to Gregorian date parts.

Parameters

  • jy: Jalali year (e.g., 1403)
  • jm: Jalali month 1..12
  • jd: Jalali day 1..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]);

isJalaliLeapYear(jy)

Returns true if jy is a leap year in the Jalali calendar.

isJalaliLeapYear(1399); // true
isJalaliLeapYear(1400); // false

isGregorianLeapYear(gy)

Returns true if gy is a leap year in the Gregorian calendar.

isGregorianLeapYear(2024); // true
isGregorianLeapYear(2100); // false

Algorithm & correctness

JalCal 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).


Why not Julian Day Numbers?

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.


Edge Cases Covered

  • ✅ Gregorian leap day 2024‑02‑291402‑12‑10
  • ✅ Jalali leap day 1403‑12‑302025‑03‑20
  • ✅ End‑of‑month dates (Shahrivar 31, Dey/Esfand boundaries)
  • ✅ Round‑trip stability: G → J → G and J → G → J


Validation & Errors

  • Months are 1‑based; days are validated against month length and leap years.

Limitations & Notes

  • 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 Date may shift a day if created in a different time zone around midnight.
  • Input range: Practical range ±4,000 years is supported.

Testing

The library has been exhaustively tested for all Jalali years 1300–1700, including round‑trip invariants and leap‑day boundaries.


License

MIT © 2025 Ali HM

About

yet another javascript library to convert dates between Jalali and Gregorian calendars without using Julian day

Resources

License

Stars

Watchers

Forks