Skip to main content

taqvim — Persian (Jalali / Shamsi / Solar Hijri) calendar for JavaScript, TypeScript and Python

Accurate Iranian calendar library with the same API in two languages. Convert between Jalali and Gregorian dates, parse dates the way Iranians and Afghans actually type them, format in Persian, Dari or English, do month arithmetic safely, and get Tehran time right.

فارسی · Accuracy report · Nowruz table 1–2999 · llms.txt

JavaScript / TypeScript Python
Package taqvim (npm) taqvim (PyPI)
Source js/ python/
Dependencies none none
Size 7.4 KB gzipped pure Python
Runtimes Node ≥ 18, browsers, Deno, Bun Python ≥ 3.9

Taqvim (تقویم) means "calendar" in Persian. The calendar itself is called Solar Hijri, Jalali, Shamsi, Iranian or Persian calendar; it is the official calendar of Iran and Afghanistan.

Why another Jalali library?

Existing libraries cover pieces of the problem, and several have bugs that matter (full report):

  • Wrong leap years. Libraries using an arithmetic cycle drift from the real calendar. Python's khayyam puts Nowruz 1404 on 20 March 2025 (it was 21 March), so all of 1404 is one day off. persian-date is wrong for 1963. taqvim uses an astronomical table of the vernal equinox for years 1–2999, cross-checked against NASA JPL ephemeris DE421.
  • Silent rollover. 1404/12/30 does not exist (1404 is not a leap year). Several libraries quietly turn it into 1 Farvardin 1405 or call it valid. taqvim rejects it with a clear message.
  • Persian digits and keyboards. ۱۴۰۴/۰۱/۱۵ makes some libraries fail or return year 621. taqvim accepts Persian and Arabic digits, ي/ك, ZWNJ, RTL marks, month names, weekdays and ه.ش.
  • Tehran time. Iran abolished daylight saving time in 2022. Devices with old tzdata still show the wrong hour. taqvim embeds the Asia/Tehran history.
  • Two ecosystems. Front-end in JS and back-end in Python usually means two libraries with different rules. taqvim is one specification, one shared test suite, two packages.

Install

npm install taqvim
pip install taqvim

Quick start

TypeScript / JavaScript

import { JalaliDate, toJalali, toGregorian, formatDateTime } from 'taqvim';

toJalali(2025, 3, 21);            // { year: 1404, month: 1, day: 1 }
toGregorian(1403, 12, 30);        // { year: 2025, month: 3, day: 20 }

const d = new JalaliDate(1404, 1, 15);
d.format('dddd D MMMM YYYY');                     // 'جمعه ۱۵ فروردین ۱۴۰۴'
d.format('dddd D MMMM YYYY', { locale: 'en' });   // 'Friday 15 Farvardin 1404'
d.format('D MMMM YYYY', { locale: 'fa-AF' });     // '۱۵ حمل ۱۴۰۴'  (Afghanistan)
d.addMonths(1).toString();                        // '1404/02/15'
d.toISODate();                                    // '2025-04-04'

JalaliDate.parse('شنبه ۱۶ فروردین ۱۴۰۴ ه.ش');     // JalaliDate 1404/01/16
JalaliDate.today();                               // today in Tehran
formatDateTime(new Date(), 'YYYY/MM/DD HH:mm');   // '۱۴۰۵/۰۸/۰۵ ۱۷:۰۰'

new JalaliDate(1404, 12, 30);
// RangeError: 1404/12/30 does not exist: Esfand 1404 has 29 days (1404 is not a leap year)

Python

from datetime import date, timedelta
from taqvim import JalaliDate, to_jalali, to_gregorian, format_datetime

to_jalali(2025, 3, 21)            # YMD(year=1404, month=1, day=1)
to_gregorian(1403, 12, 30)        # YMD(year=2025, month=3, day=20)

d = JalaliDate(1404, 1, 15)
d.format('dddd D MMMM YYYY')                # 'جمعه ۱۵ فروردین ۱۴۰۴'
d.format('dddd D MMMM YYYY', locale='en')   # 'Friday 15 Farvardin 1404'
d.add_months(1)                             # JalaliDate(1404, 2, 15)
d.to_date()                                 # datetime.date(2025, 4, 4)
JalaliDate.from_date(date(2025, 4, 4)) == d # True
d + timedelta(days=1)                       # JalaliDate(1404, 1, 16)

JalaliDate.parse('۱۵ فروردین ۱۴۰۴')         # JalaliDate(1404, 1, 15)
JalaliDate.today()                          # today in Tehran

API

Names are camelCase in JavaScript and snake_case in Python; behaviour is identical.

Conversion and calendar facts

JavaScript Python
toJalali(gy, gm, gd) to_jalali(gy, gm, gd) Gregorian → Jalali
toGregorian(jy, jm, jd) to_gregorian(jy, jm, jd) Jalali → Gregorian
isLeapYear(jy) is_leap_year(jy) 366-day year?
daysInMonth(jy, jm) days_in_month(jy, jm) 29–31
isValidDate(jy, jm, jd) is_valid_date(jy, jm, jd) never throws
nowruz(jy) nowruz(jy) Gregorian date of 1 Farvardin
isUncertainYear(jy) is_uncertain_year(jy) astronomically borderline year (why)
MIN_YEAR, MAX_YEAR same 1 and 2999

JalaliDate (immutable)

