DEV Community

Cover image for jalali-js: Persian calendar for JavaScript
Pooyan Razian
Pooyan Razian

Posted on • Originally published at pooyan.info

jalali-js: Persian calendar for JavaScript

You've probably been here: you want Jalali dates in the UI, but not a pile of packages, Moment plugins, and Jalali strings leaking into your API.

jalali-js (currently 0.4.2) is the small toolkit I reach for in that situation. Core does conversion and date math. React, Vue, or plain Web Components sit on top. Show Jalali. Store Gregorian by default, same idea as a native <input type="date">. Farsi docs are also available: jalali-js.yanovian.com/fa.

Docs · GitHub · npm

jalali-js documentation homepage

Docs and playgrounds live here

Who it is for

  • You ship for Persian (or other Jalali-reading) users and need a date picker that doesn't fight your stack
  • You want conversion / date math on the server or in shared code, with zero UI
  • You're on React, Vue, Nuxt, Next, or no framework at all (Web Components)
  • You're done stitching Moment plugins, math-only packages, and one-framework pickers together

Skip it if you only need a one-off Gregorian date and never show Jalali.

What you get

  • Jalali on screen, Gregorian in storage by default (opt out when you really need Jalali-shaped values)
  • A tiny TypeScript core: conversion, date math, selection rules
  • Ready pickers for React / Vue / Web Components, plus ranges and event calendars (month, week, day, timeline / roadmap)
  • en / fa / ps, Iran holidays, and a bit of natural-language parsing ("فردا", "next Farvardin")
  • Light and dark themes via CSS variables

Install only what you need

npm install jalali-js                 # currently 0.4.2
npm install @jalali-js/react         # or @jalali-js/vue / @jalali-js/web
Enter fullscreen mode Exit fullscreen mode

Want ranges, event calendars, NLP, or Iran holidays? Grab @jalali-js/ui-*, @jalali-js/nlp, @jalali-js/holidays, @jalali-js/i18n as you go. Same version everywhere.

Core

import { createCalendar, toGregorian, fromGregorian, addMonths } from 'jalali-js';

const jalali = createCalendar({ system: 'jalali' });
jalali.today();

toGregorian({ year: 1403, month: 5, day: 15 }, 'jalali');
// { year: 2024, month: 8, day: 5 }

addMonths({ year: 1403, month: 1, day: 31 }, 6, 'jalali');
// day clamps to the target month
Enter fullscreen mode Exit fullscreen mode

Arithmetic leap years are the default (matches ICU for normal app ranges). If you ever need equinox-based leaps way outside that, pass engine: 'astronomical'.

Pickers

This is the important part: Jalali on screen, Gregorian out. Default onChange is an ISO string your DB can treat like any other date.

import '@jalali-js/react/date-picker.css';
import { DatePicker } from '@jalali-js/react';

<DatePicker
  system="jalali"
  locale="fa"
  showHolidays
  rules={{
    minDate: { year: 1403, month: 1, day: 1 },
    maxDate: { year: 1403, month: 12, day: 29 },
    disabledWeekdays: [4, 5],
  }}
  onChange={(value) => {
    // e.g. '2024-08-05'
  }}
/>;
Enter fullscreen mode Exit fullscreen mode

Jalali calendar with Iran holidays highlighted

Holidays show up on the grid; tips name them on hover

Need a time too? precision="datetime". On Vue, v-model just works. No framework? @jalali-js/web and <jalali-date-picker>.

<script setup lang="ts">
import '@jalali-js/vue/date-picker.css';
import { DatePicker } from '@jalali-js/vue';
import type { StorageValue } from 'jalali-js';
import { ref } from 'vue';

const stored = ref<StorageValue>();
</script>

<template>
  <DatePicker v-model="stored" system="jalali" locale="fa" show-holidays />
</template>
Enter fullscreen mode Exit fullscreen mode

Ranges and events

@jalali-js/ui-react (and the Vue / web twins) give you RangePicker and EventCalendar. You own the event data; it handles month, week, day, and timeline layouts. Overlaps sit side by side.

import { EventCalendar } from '@jalali-js/ui-react';

<EventCalendar
  system="jalali"
  locale="fa"
  view="week"
  initialDate={{ year: 1403, month: 5, day: 15 }}
  events={events}
/>;
Enter fullscreen mode Exit fullscreen mode

EventCalendar week view with overlap lanes

Week view

For a project-style list, try view="timeline" with layout: 'single' | 'alternating' | 'roadmap'.

<EventCalendar
  system="jalali"
  locale="fa"
  view="timeline"
  timeline={{ layout: 'roadmap', showIcons: true }}
  events={milestones}
/>;
Enter fullscreen mode Exit fullscreen mode

EventCalendar roadmap layout in light mode

Roadmap

EventCalendar roadmap layout in dark mode

Same thing in dark

EventCalendar single-sided timeline

Timeline: single

EventCalendar alternating timeline

Timeline: alternating

Format, phrases, holidays

import { format, formatRelative, fa } from '@jalali-js/i18n';
import { parse } from '@jalali-js/nlp';
import { isHoliday } from '@jalali-js/holidays';

format(date, fa); // '۱۵ مرداد ۱۴۰۳'
formatRelative(earlier, date, fa); // '۳ روز پیش'
parse('فردا', 'fa');
isHoliday({ year: 1403, month: 1, day: 1 });
Enter fullscreen mode Exit fullscreen mode

Locales out of the box: en, fa, ps.

Which package?

Package Grab it when…
jalali-js You only need conversion / math
@jalali-js/react / vue / web You want a picker
@jalali-js/ui-* Ranges, events, themes
@jalali-js/i18n / nlp / holidays Formatting, "فردا", Iran holidays

That's it

Start here if you like reading: getting started. Or just open a playground and steal a snippet: React · Vue · Web Components.

MIT. If you hit a weird edge or something's confusing, drop it on GitHub issues. Always glad when people poke at this stuff.

This article was originally published at pooyan.info.


If you liked the article, feel free to share it with your friends, family, or colleagues. You can also follow me on LinkedIn.

Copyright & Disclaimer

  • All content provided on this article is for informational and educational purposes only. The author makes no representations as to the accuracy or completeness of any information on this site or found by following any link on this site.
  • All the content is copyrighted, except the assets and content I have referenced to other people's work, and may not be reproduced on other websites, blogs, or social media. You are not allowed to reproduce, summarize to create derivative work, or use any content from this website under your name. This includes creating a similar article or summary based on AI/GenAI. For educational purposes, you may refer to parts of the content, and only refer, but you must provide a link back to the original article on this website. This is allowed only if your content is less than 10% similar to the original article.
  • While every care has been taken to ensure the accuracy of the content of this website, I make no representation as to the accuracy, correctness, or fitness for any purpose of the site content, nor do I accept any liability for loss or damage (including consequential loss or damage), however caused, which may be incurred by any person or organization from reliance on or use of information on this site.
  • The contents of this article should not be construed as legal advice.
  • Opinions are my own and not the views of my employer.
  • English is not my mother-tongue language, so even though I try my best to express myself correctly, there might be a chance of miscommunication.
  • Links or references to other websites, including the use of information from 3rd-parties, are provided for the benefit of people who use this website. I am not responsible for the accuracy of the content on the websites that I have put a link to and I do not endorse any of those organizations or their contents.
  • If you have any queries or if you believe any information on this article is inaccurate, or if you think any of the assets used in this article are in violation of copyright, please contact me and let me know.

Top comments (0)