پرش به مطلب اصلی

Jalali — تقویم شمسی

یک شیء تاریخ شمسی ، با پیاده‌سازی مستقل بر پایهٔ Carbon — با امکان تبدیل به/از Carbon و Hijri، و مدیریت تایم‌زون ذخیره‌سازی و نمایش به‌صورت پویا.

نصب و راه‌اندازی

ماژول Jalali بخشی از dornica/doravel است و نیازی به نصب جداگانه ندارد.

use Dornica\Foundation\Jalali\Facade\Jalali;
// یا کلاس
use Dornica\Foundation\Jalali\Jalali;

نکته: تابع کمکی سراسری jalali() توسط این پکیج تأمین می‌شود (جایگزین تابع کمکی هم‌نام در مجموعهٔ پکیج‌ها؛ verta و verta() بدون تغییر باقی می‌مانند).


متدها

now()

تاریخ شمسی لحظهٔ جاری:

$date = Jalali::now();
$date = Jalali::now('Asia/Tehran');
$date = Jalali::now(new DateTimeZone('Asia/Tehran'));

// با تابع کمکی
$date = jalali();

today / tomorrow / yesterday

$date = Jalali::today();
$date = Jalali::tomorrow();
$date = Jalali::yesterday();

parse()

ساخت از رشتهٔ شمسی — فرمت‌های مجاز: Y-m-d یا Y/m/d با یا بدون زمان:

$date = Jalali::parse('1403-05-14');
$date = Jalali::parse('1403/05/14');
$date = Jalali::parse('1403/05/14 13:30:00');

// با تابع کمکی
$date = jalali('1403/05/14');
از ارسال رشتهٔ میلادی به jalali() خودداری کنید

parse() — و در نتیجه jalali('...') — رشته را همیشه شمسی فرض می‌کند و هیچ تشخیصی روی میلادی بودن انجام نمی‌دهد. نکتهٔ مهم‌تر اینکه در بیشتر موارد خطایی صادر نمی‌شود و تاریخ نادرست بازگردانده می‌شود:

// ❌ نادرست و بدون خطا — «2026-07-15» عیناً شمسی خوانده می‌شود
jalali('2026-07-15')->format('Y/m/d'); // 2026/07/15 (مقدار صحیح: 1405/04/24)

// ❌ بروز خطا قطعی نیست و تنها در برخی حالت‌ها رخ می‌دهد
jalali('2026-07-31');
// InvalidJalaliDateException: Invalid Jalali date: 2026/7/31
// (به دلیل اینکه روز 31 در ماه 7 شمسی وجود ندارد، نه به دلیل میلادی بودن سال)

// ✅ شیء تاریخ را مستقیماً ارسال کنید
jalali($user->created_at);
jalali(new DateTime('2026-07-31'));

// ✅ یا در صورتی که تنها اجزای میلادی در دسترس است
Jalali::createGregorian(2026, 7, 31);

رایج‌ترین موقعیت بروز این خطا زمانی است که تاریخ ابتدا با format() به رشته تبدیل شده و سپس همان رشته به jalali() ارسال می‌شود:

// ❌ تبدیل غیرضروری به رشته و بازگردانی مجدد
$key = $date->format('Y-m-d');
$label = jalali($key)->format('m/d');

// ✅ تبدیل را روی خود شیء تاریخ انجام دهید
$key = $date->format('Y-m-d');
$label = jalali($date)->format('m/d');

تابع کمکی jalali() برای Carbon، DateTimeInterface، Hijri و timestamp عددی مسیر صحیح را به‌صورت خودکار انتخاب می‌کند؛ تنها رشته است که شمسی فرض می‌شود.

create()

ساخت از اجزای شمسی:

$date = Jalali::create(1403, 5, 14);
$date = Jalali::create(1403, 5, 14, 13, 30, 0, 'Asia/Tehran');

createGregorian()

ساخت از اجزای میلادی:

$date = Jalali::createGregorian(2024, 8, 4);
$date = Jalali::createGregorian(2024, 8, 4, 13, 30, 0, 'Asia/Tehran');