JavaScript Python
new JalaliDate(y, m, d) JalaliDate(y, m, d) throws on non-existent dates
JalaliDate.fromGregorian(y, m, d) JalaliDate.from_gregorian(...) / from_date(date)
JalaliDate.fromDate(date, {timeZone}) JalaliDate.from_datetime(dt, time_zone) date of an instant; default Asia/Tehran
JalaliDate.today() JalaliDate.today()
JalaliDate.parse(text) / tryParse JalaliDate.parse(text) / try_parse see parsing
.year .month .day same
.dayOfWeek .weekday 0 = Saturday … 6 = Friday
.dayOfYear .weekOfYear .season .day_of_year .week_of_year .season weeks start on Saturday
.addDays/addWeeks/addMonths/addYears(n) .add_days/... month/year arithmetic clamps to month end
.startOfMonth() .endOfMonth() .startOfYear() .endOfYear() .startOfWeek() .endOfWeek() snake_case
.diffInDays/diffInMonths/diffInYears(other) snake_case whole units
.equals .isBefore .isAfter .isBetween, < > == < >, is_between
.toGregorian() .toISODate() .toDate({hour, timeZone}) .to_gregorian() .to_date() .to_datetime(...)
.format(pattern, {locale, digits}) .format(pattern, locale, digits)

Formatting tokens

YYYY 1404 · YY 04 · M 1 · MM 01 · MMMM فروردین · D 5 · DD 05 · DDD day of year · d weekday 1–7 (Saturday = 1) · ddd ش · dddd شنبه · w/ww week · Q season 1–4 · QQQQ بهار · H HH h hh m mm s ss A time (with formatDateTime / format_datetime) · Z +03:30 · ZZ +0330 · [text] literal. Locales: fa (default), fa-AF (Dari month names), en. Digits: persian (default for fa), latin (default for en), arabic.

Parsing

Accepted: 1404/01/15, 1404-1-15, 1404.01.15, 15/01/1404, ۱۴۰۴/۰۱/۱۵, ١٤٠٤/٠١/١٥, ۱۵ فروردین ۱۴۰۴, شنبه ۱۶ فروردین ۱۴۰۴ ه.ش, ۲۰ امرداد ۱۴۰۳, 15 Farvardin 1404, Farvardin 15, 1404, ۱۵ حمل ۱۴۰۴. Two-digit years are rejected rather than guessed. Errors say exactly what is wrong.

Time zones and date-times

toJalaliParts(instant), fromJalaliParts({year, month, day, hour, minute}, {timeZone}), formatDateTime(instant, pattern) in JS; format_datetime(dt, pattern), TEHRAN (a tzinfo), to_zone(dt) in Python. Asia/Tehran comes from embedded tzdata; any other IANA zone works through the host. Non-existent local times move forward; ambiguous ones resolve to the earlier instant.

Holidays and business days

solarHolidays(year), isHoliday(date, {extra, weekendDays}), isWeekend, addBusinessDays. Covers Iran's fixed solar public holidays (Nowruz 1–4 Farvardin, 12 and 13 Farvardin, 14 and 15 Khordad, 22 Bahman, 29 Esfand). Islamic lunar holidays are announced each year by moon sighting and cannot be computed in advance — pass them in extra. Weekend defaults to Friday.

Utilities

toPersianDigits, toLatinDigits, convertDigits, normalizePersian (Python: snake_case).

Limits, stated plainly

  • Supported years: 1–2999 AP (622–3621 CE). Dates before 1304 AP (1925) are proleptic.
  • A few years are astronomically borderline; the next one is 1470 AP (2091 CE). They are flagged by isUncertainYear, never hidden.
  • Lunar Hijri holidays are not computed (see above).

Development

cd js && npm install && npm test          # builds, then runs every day 1–2999 + shared cases
cd python && PYTHONPATH=src python -m unittest discover -s tests

Rebuild the astronomical data: python scripts/equinox.py && python scripts/build_reference.py && python scripts/generate_data.py. Reproduce the comparison: see scripts/compare.

License

MIT © 2026 Alireza Rahimi

Keywords: Jalali calendar, Shamsi date, Persian calendar, Solar Hijri, Iranian calendar, Afghan calendar, Hijri Shamsi, Farsi date, Nowruz, convert Jalali to Gregorian, تاریخ شمسی، تقویم جلالی، تبدیل تاریخ شمسی به میلادی، هجری خورشیدی.

Metadata

Release files for taqvim 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for taqvim 1.0.0
File Size Uploaded Provenance Actions
taqvim-1.0.0.tar.gz 18.3 kB With provenance Details for taqvim-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for taqvim 1.0.0
File Interpreter ABI Platform Actions
taqvim-1.0.0-py3-none-any.whl Python 3 none any Details for taqvim-1.0.0-py3-none-any.whl

Total release size: 39.0 kB

Release files / taqvim-1.0.0.tar.gz

Download URL taqvim-1.0.0.tar.gz
Size 18.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fa8fec7ae7d81a979afa46622266660be16e8463fd42d59aaef5cf16ad95466c
BLAKE2b-256 checksum
How to use checksums
475826c5caf6d55067ebf62afe60686149a454ed31b925b4a9dfd6dcd5f792da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Release files / taqvim-1.0.0-py3-none-any.whl

Download URL taqvim-1.0.0-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05e5aad46353063a360bd368264cfb67353b66f90c791d21b518f63135b4ca24
BLAKE2b-256 checksum
How to use checksums
f29d0639e07bb23acd5741bb502d38067bc6ada29037be1108e8d7150f368f5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Security for taqvim 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for taqvim-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page