On this page
Validation rules#
Once the package is installed (Laravel setup), six rules are available in any validator, form request or $request->validate() call. They have seven names, because mobile is a short alias of iran_mobile. They wrap the validators described on the validation pages.
| Rule | Accepts | Details |
|---|---|---|
national_code | Iranian national code | National code |
sheba | Sheba / IBAN, with or without the IR prefix | Sheba and bank card |
bank_card | 16-digit bank card number (Luhn check) | Sheba and bank card |
iran_mobile | Iranian mobile number | Mobile, postal code, plate |
mobile | Short alias of iran_mobile | same behaviour |
postal_code | 10-digit Iranian postal code | Mobile, postal code, plate |
vehicle_plate | Iranian vehicle plate, for example 12ب345-67 | Mobile, postal code, plate |
mobile is a generic word. If another package or your own code registers a rule named mobile, use iran_mobile everywhere so there is no doubt which rule runs.Using them#
use Illuminate\Http\Request;
public function store(Request $request)
{
$data = $request->validate([
'national_code' => 'required|national_code',
'mobile' => 'required|iran_mobile',
'card' => 'nullable|bank_card',
'iban' => 'nullable|sheba',
'postal' => 'required|postal_code',
'plate' => 'nullable|vehicle_plate',
]);
}
A real validation run with bad data and the locale fa gave:
// national_code = '1234567890', mobile = '0912', iban = 'IR000000000000000000000000'
// card, postal and plate hold valid values and pass.
//
// national_code: national code کد ملی معتبر نیست.
// mobile: mobile شماره موبایل معتبر نیست.
// iban: iban شماره شبا معتبر نیست.
The English text is "The national code is not a valid Iranian national code." and an Arabic text is available too. See translations and overriding messages.
What input the rules accept#
- Strings. Persian and Arabic digits are accepted.
۰۰۱۳۵۴۲۴۱۹passesnational_code. - Integers and floats are converted to a string first. Be careful with numbers that start with zero. The JSON number
13542419has lost the leading zeros of0013542419and fails. Send national codes, mobile numbers and postal codes as strings. - Arrays, objects and booleans never reach the validator and fail the rule.
- Empty values. As with Laravel's own rules, an empty string and a missing key are skipped unless the field is
required. An explicitnullfails unless the field isnullable. Say what you mean withrequiredornullable.
$f->make(['n' => '۰۰۱۳۵۴۲۴۱۹'], ['n' => 'national_code'])->passes(); // true
$f->make(['n' => 13542419], ['n' => 'national_code'])->passes(); // false (leading zeros lost)
$f->make(['n' => null], ['n' => 'nullable|national_code'])->passes(); // true
$f->make(['n' => null], ['n' => 'national_code'])->passes(); // false
$f->make(['n' => ''], ['n' => 'national_code'])->passes(); // true (skipped)
$f->make(['n' => ''], ['n' => 'required|national_code'])->passes(); // false
$f->make([], ['n' => 'national_code'])->passes(); // true (key missing)
If you need the reason a value failed (wrong length, bad checksum, wrong type), call the standalone validators. They return a structured Result. See Validators overview.
The JalaliCast Eloquent cast#
RtlyKit\Laravel\Casts\JalaliCast keeps the database column in Gregorian (Y-m-d H:i:s), as every tool expects. On the model, the attribute is an immutable RtlyKit\Calendar\Jalali object.
use Illuminate\Database\Eloquent\Model;
use RtlyKit\Laravel\Casts\JalaliCast;
class Post extends Model
{
protected $guarded = [];
protected $casts = [
'published_at' => JalaliCast::class,
];
}
$post = new Post();
$post->published_at = '1404/01/15 10:30';
echo $post->getAttributes()['published_at']; // 2025-04-04 10:30:00 (stored value)
echo $post->published_at->format('Y/m/d H:i'); // 1404/01/15 10:30
echo get_class($post->published_at); // RtlyKit\Calendar\Jalali
echo $post->published_at->addDays(20)->format('Y/m/d'); // 1404/02/04
The column type stays datetime or timestamp. You need no migration change.
Accepted values on assignment#
| Value assigned | Stored (Gregorian) |
|---|---|
Jalali string '1404/01/15 10:30' | 2025-04-04 10:30:00 |
Jalali date only, Persian digits and dashes '۱۴۰۴-۰۱-۱۵' | 2025-04-04 00:00:00 |
Gregorian string '2025-04-04 08:00:00' | 2025-04-04 08:00:00 |
Unix timestamp (int) 1743753600 | 2025-04-04 08:00:00 (with a UTC default time zone) |
Any DateTimeInterface (Carbon included) | the same moment, as Y-m-d H:i:s |
A Jalali object | its Gregorian equivalent |
null or '' | null |
A string is read as Jalali when its year is 1200 to 1599 and the separator is / or -, with an optional time HH:MM or HH:MM:SS after a space or T. Any other string goes through the normal date parser as Gregorian.
Errors#
Bad input throws RtlyKit\Exceptions\InvalidDateException when you assign it, never a raw PHP error:
$post->published_at = '1404/13/01'; // Invalid Jalali date: 1404/13/1
$post->published_at = '1404/07/31'; // Invalid Jalali date: 1404/7/31 (Mehr has 30 days)
$post->published_at = 'not a date'; // Unable to parse date: not a date
$post->published_at = 1.5; // Cannot cast value for 'published_at' to a date.
$post->published_at = ['x']; // Cannot cast value for 'published_at' to a date.
Validate user input before it reaches the model (see the form request below), so a typo gives a form error and not an exception. Reading is relaxed for null and the empty string (both give null). An attribute that holds unreadable text in the database throws InvalidDateException when you read it.
Serialising a model#
Jalali, Hijri and Hebrew implement JsonSerializable. toJson() on a model with a JalaliCast attribute gives the same text as (string) $date, for example {"published_at":"1404/01/01 10:00:00"}. toArray() keeps the Jalali object. Format it yourself if you need another pattern.
Form requests#
There is no built-in rule for a Jalali date field. Check the shape with Laravel's regex rule or a small custom rule, then let the cast do the conversion:
public function rules(): array
{
return [
'published_at' => ['required', 'regex:/^1[2-5]\d\d[\/-]\d{1,2}[\/-]\d{1,2}$/u'],
];
}
A well-formed but impossible date such as 1404/07/31 still reaches the cast and throws there. Catch InvalidDateException in a custom rule or in the controller if users type free text.
Blade#
The cast gives you a Jalali object, so Blade needs no helper:
{{ $post->published_at?->format('Y/m/d') }} {{-- 1404/01/15 --}}
{{ $post->published_at?->format('l j F Y') }} {{-- جمعه 15 فروردین 1404 --}}
{{ \RtlyKit\to_persian_digits($post->published_at->format('Y/m/d')) }} {{-- ۱۴۰۴/۰۱/۱۵ --}}
For a column that is not cast, such as the standard created_at (a Carbon instance), use the namespaced helper. Namespaced helpers are always loaded, so you register nothing:
{{ \RtlyKit\jdate($post->created_at)->format('Y/m/d H:i') }} {{-- 1404/01/15 08:00 --}}
If you use it often, import the function at the top of a Blade file with @php use function RtlyKit\jdate; @endphp. With the Carbon macros the same call is $post->created_at->jformat('Y/m/d'). See Carbon macros.
?->) as shown, or Blade throws on null. For the full list of format letters, see the Jalali page.