fromCarbon()

ساخت از Carbon:

$date = Jalali::fromCarbon(Carbon::now());

// با تابع کمکی
$date = jalali(Carbon::now());

fromHijri()

ساخت از Hijri — تبدیل تاریخ قمری به شمسی:

$date = Jalali::fromHijri(hijri('1447-09-15'));

// با تابع کمکی
$date = jalali(hijri('1447-09-15'));

زنجیرهٔ تبدیل: Hijri (قمری)Carbon (میلادی)Jalali (شمسی)

fromDateTime()

ساخت از DateTime:

$date = Jalali::fromDateTime(new DateTime('2024-08-04'));

// با تابع کمکی
$date = jalali(new DateTime('2024-08-04'));

fromTimestamp()

ساخت از Unix timestamp:

$date = Jalali::fromTimestamp(1722765645);
$date = Jalali::fromTimestamp(1722765645, 'Asia/Tehran');

// با تابع کمکی
$date = jalali(1722765645);

فرمت‌بندی

متدهای آماده

$date = jalali('1403/05/14 13:30:45');

$date->formatDate(); // 1403/05/14
$date->formatDatetime(); // 1403/05/14 13:30:45
$date->formatTime(); // 13:30:45
$date->formatWord(); // یکشنبه 14 مرداد 1403
(string) $date; // 1403/05/14 13:30:45

format()

فرمت‌بندی سفارشی مشابه date() در PHP:

$date->format('Y-m-d'); // 1403-05-14
$date->format('l j F Y'); // یکشنبه 14 مرداد 1403
$date->format('Y/m/d H:i'); // 1403/05/14 13:30
$date->format('\\Y=Y'); // Y=1403 (کاراکتر گریز با \)

جدول توکن‌ها:

توکنتوضیحمثال
Yسال چهار رقمی1403
yسال دو رقمی03
mماه دو رقمی05
nماه بدون صفر5
dروز دو رقمی14
jروز بدون صفر14
Hساعت ۲۴ ساعته13
Gساعت ۲۴ ساعته بدون صفر13
hساعت ۱۲ ساعته01
gساعت ۱۲ ساعته بدون صفر1
iدقیقه30
sثانیه45
Aقبل/بعد از ظهربعد از ظهر
aق.ظ / ب.ظب.ظ
Fنام ماهمرداد
lنام روز هفتهیکشنبه
Dنام کوتاه روزی
w / Nشماره روز هفته (شنبه=۱)2
tتعداد روزهای ماه31
Lسال کبیسه (۱/۰)1
UUnix timestamp1722765645

نام ماه و روز

$date->monthName(); // مرداد
$date->dayName(); // یکشنبه
$date->dayOfWeek(); // 2 (شنبه = 1 … جمعه = 7)

تبدیل

$date = jalali('1403/05/14 13:30:45');

// به Carbon (میلادی)
$carbon = $date->toCarbon();
$carbon->format('Y-m-d'); // 2024-08-04

// به Hijri (قمری)
$hijri = $date->toHijri();
$hijri->format('Y/m/d'); // 1446/01/28

// به DateTime
$dt = $date->toDateTime();

// اجزای شمسی به‌صورت آرایه [سال، ماه، روز]
$date->toArrayJalali(); // [1403, 5, 14]

// Unix timestamp
$date->timestamp(); // int

تغییر تاریخ (Mutation)

شیء Jalali تغییرپذیر (mutable) است

برخلاف Hijri که تغییرناپذیر (immutable) است، شیء Jalali تغییرپذیر است: متدهای add*/sub* خودِ شیء را تغییر می‌دهند و همان شیء را بازمی‌گردانند. برای حفظ نسخهٔ اصلی از copy() استفاده کنید.

$start = jalali();
$end = $start->addDays(20); // ❌ نادرست: 20 روز به $start نیز افزوده می‌شود (همان شیء)
$end = $start->copy()->addDays(20); // ✔ درست: نسخهٔ مستقل

