DEV Community

Cover image for How to Convert Persian (Jalali) Date to Gregorian in JavaScript — Zero Dependencies
M Gh
M Gh

Posted on

How to Convert Persian (Jalali) Date to Gregorian in JavaScript — Zero Dependencies

If you've ever built an app for Iranian users, you've run into the Persian calendar problem. Iran uses the Solar Hijri (Jalali) calendar as its official calendar — not Gregorian. So when a user types their birthdate as 1370/06/15, you can't just store it as-is if your backend expects Gregorian dates.

In this post I'll show you a pure JavaScript implementation with zero external dependencies that handles both directions: Jalali ↔ Gregorian. This is the same algorithm powering تبدیل تاریخ — tabdiltarikh.ir, a free Persian date converter tool.


A Quick Background on the Jalali Calendar

The Jalali (Solar Hijri) calendar:


  • Is the official calendar of Iran and Afghanistan
  • Starts from the vernal equinox (Nowruz, March 20/21)
  • Has 6 months of 31 days, 5 months of 30 days, and 1 month of 29 or 30 days (leap year)
  • Leap years follow a 4-5 year pattern, not strictly every 4 years like Gregorian
  • Is currently 621 or 622 years behind the Gregorian calendar (depending on the month)

The Algorithm

We use the astronomical Jalali algorithm — the same one used in Iran's official calendar. It works via Julian Day Numbers (JDN) as an intermediate step, which makes bidirectional conversion straightforward.

javascriptcopy// Helper: integer division
function div(a, b) {
return ~~(a / b);
}

// Core: compute Jalali calendar properties for a given Jalali year
function jalCal(jy) {
const breaks = [
-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181,
1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
];
const bl = breaks.length;
const gy = jy + 621;
let leapJ = -14, jp = breaks[0], jm, jump, leap, n, i;

if (jy < jp || jy >= breaks[bl - 1])
throw new Error('Invalid Jalaali year ' + jy);

for (i = 1; i < bl; i++) {
jm = breaks[i];
jump = jm - jp;
if (jy < jm) break;
leapJ = leapJ + div(jump, 33) * 8 + div(jump % 33, 4);
jp = jm;
}

n = jy - jp;
leapJ = leapJ + div(n, 33) * 8 + div((n % 33) + 3, 4);
if ((jump % 33) === 4 && jump - n === 4) leapJ++;

const leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150;
const march = 20 + leapJ - leapG;

if (jump - n < 6) n = n - jump + div(jump + 4, 33) * 33;
leap = (((n + 1) % 33) - 1) % 4;
if (leap === -1) leap = 4;

return { leap, gy, march };
}

// Gregorian date → Julian Day Number
function g2d(gy, gm, gd) {
let d = div((gy + div(gm - 8, 6) + 100100) * 1461, 4)
+ div(153 * ((gm + 9) % 12) + 2, 5)
+ gd - 34840408;
d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752;
return d;
}

// Julian Day Number → Gregorian date
function d2g(jdn) {
let j = 4 * jdn + 139361631;
j = j + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
const i = div((j % 1461), 4) * 5 + 308;
const gd = div(i % 153, 5) + 1;
const gm = (div(i, 153) % 12) + 1;
const gy = div(j, 1461) - 100100 + div(8 - gm, 6);
return { gy, gm, gd };
}

// Jalali date → Julian Day Number
function j2d(jy, jm, jd) {
const r = jalCal(jy);
return g2d(r.gy, 3, r.march)
+ (jm - 1) * 31
- div(jm, 7) * (jm - 7)
+ jd - 1;
}

// Julian Day Number → Jalali date
function d2j(jdn) {
const gy = d2g(jdn).gy;
let jy = gy - 621;
let r = jalCal(jy);
let jdn1f = g2d(gy, 3, r.march);
let jd, jm, k;

k = jdn - jdn1f;
if (k >= 0) {
if (k <= 185) {
jm = 1 + div(k, 31);
jd = (k % 31) + 1;
return { jy, jm, jd };
} else {
k -= 186;
}
} else {
jy -= 1;
k += 179;
if (r.leap === 1) k++;
}

jm = 7 + div(k, 30);

jd = (k % 30) + 1;

return { jy, jm, jd };

}


