persian-calendar-suite
v1.5.2
Published
Complete Persian (Jalali/Shamsi) calendar suite for React with datepicker, range picker, time range picker, event calendar, and timeline
Maintainers
Readme
Persian Calendar Suite
A comprehensive Persian (Jalali/Shamsi) calendar suite for React with datepicker, range picker, event calendar, and multiple output formats.
Table of Contents
- Installation
- Compatibility
- Quick Start
- Components
- Theme Customization
- Output Formats
- Programmatic Control
- Examples
- Browser Support
- License
Installation
npm install persian-calendar-suiteor with yarn:
yarn add persian-calendar-suiteCompatibility
- React: +16.8 (Hooks required)
- React DOM: +16.8
- Next.js: +12 (Client-side components)
- Node: 12+
Peer Dependencies
npm install react react-domQuick Start
React
import React, { useState } from 'react';
import { PersianDateTimePicker } from 'persian-calendar-suite';
function App() {
const [date, setDate] = useState(null);
return (
<PersianDateTimePicker
value={date}
onChange={setDate}
/>
);
}Next.js
'use client'; // Required for Next.js 13+ App Router
import { useState } from 'react';
import { PersianDateTimePicker } from 'persian-calendar-suite';
export default function Page() {
const [date, setDate] = useState(null);
return (
<PersianDateTimePicker
value={date}
onChange={setDate}
/>
);
}Components
PersianDateTimePicker
Single date and time picker with Shamsi calendar.
Import
import { PersianDateTimePicker } from 'persian-calendar-suite';Basic Usage
const [value, setValue] = useState(null);
<PersianDateTimePicker
value={value}
onChange={setValue}
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string \| number \| null | null | Selected date value |
| onChange | (value: string \| number) => void | - | Callback when date changes |
| showTime | boolean | true | Show time picker |
| minuteStep | number | 1 | Minute step interval |
| outputFormat | 'iso' \| 'shamsi' \| 'gregorian' \| 'hijri' \| 'timestamp' | 'iso' | Output format |
| showFooter | boolean | true | Show OK/Cancel buttons |
| theme | ThemeObject | {} | Theme customization |
| disabledHours | number[] | [] | Array of disabled hours |
| minDate | string \| Date | null | Minimum selectable date |
| maxDate | string \| Date | null | Maximum selectable date |
| persianNumbers | boolean | false | Display numbers in Persian digits |
| rtlCalendar | boolean | false | Start calendar from right (Sunday first) |
Example with Options
<PersianDateTimePicker
value={value}
onChange={setValue}
showTime={true}
outputFormat="shamsi"
showFooter={false}
theme={{
primaryColor: '#6366f1',
circularDates: true
}}
/>PersianDateRangePicker
Date range picker with dual calendars.
Import
import { PersianDateRangePicker } from 'persian-calendar-suite';Basic Usage
const [range, setRange] = useState(null);
<PersianDateRangePicker
value={range}
onChange={setRange}
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | [string, string] \| null | null | Selected range [start, end] |
| onChange | (value: [string, string]) => void | - | Callback when range changes |
| placeholder | [string, string] | ['تاریخ شروع', 'تاریخ پایان'] | Placeholder text |
| disabled | boolean | false | Disable the picker |
| outputFormat | 'iso' \| 'shamsi' \| 'gregorian' \| 'hijri' \| 'timestamp' | 'iso' | Output format |
| showFooter | boolean | true | Show OK/Cancel buttons |
| theme | ThemeObject | {} | Theme customization |
| minDate | string \| Date | null | Minimum selectable date |
| maxDate | string \| Date | null | Maximum selectable date |
| persianNumbers | boolean | false | Display numbers in Persian digits |
| rtlCalendar | boolean | false | Start calendar from right (Sunday first) |
Example with Options
<PersianDateRangePicker
value={range}
onChange={setRange}
placeholder={['Start', 'End']}
outputFormat="timestamp"
theme={{
primaryColor: '#10b981'
}}
/>PersianTimePicker
Standalone time picker component with range support.
Import
import { PersianTimePicker } from 'persian-calendar-suite';Basic Usage
const [time, setTime] = useState('');
<PersianTimePicker
value={time}
onChange={setTime}
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string \| string[] | '' | Selected time value (HH:MM or [HH:MM, HH:MM] for range) |
| onChange | (value: string \| string[]) => void | - | Callback when time changes |
| defaultValue | string \| 'now' | null | Default time ('now' or 'HH:MM') |
| minuteStep | number | 1 | Minute step interval |
| disabledHours | number[] | [] | Array of disabled hours |
| placeholder | string | 'انتخاب زمان' | Placeholder text |
| persianNumbers | boolean | false | Display numbers in Persian digits |
| isRange | boolean | false | Enable time range selection |
| theme | ThemeObject | {} | Theme customization |
Example with Options
<PersianTimePicker
value={time}
onChange={setTime}
defaultValue="now"
minuteStep={15}
disabledHours={[0, 1, 2, 22, 23]}
theme={{
primaryColor: '#10b981'
}}
/>Time Range Picker
const [timeRange, setTimeRange] = useState([]);
<PersianTimePicker
value={timeRange}
onChange={setTimeRange}
isRange={true}
minuteStep={15}
/>
// Output: ["09:00", "17:00"]PersianTimeline
Timeline component for displaying chronological events with Persian dates.
Import
import { PersianTimeline } from 'persian-calendar-suite';Basic Usage
const [events] = useState([
{
id: 1,
date: '2025-12-10',
time: '14:30',
title: 'Project Started',
description: 'Initial project setup and planning',
color: '#10b981',
icon: '🚀'
},
{
id: 2,
date: '2025-12-15',
title: 'Milestone Reached',
color: '#6366f1',
image: '/milestone.jpg'
}
]);
<PersianTimeline
events={events}
onEventClick={(event) => console.log(event)}
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| events | Event[] | [] | Array of timeline events |
| onEventClick | (event: Event) => void | - | Callback when event is clicked |
| direction | 'vertical' \| 'horizontal' | 'vertical' | Timeline orientation |
| markerShape | 'circular' \| 'rect' | 'circular' | Shape of event markers |
| showIcons | boolean | true | Show event icons/images |
| alternating | boolean | true | Alternate event sides (vertical only) |
| persianNumbers | boolean | false | Display numbers in Persian digits |
| theme | ThemeObject | {} | Theme customization |
Event Object
{
id: number; // Unique identifier
date: string; // ISO date string (YYYY-MM-DD)
time?: string; // Optional time (HH:mm)
title: string; // Event title
description?: string; // Event description
color?: string; // Event color (hex)
icon?: string | ReactNode; // Event icon (emoji or component)
image?: string; // Event image URL
}Timeline Types
Vertical Timeline (default)
<PersianTimeline
events={events}
direction="vertical"
alternating={true}
/>Horizontal Timeline
<PersianTimeline
events={events}
direction="horizontal"
/>Custom Markers
<PersianTimeline
events={events}
markerShape="rect"
showIcons={true}
/>Advanced Example
const timelineEvents = [
{
id: 1,
date: '2025-01-15',
time: '09:00',
title: 'Project Kickoff',
description: 'Initial team meeting and project planning session',
color: '#10b981',
icon: '🎯'
},
{
id: 2,
date: '2025-02-20',
title: 'Design Phase Complete',
description: 'UI/UX designs approved and ready for development',
color: '#6366f1',
image: '/design-preview.jpg'
},
{
id: 3,
date: '2025-03-30',
time: '16:00',
title: 'Beta Release',
description: 'First beta version released to testing team',
color: '#f59e0b',
icon: <svg>...</svg>
}
];
<PersianTimeline
events={timelineEvents}
direction="vertical"
markerShape="circular"
alternating={true}
persianNumbers={true}
onEventClick={(event) => {
console.log('Timeline event clicked:', event);
}}
theme={{
primaryColor: '#6366f1',
lineColor: '#e5e7eb',
markerSize: '16px',
eventRadius: '12px'
}}
/>PersianMoment
Jalali date arithmetic utility for date calculations and conversions.
Import
import persianMoment from 'persian-calendar-suite/PersianMoment';Basic Usage
// Create Persian dates
const m1 = persianMoment('1404/12/30', 'jYYYY/jMM/jDD');
const m2 = persianMoment('1404/01/02', 'jYYYY/jMM/jDD');
// Calculate differences
console.log(m2.diff(m1, 'day')); // 3
console.log(m2.diff(m1, 'jMonth')); // 1
// Format conversion
console.log(m1.format('YYYY/MM/DD')); // 2025/03/19
console.log(m1.format('jYYYY/jMM/jDD')); // 1404/12/30Supported Input Formats
- Jalali:
'1404/12/30'with format'jYYYY/jMM/jDD' - Gregorian:
'2025/03/19'with format'YYYY/MM/DD' - ISO:
'2025-03-19'(no format needed)
Methods
diff(other, unit, outputFormat)
unit:'day','jDay','jMonth','jYear'outputFormat:'number','persian','persian-text'
format(format)
format:'jYYYY/jMM/jDD','YYYY/MM/DD','iso'
Output Formats
const diff = m2.diff(m1, 'day', 'persian-text');
// Output: "۳ روز"
const diff2 = m2.diff(m1, 'jMonth', 'persian');
// Output: "۱"PersianCalendar
Full-featured calendar with event management (like FullCalendar).
Import
import { PersianCalendar } from 'persian-calendar-suite';Basic Usage
const [events, setEvents] = useState([]);
<PersianCalendar
events={events}
onEventCreate={(event) => setEvents([...events, event])}
onEventUpdate={(updated) => setEvents(events.map(e => e.id === updated.id ? updated : e))}
onEventDelete={(deleted) => setEvents(events.filter(e => e.id !== deleted.id))}
/>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| events | Event[] | [] | Array of event objects |
| onEventCreate | (event: Event) => void | - | Callback when event is created |
| onEventUpdate | (event: Event) => void | - | Callback when event is updated |
| onEventDelete | (event: Event) => void | - | Callback when event is deleted |
| onEventClick | (event: Event) => void | - | Callback when event is clicked |
| initialView | 'day' \| 'week' \| 'month' | 'month' | Initial view mode |
| editable | boolean | true | Enable event creation/editing |
| showWeekends | boolean | true | Show weekend days |
| headerFormat | 'full' \| 'short' | 'full' | Header format |
| disabledHours | number[] | [] | Array of disabled hours (0-23) |
| theme | ThemeObject | {} | Theme customization |
Event Object
{
id: number; // Unique identifier
date: string; // ISO date string (YYYY-MM-DD)
startTime: string; // Start time (HH:mm)
endTime: string; // End time (HH:mm)
title: string; // Event title
color: string; // Event color (hex)
description?: string; // Event description (optional)
isAllDay?: boolean; // All-day event flag
isRecurring?: boolean; // Recurring event flag
recurringType?: 'daily' | 'weekly' | 'monthly' | 'yearly'; // Recurrence pattern
recurringEnd?: string; // End date for recurring events
isMultiDay?: boolean; // Multi-day event flag
endDate?: string; // End date for multi-day events
readOnly?: boolean; // Read-only event flag (cannot be edited)
}Creating Events
Day View: Click on hour slots (00:00 - 23:00)
// User clicks on 09:00 slot
// Modal opens with startTime: "09:00", endTime: "10:00"Week View: Click on time cells for any day
// User clicks on Tuesday 14:00
// Modal opens for that specific day and timeMonth View: Click on day cells
// User clicks on day 15
// Modal opens with default time 09:00Editing Events
Click on any existing event to open the edit modal:
- Modify title, start/end time, color, description
- Delete button available in modal
- Changes trigger
onEventUpdatecallback
Advanced Features
Event Types:
- All-day events: Check "رویداد تمام روز" to create events spanning entire days
- Multi-day events: Check "رویداد چند روزه" to create events spanning multiple days
- Recurring events: Check "رویداد تکراری" for daily/weekly/monthly/yearly repetition
- Today button: Quick navigation to current date
- Event tooltips: Hover over events to see full details
Overlap Detection:
- Shows orange badge with count (e.g., "2 تداخل")
- Highlights slots with light orange background
- Displays events side-by-side in day view
Visual Indicators:
- All-day events: Gradient background with dashed border and sun icon
- Recurring events: Refresh icon after title
- Multi-day and recurring events are mutually exclusive
Full Example
const [events, setEvents] = useState([
{
id: 1,
date: '2025-12-10',
startTime: '09:00',
endTime: '10:00',
title: 'Team Meeting',
color: '#10b981',
description: 'Weekly team sync',
isRecurring: true,
recurringType: 'weekly'
},
{
id: 2,
date: '2025-12-11',
title: 'Company Event',
color: '#ef4444',
isAllDay: true
}
]);
<PersianCalendar
events={events}
onEventCreate={(event) => {
console.log('Created:', event);
setEvents([...events, event]);
}}
onEventUpdate={(updated) => {
console.log('Updated:', updated);
setEvents(events.map(e => e.id === updated.id ? updated : e));
}}
onEventDelete={(deleted) => {
console.log('Deleted:', deleted);
setEvents(events.filter(e => e.id !== deleted.id));
}}
onEventClick={(event) => {
console.log('Clicked:', event);
}}
initialView="day"
editable={true}
theme={{
primaryColor: '#6366f1'
}}
disabledHours={[0, 1, 2, 22, 23]} // Disable late night/early morning hours
/>Theme Customization
All components support theme customization via the theme prop.
Theme Object
{
primaryColor?: string; // Primary color (default: '#1890ff')
backgroundColor?: string; // Background color (default: '#ffffff')
textColor?: string; // Text color (default: '#000000')
borderColor?: string; // Border color (default: '#d9d9d9')
hoverColor?: string; // Hover color (default: '#f0f0f0')
selectedTextColor?: string; // Selected text color (default: '#ffffff')
circularDates?: boolean; // Circular date cells (default: false)
// Calendar specific
headerBg?: string; // Header background (default: '#fafafa')
eventRadius?: string; // Event border radius (default: '6px')
shadow?: string; // Shadow (default: '0 2px 8px rgba(0,0,0,0.08)')
}Example
const customTheme = {
primaryColor: '#6366f1',
backgroundColor: '#ffffff',
textColor: '#1f2937',
borderColor: '#e5e7eb',
hoverColor: '#f3f4f6',
circularDates: true
};
<PersianDateTimePicker
value={value}
onChange={setValue}
theme={customTheme}
/>Output Formats
ISO 8601 (default)
2025-12-10T14:30:00Shamsi
1404/09/20 14:30Gregorian
2025/12/10 14:30Hijri
1446/06/08 14:30Timestamp
1702217400000Changing Format
<PersianDateTimePicker
value={value}
onChange={setValue}
outputFormat="shamsi" // or 'iso', 'gregorian', 'hijri', 'timestamp'
/>Programmatic Control
Reading Data
All components are controlled - you manage the state:
const [date, setDate] = useState(null);
const [range, setRange] = useState(null);
const [events, setEvents] = useState([]);
// Read current values anytime
console.log('Current date:', date);
console.log('Current range:', range);
console.log('All events:', events);Setting Data Programmatically
Set Date/Time
const [date, setDate] = useState(null);
// Set to current date
setDate(new Date().toISOString());
// Set specific date (ISO format)
setDate('2025-12-10T14:30:00');
// Set via timestamp
setDate(1702217400000);
// Clear date
setDate(null);Set Date Range
const [range, setRange] = useState(null);
// Set range
setRange(['2025-12-01T00:00:00', '2025-12-31T23:59:59']);
// Set last 7 days
const end = new Date();
const start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
setRange([start.toISOString(), end.toISOString()]);
// Clear range
setRange(null);Manage Events
const [events, setEvents] = useState([]);
// Add event programmatically
const addEvent = () => {
const newEvent = {
id: Date.now(),
date: '2025-12-15',
startTime: '10:00',
endTime: '11:00',
title: 'New Meeting',
color: '#3b82f6',
description: 'Auto-created event'
};
setEvents([...events, newEvent]);
};
// Update specific event
const updateEvent = (id, updates) => {
setEvents(events.map(e =>
e.id === id ? { ...e, ...updates } : e
));
};
// Delete event
const deleteEvent = (id) => {
setEvents(events.filter(e => e.id !== id));
};
// Clear all events
setEvents([]);
// Load events from API
fetch('/api/events')
.then(res => res.json())
.then(data => setEvents(data));Dynamic Updates
Auto-select Today
const [date, setDate] = useState(new Date().toISOString());
// Update to today on mount
useEffect(() => {
setDate(new Date().toISOString());
}, []);Filter Events by Date
const [events, setEvents] = useState([...]);
const [selectedDate, setSelectedDate] = useState('2025-12-10');
// Get events for specific date
const todayEvents = events.filter(e => e.date === selectedDate);
// Get events in date range
const rangeEvents = events.filter(e =>
e.date >= '2025-12-01' && e.date <= '2025-12-31'
);Bulk Operations
// Change all event colors
const changeAllColors = (newColor) => {
setEvents(events.map(e => ({ ...e, color: newColor })));
};
// Shift all events by 1 hour
const shiftEvents = () => {
setEvents(events.map(e => ({
...e,
startTime: addHour(e.startTime),
endTime: addHour(e.endTime)
})));
};
// Delete events by criteria
const deletePastEvents = () => {
const today = new Date().toISOString().split('T')[0];
setEvents(events.filter(e => e.date >= today));
};Responding to Changes
// Track date changes
const [date, setDate] = useState(null);
useEffect(() => {
if (date) {
console.log('Date changed to:', date);
// Trigger API call, validation, etc.
}
}, [date]);
// Track event changes
const [events, setEvents] = useState([]);
useEffect(() => {
console.log('Events updated:', events.length);
// Save to localStorage, sync with backend, etc.
localStorage.setItem('events', JSON.stringify(events));
}, [events]);Complete Interactive Example
function App() {
const [events, setEvents] = useState([]);
const [selectedDate, setSelectedDate] = useState(null);
// Add event via button
const addQuickEvent = () => {
setEvents([...events, {
id: Date.now(),
date: new Date().toISOString().split('T')[0],
startTime: '09:00',
endTime: '10:00',
title: 'Quick Event',
color: '#10b981'
}]);
};
// Jump to specific date
const jumpToDate = (dateStr) => {
setSelectedDate(dateStr);
};
// Export events
const exportEvents = () => {
const json = JSON.stringify(events, null, 2);
console.log(json);
// Download or send to API
};
// Import events
const importEvents = (jsonData) => {
setEvents(JSON.parse(jsonData));
};
return (
<div>
<button onClick={addQuickEvent}>Add Event</button>
<button onClick={() => jumpToDate('2025-12-25')}>Jump to Dec 25</button>
<button onClick={exportEvents}>Export</button>
<PersianCalendar
events={events}
onEventCreate={(e) => setEvents([...events, e])}
onEventUpdate={(e) => setEvents(events.map(ev => ev.id === e.id ? e : ev))}
onEventDelete={(e) => setEvents(events.filter(ev => ev.id !== e.id))}
/>
</div>
);
}Examples
DateTime Picker with Time
<PersianDateTimePicker
value={value}
onChange={setValue}
showTime={true}
minuteStep={15}
/>Date Only (No Time)
<PersianDateTimePicker
value={value}
onChange={setValue}
showTime={false}
/>Date Restrictions
<PersianDateTimePicker
value={value}
onChange={setValue}
minDate="2025-01-01"
maxDate="2025-12-31"
/>Time Picker with Default
<PersianTimePicker
value={time}
onChange={setTime}
defaultValue="now"
minuteStep={15}
/>Auto-close (No Footer)
<PersianDateTimePicker
value={value}
onChange={setValue}
showFooter={false}
/>Range Picker with Timestamp
<PersianDateRangePicker
value={range}
onChange={setRange}
outputFormat="timestamp"
/>Calendar with Custom Theme
<PersianCalendar
events={events}
onEventCreate={handleCreate}
onEventUpdate={handleUpdate}
onEventDelete={handleDelete}
theme={{
primaryColor: '#ef4444',
headerBg: '#fee2e2',
eventRadius: '12px'
}}
/>Read-only Calendar
<PersianCalendar
events={events}
onEventClick={handleClick}
editable={false}
/>Mobile Support
All components are fully responsive and optimized for mobile devices:
- Touch-friendly interface with larger tap targets
- Adaptive layouts for small screens
- Centered modals on mobile
- Optimized spacing and font sizes
- Smooth scrolling within pickers
Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Opera (latest)
- Mobile browsers (iOS Safari, Chrome Mobile, Samsung Internet)
Persian Holiday Integration
All calendar components support Persian holiday integration via the Time.ir API:
<PersianDateTimePicker showHolidays={true} />
<PersianDateRangePicker showHolidays={true} />
<PersianCalendar showHolidays={true} />Holiday Features
- Automatic Loading: Holidays load automatically for the current month
- Visual Highlighting: Holiday dates appear with red background
- Read-Only: Holidays cannot be edited or deleted
- Clickable: Click holidays to see details
- API Integration: Uses Time.ir Persian calendar API with CORS proxy
License
MIT © BBBird1450
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Support
For issues and questions:
- Open an issue on GitHub
Changelog
See CHANGELOG.md for release history.