محاسبات ماه و سال به‌صورت شمسی‌آگاه انجام می‌شوند (روز به آخرین روز معتبر ماه مقصد محدود می‌شود)؛ سایر واحدها به Carbon واگذار می‌شوند.

$base = jalali('1403/05/14 12:00:00');

// روز / هفته
$base->copy()->addDay(); // 1403/05/15
$base->copy()->addDays(20); // 1403/06/03
$base->copy()->subDays(14); // 1403/04/31
$base->copy()->addWeek(); // 1403/05/21

// ماه (شمسی‌آگاه)
$base->copy()->addMonth(); // 1403/06/14
$base->copy()->addMonths(10); // 1404/03/14 (سرریز به سال بعد)
$base->copy()->subMonths(6); // 1402/11/14

// سال (شمسی‌آگاه + محدودسازی روز در اسفند)
$base->copy()->addYear(); // 1404/05/14
Jalali::create(1403, 12, 30)->addYear(); // 1404/12/29 (سال 1404 کبیسه نیست)

// ساعت / دقیقه / ثانیه
$base->copy()->addHours(14);
$base->copy()->addMinutes(90);
$base->copy()->subSeconds(30);

// شروع/پایان (واگذاری به Carbon)
$base->copy()->startOfDay();
$base->copy()->endOfDay();

تنظیم‌کننده‌ها

$date->setTime(14, 30, 0);
$date->setDateJalali(1404, 1, 1);

تایم‌زون (ذخیره‌سازی و نمایش)

Jalali امکان تنظیم مستقل تایم‌زون ذخیره‌سازی (مقداری که در پایگاه داده نوشته می‌شود) و تایم‌زون نمایش را فراهم می‌کند — حتی خارج از چرخهٔ راه‌اندازی لاراول.

فایل کانفیگ config/jalali.php:

'timezone' => 'Asia/Tehran', // تایم‌زون پیش‌فرض ساخت
'storage' => 'UTC', // تایم‌زون ذخیره‌سازی در پایگاه داده
'display' => 'Asia/Tehran', // تایم‌زون نمایش به کاربر

تنظیم پویا (در هر بستری، حتی خارج از لاراول):

Jalali::setDefaultTimezone('Asia/Tehran');
Jalali::setStorageTimezone('UTC');
Jalali::setDisplayTimezone('Asia/Tehran');

Jalali::defaultTimezone(); // Asia/Tehran
Jalali::storageTimezone(); // UTC
Jalali::displayTimezone(); // Asia/Tehran

تبدیل بین دو تایم‌زون:

Jalali::setStorageTimezone('UTC');
Jalali::setDisplayTimezone('Asia/Tehran');

$d = Jalali::createGregorian(2024, 3, 20, 10, 0, 0, 'Asia/Tehran'); // 10:00 تهران

$d->toStorage(); // Carbon: 2024-03-20 06:30:00 (UTC) ← برای ذخیره در پایگاه داده
$d->toDisplay(); // همان لحظه در تایم‌زون نمایش (تهران)

// تایم‌زون خود شیء
$d->setTimezone('UTC'); // (alias: $d->timezone('UTC'))
$d->getTimezone(); // DateTimeZone

Eloquent Cast

برای اینکه یک ستون به‌طور خودکار با تایم‌زون ذخیره‌سازی و نمایش هماهنگ شود، از cast استفاده کنید:

use Dornica\Foundation\Jalali\Casts\JalaliDate;

protected function casts(): array
{
return [
'published_at' => JalaliDate::class, // فرمت پیش‌فرض: Y-m-d H:i:s
'birth_date' => JalaliDate::class.':Y-m-d', // فرمت سفارشی
];
}
  • هنگام خواندن: مقدار در تایم‌زون storage تفسیر و به display منتقل می‌شود و یک شیء Jalali بازگردانده می‌شود.
  • هنگام نوشتن: مقدار به تایم‌زون storage تبدیل می‌شود. بنابراین هر مقداری (now()، jalali()، Carbon، رشتهٔ شمسی) به‌درستی ذخیره می‌شود.
$model->published_at = jalali(); // یا now() یا Carbon
$model->save(); // در پایگاه داده با تایم‌زون storage (مثلاً UTC) ذخیره می‌شود

