borsaco/jalali-date-time
Convert dates between Gregorian and Jalali (Shamsi/Hijri Shamsi) calendars in PHP. Simple standalone class or Composer package, with support for dates beyond the 2038 limit.
Installation:
composer require borsaco/jalali-date-time
Or manually include jdatetime.class.php in your project.
Basic Usage:
require_once 'vendor/borsaco/jalali-date-time/jdatetime.class.php';
$jalaliDate = new JalaliDateTime('2023-10-25'); // Gregorian input
echo $jalaliDate->format('Y-m-d'); // Outputs Jalali date (e.g., 1402-08-03)
First Use Case:
Convert a Gregorian Carbon instance to Jalali:
use Carbon\Carbon;
$gregorianDate = Carbon::now();
$jalaliDate = new JalaliDateTime($gregorianDate->toDateString());
echo $jalaliDate->format('Y/m/d H:i:s'); // Jalali formatted output
Date Conversion:
$jalali = new JalaliDateTime('2023-10-25');
echo $jalali->format('Y/m/d'); // 1402/08/03
$gregorian = new JalaliDateTime('1402-08-03', true); // Second param: $jalaliInput
echo $gregorian->format('Y-m-d'); // 2023-10-25
Integration with Laravel:
use borsaco\JalaliDateTime\JalaliDateTime;
class Post extends Model {
protected $dates = ['published_at'];
public function getPublishedAtAttribute($value) {
return JalaliDateTime::createFromFormat('Y-m-d', $value)->format('Y/m/d');
}
public function setPublishedAtAttribute($value) {
$this->attributes['published_at'] = JalaliDateTime::createFromFormat('Y/m/d', $value)->format('Y-m-d');
}
}
Timezone Handling:
DateTime methods for timezone-aware operations:
$jalaliDate = new JalaliDateTime('2023-10-25 14:30:00');
$jalaliDate->setTimezone(new DateTimeZone('Asia/Tehran'));
echo $jalaliDate->format('Y/m/d H:i:s');
Validation:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make(['date' => '1402/08/03'], [
'date' => 'required|date_format:Y/m/d|after_or_equal:1300/01/01',
]);
Carbon Compatibility:
Carbon instances bidirectionally:
$carbon = Carbon::parse('2023-10-25');
$jalali = new JalaliDateTime($carbon->toDateString());
$carbonFromJalali = Carbon::parse($jalali->format('Y-m-d'));
Middleware for Jalali Dates: Parse/format Jalali dates globally in requests/responses:
namespace App\Http\Middleware;
use Closure;
use borsaco\JalaliDateTime\JalaliDateTime;
class ConvertJalaliDates {
public function handle($request, Closure $next) {
$request->merge([
'jalali_date' => JalaliDateTime::createFromFormat('Y/m/d', $request->date)->format('Y-m-d')
]);
return $next($request);
}
}
API Responses:
Use Laravel’s Response macros to auto-format dates:
Response::macro('jalali', function ($data, $status = 200, array $headers = [], $options = 0) {
return $this->json($this->convertDatesToJalali($data), $status, $headers, $options);
});
protected function convertDatesToJalali($data) {
if (is_array($data)) {
return array_map([$this, 'convertDatesToJalali'], $data);
} elseif ($data instanceof \DateTime) {
return (new JalaliDateTime($data))->format('Y/m/d H:i:s');
}
return $data;
}
Year 2038 Limitation:
14000-01-01). The underlying logic may still fail for extreme values.Time Handling:
H:i:s for time-aware operations:
// ❌ Silent failure (time defaults to 00:00:00)
$jalaliDate = new JalaliDateTime('2023-10-25');
// ✅ Explicit time
$jalaliDate = new JalaliDateTime('2023-10-25 14:30:00');
Leap Year Bugs:
1404-03-31) may cause off-by-one errors. Validate with:
$jalaliDate = new JalaliDateTime('1404-03-31');
if (!$jalaliDate->isValid()) {
// Handle invalid date (e.g., throw exception or adjust)
}
Locale Conflicts:
setlocale() or IntlDateFormatter unless you reset the locale afterward:
setlocale(LC_TIME, 'fa_IR'); // May interfere with JalaliDateTime
// ... operations ...
setlocale(LC_TIME, 'C'); // Reset to default
Carbon Incompatibility:
Carbon instances without converting to string may fail:
// ❌ Fails silently
$jalaliDate = new JalaliDateTime(Carbon::now());
// ✅ Works
$jalaliDate = new JalaliDateTime(Carbon::now()->toDateTimeString());
Validate Dates:
Use the isValid() method to check for invalid dates:
$jalaliDate = new JalaliDateTime('invalid-date');
if (!$jalaliDate->isValid()) {
throw new \InvalidArgumentException('Invalid Jalali date');
}
Log Raw Conversions: Debug conversion issues by logging intermediate steps:
$gregorian = new JalaliDateTime('1402-08-03', true);
\Log::debug('Gregorian timestamp:', $gregorian->getTimestamp());
Test Edge Cases:
1402-08-02 to 1402-08-03).1402-08-03 23:59:59).Custom Formatters: Extend the class to add Jalali-specific formatters:
class ExtendedJalaliDateTime extends \JalaliDateTime {
public function formatPersianWeekday() {
$weekdays = ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
return $weekdays[$this->format('w')];
}
}
Database Storage: Store Jalali dates in Gregorian format in the DB for consistency:
// Model
protected $dates = ['jalali_published_at'];
public function setJalaliPublishedAtAttribute($value) {
$this->attributes['jalali_published_at'] = (new JalaliDateTime($value))->format('Y-m-d');
}
public function getJalaliPublishedAtAttribute($value) {
return (new JalaliDateTime($value))->format('Y/m/d');
}
Service Provider: Bind the package to Laravel’s container for dependency injection:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->bind(JalaliDateTime::class, function () {
return new \JalaliDateTime();
How can I help you explore Laravel packages today?