A Jalali to Gregorian converter in JavaScript with support of formatting output
Install from npm:
npm install jalali-date
Both ES modules and CommonJS are supported:
import JDate from 'jalali-date'; // ESM
const JDate = require('jalali-date'); // CommonJSFor the browser, grab a build from the lib directory (or a CDN) and load it with a
<script> tag, which defines a global JDate:
<head>
<script src="jdate.js" type="text/javascript" charset="utf-8"></script>
<script src="jdate.min.js" type="text/javascript" charset="utf-8"></script>
</head>The full version is useful for debugging. You may want to use the minified version in production as it is smaller. Both ship with external source maps.
| File | Format | Used by |
|---|---|---|
lib/jdate.mjs |
ESM | import |
lib/jdate.cjs |
CommonJS | require |
lib/jdate.js |
IIFE | <script>, sets window.JDate |
lib/jdate.min.js |
IIFE, minified | <script>, CDN |
AMD/RequireJS is no longer supported as of 1.3.0. The bundles target ES2019, so Internet Explorer is no longer supported either — use 1.2.x if you need either.
Types ship with the package — there is no @types/jalali-date to install, and nothing
to configure. The library itself stays JavaScript; the declarations are hand written in
types/jdate.d.ts and emitted per module format, so import and require each get the
shape they actually receive at runtime.
import JDate, { type JalaliDate, type JDateConfig } from 'jalali-date';
const jdate = new JDate([1396, 8, 26], { persianNumerical: true });
const formatted: string = jdate.format('dddd DD MMMM YYYY');
const parts: JalaliDate = jdate.date;Under require, the same types come through a namespace on the class:
import JDate = require('jalali-date');
const config: JDate.JDateConfig = { persianNumerical: true };The constructor is typed as four overloads, so the argument mistakes that fail quietly at
runtime fail at compile time instead — new JDate([1396, 8]) builds an Invalid Date
rather than throwing, and is rejected here because JalaliDate is a three-element tuple.
Config entry counts stay a runtime check, so that a name list inferred as string[]
rather than as a fixed-length tuple can still be passed.
For initializing JDate you may either pass an array of Jalali date to it or a Date object. If no parameter is passed, the default is today:
const jdate = new JDate; // => default to today
const jdate2 = new JDate(1393, 10, 11);
const jdate3 = new JDate([1393, 10, 11]);
const jdate4 = new JDate(new Date(2014, 1, 3));Every form also takes an optional config object as its last argument.
jdate.date //=> [1393, 5, 13] An Array of Jalali Date
jdate._d // => Gregorian Date Object
// Getters
jdate.getFullYear() // => 1393
jdate.getMonth() // => 5
jdate.getDate() // => 13
jdate.getDay() // => 1
// Setters
jdate.setFullYear(1394)
jdate.setMonth(6)
jdate.setDate(12)
// Formatting output
jdate.format('dddd DD MMMM YYYY') // => پنجشنبه 12 شهریور 1394
// Static functions
JDate.isLeapYear(1393) // => false
JDate.daysInMonth(1393, 5) // => 31
JDate.toGregorian(1393, 12, 11) // => Gregorian Date object
JDate.toJalali(new Date) // => JDate object
// Display options (see Configuration)
jdate.config // => the frozen names and options this instance formats with
JDate.setDefaultConfig({ monthNames: [...] })
JDate.getDefaultConfig()
JDate.resetDefaultConfig()Note on month numbering. Unlike
Date#getMonth/Date#setMonth, months on an instance are one-based —1is فروردین and12is اسفند.getDate()is likewise1–31. Values passed tosetMonth()outside1..12roll the year over, sosetMonth(13)gives month1of the following year.The static
JDate.daysInMonth(year, month)is the exception: it is zero-based (0is فروردین,11is اسفند), so it does not compose directly withgetMonth():JDate.daysInMonth(jdate.getFullYear(), jdate.getMonth() - 1) // note the -1This inconsistency is retained for backwards compatibility and is expected to be resolved in a future major version.
Use format() and following conversion identifiers as follows:
date.format('dddd D MMMM YYYY') //=> دوشنبه 6 امرداد 1393The conversion identifiers are as follows:
| Identifier | Description | Example |
|---|---|---|
YYY or YYYY |
Full Year (4 digits) | 1393 |
YY |
Year (2 digits) | 93 |
M |
Month in number | returns 5 for امرداد |
MM |
Month in number | returns 05 for امرداد |
MMM or MMMM |
Month in string | امرداد |
D |
Day in number | 26 |
DD |
Day in number | 06 |
d or dd |
Abbreviation of day name in string | ۱ش (for یکشنبه) |
ddd or dddd |
Full day name in string | یکشنبه |
Anything that is not an identifier is passed through unchanged, so separators need no special treatment:
date.format('YYYY-MM-DD') //=> 1393-05-06Identifiers are matched wherever they appear, including inside words. To keep literal
text that contains a Y, M, D or d, wrap it in square brackets:
date.format('[Day] D') //=> Day 6
date.format('Day D') //=> 6ay 6 (the D in "Day" is an identifier)
date.format('YYYY [در] MMMM') //=> 1393 در امردادWrite [[] for a literal [. A bracketed run cannot itself contain ], and an
unterminated [ opens literal mode that runs to the end of the format string:
date.format('MM [YYYY') //=> 05 YYYYA run of an unsupported length is left alone rather than partly consumed — YYYYY and
DDD come back as-is.
What format() prints defaults to Persian names and ASCII digits, and can be changed with
a config object. There are four keys, all optional:
| Key | Value | Used by | Notes |
|---|---|---|---|
monthNames |
12 strings | MMM, MMMM |
calendar order, فروردین first |
dayNames |
7 strings | ddd, dddd |
Sunday first, matching Date#getDay() |
abbrDays |
7 strings | d, dd |
Sunday first, matching Date#getDay() |
persianNumerical |
boolean | YYYY, YY, M, MM, D, DD |
false by default; true prints Persian digits |
Keys you leave out keep their built-in values.
The numeric identifiers print ASCII digits unless persianNumerical is true:
new JDate([1396, 8, 26]).format('YYYY/MM/DD') //=> 1396/08/26
new JDate([1396, 8, 26], { persianNumerical: true }).format('YYYY/MM/DD') //=> ۱۳۹۶/۰۸/۲۶It applies only to those identifiers. Names come out of the config verbatim, so a Latin abbreviation keeps its own digits, and bracketed literals are never touched:
const jdate = new JDate([1396, 8, 26], { persianNumerical: true });
jdate.format('dddd DD MMMM YYYY') //=> جمعه ۲۶ آبان ۱۳۹۶
jdate.format('[Day 1] D') //=> Day 1 ۲۶It is false by default so that existing output does not change; set it app-wide with
JDate.setDefaultConfig({ persianNumerical: true }) if you want it everywhere.
Note that
falsedoes not mean "ASCII everywhere". The built-inabbrDaysare written with Persian digits already (۱ش,۲ش, …), sod/ddprint them whatever this is set to. OverrideabbrDaysif you need those in ASCII too.
Pass the config as the last constructor argument, in any of the four initialization forms:
const en = {
monthNames: ['Farvardin', 'Ordibehesht', 'Khordad', 'Tir', 'Mordad', 'Shahrivar',
'Mehr', 'Aban', 'Azar', 'Dey', 'Bahman', 'Esfand'],
dayNames: ['Yekshanbe', 'Doshanbe', 'Seshanbe', 'Chaharshanbe',
'Panjshanbe', 'Jome', 'Shanbe']
};
new JDate([1396, 8, 26], en).format('dddd DD MMMM YYYY') //=> Jome 26 Aban 1396
new JDate(1396, 8, 26, en)
new JDate(new Date(2017, 10, 17), en)
new JDate(en) // today, with these namesBecause it is per instance, two locales can be rendered side by side:
new JDate([1396, 8, 26]).format('MMMM') //=> آبان
new JDate([1396, 8, 26], en).format('MMMM') //=> AbanJDate.setDefaultConfig() sets what every instance created afterwards will use:
JDate.setDefaultConfig(en);
new JDate([1396, 8, 26]).format('dddd MMMM') //=> Jome Aban
// still overridable per instance, layered over the default
new JDate([1396, 8, 26], { dayNames: [...] }).format('dddd MMMM')
JDate.resetDefaultConfig(); // back to the built-in Persian names and ASCII digitsTwo things to know about it:
- It replaces the default rather than merging into whatever a previous call left behind, so the result depends only on what you pass. Two partial calls do not accumulate — the second one's omitted keys revert to their built-in values.
- Instances capture the default when they are constructed, so calling it later does not retroactively change dates that already exist.
A config is checked when it is passed, so mistakes surface at the call site instead of as
undefined inside formatted output:
new JDate([1396, 8, 26], { monthName: [...] })
// Error: JDate config: unknown key "monthName", expected one of monthNames, abbrDays, dayNames, persianNumerical
new JDate([1396, 8, 26], { monthNames: ['Farvardin', 'Ordibehesht'] })
// Error: JDate config: "monthNames" must have 12 entries, got 2
new JDate([1396, 8, 26], { dayNames: 'Jome' })
// Error: JDate config: "dayNames" must be an array of 7 strings
new JDate([1396, 8, 26], { persianNumerical: 'yes' })
// Error: JDate config: "persianNumerical" must be a booleanpersianNumerical is checked strictly rather than for truthiness, so a typo is an error
instead of quietly meaning true — as 'false' otherwise would.
The config must be a plain object; anything else in that position is rejected as
Unexpected input. The resolved config is frozen and its arrays are copied, so
jdate.config cannot be mutated and changing the array you passed in afterwards does not
reach back into an existing instance.
Report bugs and suggest feature in issue tracker. Feel free to Fork and send Pull Requests.
| Script | What it does |
|---|---|
npm test |
Runs the unit tests against src/ |
npm run lint |
ESLint over the sources, tests and scripts |
npm run build |
Writes the bundles and declarations into lib/ |
npm run watch |
The same, rebuilding on change |
npm run test:types |
Compiles tests/types/ against the built declarations |
npm run test:bundles |
Checks the bundles still export what those declarations describe |
npm run test:exports |
Checks that every way of importing the package resolves to types |
The last three read lib/, so run npm run build first. Changing the public API means
changing types/jdate.d.ts alongside src/ — nothing generates one from the other.