PersianTools
PersianTools provides Jalali (Shamsi) dates and datetimes that work just like Python's datetime — plus handy tools for Persian text: digit conversion between Persian, Arabic, and English, character normalization, and numbers to Persian words.
If you know datetime.date and datetime.datetime, you already know JalaliDate and JalaliDateTime. They support the same operations: comparison, arithmetic with timedelta, timezones, strftime/strptime and formatting, hashing, and pickling.
Installation
pip install persiantools
Requires Python 3.9 or newer. No dependencies, except tzdata on Windows for timezone data.
Quick start
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
>>> import datetime
>>> JalaliDate.today()
JalaliDate(1405, 4, 12, Jomeh)
>>> JalaliDate(datetime.date(1988, 5, 4)) # Gregorian → Jalali
JalaliDate(1367, 2, 14, Chaharshanbeh)
>>> JalaliDate(1367, 2, 14).to_gregorian() # Jalali → Gregorian
datetime.date(1988, 5, 4)
>>> JalaliDateTime.now().strftime("%A %d %B %Y, %H:%M")
'Jomeh 12 Tir 1405, 14:30'
Dates
Create a JalaliDate from Jalali values, a Gregorian date, an ISO string, or a timestamp:
>>> from persiantools.jdatetime import JalaliDate
>>> import datetime
>>> JalaliDate(1367, 2, 14)
JalaliDate(1367, 2, 14, Chaharshanbeh)
>>> JalaliDate.to_jalali(2013, 9, 16)
JalaliDate(1392, 6, 25, Doshanbeh)
>>> JalaliDate.fromisoformat("1404-01-01")
JalaliDate(1404, 1, 1, Jomeh)
>>> JalaliDate.fromtimestamp(578707200)
JalaliDate(1367, 2, 14, Chaharshanbeh)
>>> JalaliDate(1400, 1, 1).replace(month=2, day=10)
JalaliDate(1400, 2, 10, Jomeh)
The week starts on Shanbeh (Saturday). weekday() counts from 0 (Shanbeh) to 6 (Jomeh), and isoweekday() from 1 to 7:
>>> d = JalaliDate(1367, 2, 14) # a Chaharshanbeh (Wednesday)
>>> d.weekday()
4
>>> d.isoweekday()
5
>>> d.isocalendar()
IsoCalendarDate(year=1367, week=7, weekday=5)
>>> d.isoformat()
'1367-02-14'
Datetimes
JalaliDateTime adds time and timezone support on top of JalaliDate:
>>> from persiantools.jdatetime import JalaliDateTime
>>> from zoneinfo import ZoneInfo
>>> import datetime
>>> JalaliDateTime.now(ZoneInfo("Asia/Tehran"))
JalaliDateTime(1405, 4, 12, 14, 30, 7, 907909, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
>>> JalaliDateTime(datetime.datetime(1988, 5, 4, 14, 30, 15))
JalaliDateTime(1367, 2, 14, 14, 30, 15)
>>> JalaliDateTime(1367, 2, 14, 14, 30, 15).to_gregorian()
datetime.datetime(1988, 5, 4, 14, 30, 15)
>>> JalaliDateTime(1367, 2, 14, 14, 30, tzinfo=datetime.timezone.utc).isoformat(timespec="minutes")
'1367-02-14T14:30+00:00'
Formatting and parsing
Both classes support strftime and strptime with the familiar directives, in English or Persian:
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
>>> JalaliDate(1367, 2, 14).strftime("%A %d %B %Y")
'Chaharshanbeh 14 Ordibehesht 1367'
>>> JalaliDateTime(1367, 2, 14, 14, 30).strftime("%c", locale="fa")
'چهارشنبه ۱۴ اردیبهشت ۱۳۶۷ ۱۴:۳۰:۰۰'
>>> JalaliDate.strptime("1367-02-14", "%Y-%m-%d")
JalaliDate(1367, 2, 14, Chaharshanbeh)
>>> JalaliDateTime.strptime("1367/02/14 14:30", "%Y/%m/%d %H:%M")
JalaliDateTime(1367, 2, 14, 14, 30)
A date created with locale="fa" renders itself with Persian digits and names everywhere:
>>> JalaliDate(1367, 2, 14, locale="fa").isoformat()
'۱۳۶۷-۰۲-۱۴'
Comparison and arithmetic
Jalali objects compare and do arithmetic with each other, with timedelta, and directly with their Gregorian counterparts:
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
>>> import datetime
>>> JalaliDate(1367, 2, 14) == datetime.date(1988, 5, 4)
True
>>> JalaliDate(1395, 2, 14) + datetime.timedelta(days=38)
JalaliDate(1395, 3, 21, Jomeh)
>>> JalaliDateTime(1395, 12, 30) - JalaliDateTime(1395, 1, 1)
datetime.timedelta(days=365)
>>> JalaliDate(1399, 12, 30) > JalaliDate(1399, 12, 29) # leap-year Esfand 30
True
They are also hashable (usable as dict keys) and picklable, like the standard library types.
Digits
Convert digits between English, Persian, and Arabic:
>>> from persiantools import digits
>>> digits.en_to_fa("0987654321")
'۰۹۸۷۶۵۴۳۲۱'
>>> digits.ar_to_fa("٠٩٨٧٦٥٤٣٢١")
'۰۹۸۷۶۵۴۳۲۱'
>>> digits.fa_to_en("۰۹۸۷۶۵۴۳۲۱")
'0987654321'
>>> digits.fa_to_ar("۰۹۸۷۶۵۴۳۲۱")
'٠٩٨٧٦٥٤٣٢١'
And spell numbers out in Persian — integers, floats, and negatives:
>>> digits.to_word(9512026)
'نه میلیون و پانصد و دوازده هزار و بیست و شش'
>>> digits.to_word(15.007)
'پانزده و هفت هزارم'
>>> digits.to_word(-123.45)
'منفی یکصد و بیست و سه و چهل و پنج صدم'
Characters
Arabic and Persian share letters that look alike but have different Unicode code points (ك vs ک, ي vs ی) — a common source of failed string matching and broken search. Normalize them in either direction:
>>> from persiantools import characters
>>> characters.ar_to_fa("كيك")
'کیک'
>>> characters.fa_to_ar("کیک")
'كيك'
AI Agent Skill
Need your AI assistant to convert Persian (Shamsi/Jalali) dates accurately? Install the
convert-persian-dates — it teaches the agent to convert between
Shamsi and Gregorian (Miladi) without guessing calendar math.
npx skills add majiidd/persiantools --skill convert-persian-dates
Add --global to enable it across all projects. OpenAI Codex and Cursor also pick it up automatically when this
repo is open. After install, ask naturally:
امروز چندمه؟
What is today's date in Shamsi?
Convert 1405-01-01 Shamsi to Gregorian.
The skill needs pip install persiantools in the environment where the agent runs Python.
Support this project
If persiantools saves you time, you can support its development with a donation:
| Coin | Address |
|---|---|
| Bitcoin (BTC) | bc1qg5rp7ymznc98wmhltzvpwl2dvfuvjr33m4hy77 |
| Ethereum (ETH) | 0xC7D6bf306E456632764D0aD111C8dBBb43a3B9ad |
| Tron (TRX) | TDd63bVWZDBHmwVNFgJ6T2WdWmk9z7PBLg |
| Stellar (XLM) | GDSFPPLY34QSAOTOP4DQDXAI2YDRNRIADZHTN3HCGMQXRLIGPYOEH7L5 |
| USDT (BSC) | 0xC7D6bf306E456632764D0aD111C8dBBb43a3B9ad |
Release files for persiantools 6.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| persiantools-6.2.0.tar.gz | 56.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| persiantools-6.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 84.7 kB
Release files / persiantools-6.2.0.tar.gz
| Download URL | persiantools-6.2.0.tar.gz |
|---|---|
| Size | 56.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
21ae1ed27292d8a153946f6b16a660cb84a2d829e0a3a0afefee3e9f83ad41b8
|
|
BLAKE2b-256 checksum How to use checksums |
dea2c8f3930c3f0c50b20098e28160a29d0962eedb93cced89bb366b0262be0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 5, 2026.
Transparency logRelease files / persiantools-6.2.0-py3-none-any.whl
| Download URL | persiantools-6.2.0-py3-none-any.whl |
|---|---|
| Size | 28.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
02d0f1c9f8784b20700f23ef96f01706e8cc1b9bf9fe105b3a6c44492f4d61c6
|
|
BLAKE2b-256 checksum How to use checksums |
81146e5862cc921df714840fda14f7d65fdd52b3630fd83c66d905a3240da775
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 5, 2026.
Transparency log