The Public API

Two clean functions on top of the algorithm above:

javascriptcopy/**

  • Convert Jalali (Persian) date to Gregorian
  • @param {number} jy - Jalali year (e.g. 1403)
  • @param {number} jm - Jalali month (1–12)
  • @param {number} jd - Jalali day (1–31)
  • @returns {number[]} [gregorianYear, gregorianMonth, gregorianDay] */ function jalaaliToGregorian(jy, jm, jd) { const d = d2g(j2d(jy, jm, jd)); return [d.gy, d.gm, d.gd]; }

/**


  • Convert Gregorian date to Jalali (Persian)

  • @param {number} gy - Gregorian year

  • @param {number} gm - Gregorian month (1–12)

  • @param {number} gd - Gregorian day

  • @returns {number[]} [jalaliYear, jalaliMonth, jalaliDay]
    */
    function gregorianToJalaali(gy, gm, gd) {
    const d = d2j(g2d(gy, gm, gd));
    return [d.jy, d.jm, d.jd];
    }

Usage Examples

javascriptcopy// Jalali → Gregorian
const [gy, gm, gd] = jalaaliToGregorian(1403, 1, 1);
console.log(${gy}-${gm}-${gd}); // 2024-3-20

// Gregorian → Jalali
const [jy, jm, jd] = gregorianToJalaali(2024, 3, 20);
console.log(${jy}/${jm}/${jd}); // 1403/1/1

// Convert a user's Persian birthdate to Gregorian

const birthJalali = { y: 1370, m: 6, d: 15 };

const [by, bm, bd] = jalaaliToGregorian(birthJalali.y, birthJalali.m, birthJalali.d);

console.log(${by}-${String(bm).padStart(2,'0')}-${String(bd).padStart(2,'0')});

// → 1991-09-06


Leap Year Check

Jalali leap years don't follow the simple "divisible by 4" rule. Use this:

javascriptcopyfunction isLeapJalaliYear(jy) {
return jalCal(jy).leap === 0;
}

console.log(isLeapJalaliYear(1403)); // true (1403 is a leap year)

console.log(isLeapJalaliYear(1404)); // false

console.log(isLeapJalaliYear(1408)); // true (next leap year after 1403)

The pattern for recent leap years: 1399, 1403, 1408, 1412 — notice the 4-4-4-5 year gap.


Month Lengths

javascriptcopyfunction jalaliMonthLength(jy, jm) {
if (jm <= 6) return 31; // Farvardin–Shahrivar
if (jm <= 11) return 30; // Mehr–Bahman
return isLeapJalaliYear(jy) ? 30 : 29; // Esfand
}

Why Not Just Use a Library?

You can — jalaali-js and moment-jalaali are solid. But if you only need date conversion (no formatting, no locale, no timezone handling), this self-contained implementation is under 2KB minified and has zero npm dependencies. Useful for edge functions, service workers, or any environment where bundle size matters.


Real-World Usage

If you need a ready-to-use tool rather than implementing it yourself, تبدیل تاریخ provides a free Persian date converter that supports Jalali ↔ Gregorian, Jalali ↔ Hijri (lunar), and age calculation. It also offers a free embeddable widget — two lines of code to add it to any site:

htmlcopy<div data-ttkw></div>
<script src="https://tabdiltarikh.ir/widget.js"&gt;&lt;/script>

Summary














Feature This implementation
Jalali → Gregorian
Gregorian → Jalali
Leap year detection
Month length
Dependencies Zero
Valid range Year 1–1500 Jalali
Algorithm Astronomical Jalali (zero error)

Drop the full code into any JS project and you're done. No build step, no install, no configuration.


Built something with Persian dates? Share it in the comments — always curious to see what the dev community builds for Farsi-speaking users.

Top comments (0)