$model->published_at->formatDatetime(); // نمایش شمسی در تایم‌زون display

مقایسه

متدهای مقایسه، Jalali، Carbon یا رشتهٔ شمسی را می‌پذیرند.

$a = jalali('1403/01/01');
$b = jalali('1403/01/11');

$a->eq($b); // false
$a->ne($b); // true
$b->gt($a); // true
$a->gte($a); // true
$a->lt($b); // true
$a->lte($b); // true

// بین دو تاریخ
jalali('1403/01/05')->between($a, $b); // true

// وضعیت نسبت به زمان حال
jalali('1400/01/01')->isPast(); // true
jalali('1500/01/01')->isFuture(); // true
jalali()->isToday(); // true

// روز هفته (واگذاری به Carbon)
$a->isSaturday(); // ...
$a->isFriday(); // ...

تفاوت

$a->diffDays($b); // 10
$a->diffHours($b);
$a->diffMinutes($b);
$a->diffSeconds($b);

jalali()->subDays(3)->diffForHumans(); // 3 روز پیش (فارسی)

اعتبارسنجی (Validation)

Jalali::isValidDate(1403, 5, 14); // true
Jalali::isValidDate(1403, 13, 1); // false — ماه نامعتبر
Jalali::isValidDate(1402, 12, 30); // false — سال 1402 کبیسه نیست
Jalali::isValidDate(1403, 12, 30); // true — سال 1403 کبیسه است

jalali('1403/05/14')->isLeapYear(); // true
jalali('1403/05/14')->daysInMonth(); // 31

قوانین اعتبارسنجی فرم (Validation Rules)

قوانین اعتبارسنجی تاریخ و زمان شمسی (jalali_date، jalali_datetime و قوانین مقایسه‌ای مانند jalali_date_after) به‌همراه سایر قوانین اعتبارسنجی پکیج، در یک صفحهٔ متمرکز مستند شده‌اند:

قوانین اعتبارسنجی - قوانین تاریخ جلالی


تابع کمکی jalali()

jalali() // لحظهٔ جاری
jalali('1403/05/14') // تجزیهٔ رشتهٔ شمسی
jalali('1403/05/14 13:30:00') // با زمان
jalali(Carbon::now()) // از Carbon
jalali(hijri('1447-09-15')) // از Hijri (قمری → شمسی)
jalali(new DateTime()) // از DateTime
jalali(1722765645) // از Unix timestamp
jalali('1403/05/14', 'Asia/Tehran') // با تایم‌زون

Facade

use Dornica\Foundation\Jalali\Facade\Jalali;

Jalali::now();
Jalali::today();
Jalali::parse('1403/05/14');
Jalali::create(1403, 5, 14);
Jalali::createGregorian(2024, 8, 4);
Jalali::fromCarbon(Carbon::now());
Jalali::fromHijri(hijri('1447-09-15'));
Jalali::isValidDate(1403, 5, 14);

Jalali::setStorageTimezone('UTC');
Jalali::setDisplayTimezone('Asia/Tehran');

متدهای دسترسی (Getters)

$date = jalali('1403/05/14 13:30:45');

// متد
$date->year(); // 1403
$date->month(); // 5
$date->day(); // 14
$date->hour(); // 13
$date->minute(); // 30
$date->second(); // 45

// magic property
$date->year; // 1403
$date->month; // 5
$date->day; // 14
$date->hour; // 13
$date->minute; // 30
$date->second; // 45

استثناها (Exceptions)

Exceptionشرایط بروز
InvalidJalaliDateExceptionتاریخ نامعتبر، ماه خارج از ۱–۱۲، فرمت ناشناس
use Dornica\Foundation\Jalali\Exceptions\InvalidJalaliDateException;

try {
$date = jalali('invalid-date');
} catch (InvalidJalaliDateException $e) {
// فرمت نامعتبر
}

try {
$date = Jalali::create(1403, 13, 1);
} catch (InvalidJalaliDateException $e) {
// ماه نامعتبر
}