JalaliX
Jalali (Persian / Shamsi) calendar for Elixir. Pure Elixir, no runtime timezone database required for UTC and Asia/Tehran (golden parity).
mix deps.get
# or: {:jalalix, "~> 0.1.0"}
d = JalaliX.create("2025-02-25")
JalaliX.format(d, "YYYY/MM/DD")
# => "1403/12/07"
JalaliX.format(d, "DD MMM YYYY", %{"persianDigits" => true})
# => "۰۷ اسفند ۱۴۰۳"
JalaliX.parseJalali("1403/12/07") |> JalaliX.toISODate()
# => "2025-02-25"
toISODate/1 uses local calendar getters. Prefer it when you need a calendar date string without UTC shift surprises.
Install
# mix.exs
defp deps do
[{:jalalix, "~> 0.1.0"}]
end
d = JalaliX.create("2025-02-25")
Usage
create / format
now = JalaliX.create(System.system_time(:millisecond), %{"timezone" => "Asia/Tehran"})
JalaliX.format(now, "YYYY/MM/DD")
JalaliX.format(now, "WN، DD MMM YYYY")
JalaliX.format(now, "YYYY/MM/DD", %{"persianDigits" => true})
JalaliX.format(now, "DD MMM YYYY", %{"calendar" => "gregorian"})
format also accepts the older positional form: format(d, str, persianDigits?, gregorian?).
parseJalali
JalaliX.parseJalali("1403/12/07")
JalaliX.parseJalali("۱۴۰۳-۱۲-۰۷ 14:30")
toISODate
d = JalaliX.create("2025-02-25")
JalaliX.toISODate(d) # "2025-02-25" (local Gregorian getters)
JalaliX.toISODate(d, "jalali") # "1403-12-07"
add / start / end
JalaliX.create("2025-02-25")
|> JalaliX.addMonths(1)
|> JalaliX.getStartOfMonth()
|> JalaliX.format("YYYY/MM/DD")
# => "1404/01/01"
Persian digits
JalaliX.toPersian("1403/12/07") # "۱۴۰۳/۱۲/۰۷"
JalaliX.fromPersian("۱۴۰۳/۱۲/۰۷") # "1403/12/07"
Months
This is easy to get wrong:
getMonth/1-> 1..12 (Farvardin = 1)setMonth/2/setFullYear/3month arg -> 0..11, same as JSDate(Farvardin = 0)
d = JalaliX.create(System.system_time(:millisecond))
d = JalaliX.setFullYear(d, 1404, 0, 1) # 1 Farvardin 1404
JalaliX.getMonth(d) # 1
Setters return a new %JalaliX{} (Elixir is immutable).
Timezone
Internal storage is a UTC Unix millisecond instant plus an IANA timezone id. setTimezone/2 is instant-preserving: the timestamp stays the same; wall clock / Jalali day may change.
d = JalaliX.create(1_740_430_800_000)
tehran = JalaliX.setTimezone(d, "Asia/Tehran")
JalaliX.format(tehran, "YYYY/MM/DD") # "1403/12/07"
JalaliX.format(tehran, "HH:mm:ss") # "00:30:00"
utc = JalaliX.setTimezone(d, "UTC")
JalaliX.format(utc, "YYYY/MM/DD") # "1403/12/06"
JalaliX.format(utc, "HH:mm:ss") # "21:00:00"
- Aliases:
utc/gmt->UTC;local/default/system->TZenv when known, elseUTC. - Invalid zone -> raises
ArgumentError. - Built-in fixed offsets include
UTCandAsia/Tehran(+03:30; Iran abolished DST in 2022).
Format tokens
| Token | |
|---|---|
YYYY YY |
year |
MMM MM M |
month name / number |
DD D |
day |
HH hh mm ss |
time |
A a |
meridiem (ق.ظ. / ب.ظ. or AM/PM) |
WN WL |
weekday name / letter |
w DDD |
week of year / day of year |
X x |
unix seconds / ms |
API
Static:
create/1,create/2— timestamp (ms), ISO string,DateTime, orDateparseJalali/1,parseJalali/2isValidDate/1toPersian/1/fromPersian/1compare/2— true ifais beforeb
options is %{"timezone" => str} (e.g. "Asia/Tehran").
Instance (main ones; first arg is %JalaliX{}):
format/2…format/4,toISODate/1,toISODate/2toString/1,toDateString/1,toLocaleDateString/1,toLocaleTimeString/1getFullYear/1,getMonth/1,getDate/1,getDay/1, …setFullYear/2…setFullYear/4,setMonth/2,setDate/2,setHours/2… (return new struct)addYears/2,addMonths/2,addWeeks/2,addDays/2getStartOfYear/1/Month/Week/Dayand matchinggetEndOf*clone/1,setTimezone/2,getTimezone/1
Method names match the TypeScript package (camelCase).
Links
License
MIT