Files
hoshpoint-backend/app/Support/Jalali.php
soheil khaledabadi 936f789ac8 feat: Persian panel/API fixes and structural-model tweaks
- lang/fa: add validation/auth/passwords/pagination translations so admin
  and API validation errors are in Persian
- templates: soft-delete instead of cascade-deleting their generations;
  keep resolving trashed template on generations (labels/downloads)
- templates: add TemplateInfolist with a real thumbnail ImageEntry
- generations: auto-name template-based generations and use that name
  for the download file and as a searchable column
- admin: show all dates as Jalali via ->jalaliDate()/->jalaliDateTime()
  Filament macros (App\Support\Jalali)
- wallets: harden the deposit/withdraw action to surface the exact error
  instead of a 500
- profile: normalize Iranian mobile numbers (App\Support\Mobile),
  add social_links, coerce notification-preference booleans, require the
  current password to delete an account

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013P66ea1dC5CVM9t29496t5
2026-09-07 01:00:09 +03:30

120 lines
3.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Support;
use Carbon\CarbonInterface;
use DateTimeInterface;
/**
* Lightweight Gregorian -> Jalali (Shamsi) converter, so the admin panel can show
* Persian dates without pulling in an extra dependency. Only the conversion and a
* couple of display formatters are implemented — enough for table columns and infolists.
*/
class Jalali
{
private const MONTHS = [
1 => 'فروردین', 2 => 'اردیبهشت', 3 => 'خرداد', 4 => 'تیر', 5 => 'مرداد', 6 => 'شهریور',
7 => 'مهر', 8 => 'آبان', 9 => 'آذر', 10 => 'دی', 11 => 'بهمن', 12 => 'اسفند',
];
/**
* @return array{0: int, 1: int, 2: int} [year, month, day]
*/
public static function fromGregorian(int $gy, int $gm, int $gd): array
{
$g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
$gy2 = ($gm > 2) ? ($gy + 1) : $gy;
$days = 355666 + (365 * $gy) + intdiv($gy2 + 3, 4) - intdiv($gy2 + 99, 100)
+ intdiv($gy2 + 399, 400) + $gd + $g_d_m[$gm - 1];
$jy = -1595 + (33 * intdiv($days, 12053));
$days %= 12053;
$jy += 4 * intdiv($days, 1461);
$days %= 1461;
if ($days > 365) {
$jy += intdiv($days - 1, 365);
$days = ($days - 1) % 365;
}
if ($days < 186) {
$jm = 1 + intdiv($days, 31);
$jd = 1 + ($days % 31);
} else {
$jm = 7 + intdiv($days - 186, 30);
$jd = 1 + (($days - 186) % 30);
}
return [$jy, $jm, $jd];
}
public static function date(DateTimeInterface|CarbonInterface|string|null $value): ?string
{
$date = self::normalize($value);
if ($date === null) {
return null;
}
[$jy, $jm, $jd] = self::fromGregorian((int) $date->format('Y'), (int) $date->format('n'), (int) $date->format('j'));
return self::toPersianDigits(sprintf('%04d/%02d/%02d', $jy, $jm, $jd));
}
public static function dateTime(DateTimeInterface|CarbonInterface|string|null $value): ?string
{
$date = self::normalize($value);
if ($date === null) {
return null;
}
[$jy, $jm, $jd] = self::fromGregorian((int) $date->format('Y'), (int) $date->format('n'), (int) $date->format('j'));
return self::toPersianDigits(sprintf(
'%04d/%02d/%02d - %02d:%02d',
$jy, $jm, $jd, (int) $date->format('H'), (int) $date->format('i')
));
}
public static function longDate(DateTimeInterface|CarbonInterface|string|null $value): ?string
{
$date = self::normalize($value);
if ($date === null) {
return null;
}
[$jy, $jm, $jd] = self::fromGregorian((int) $date->format('Y'), (int) $date->format('n'), (int) $date->format('j'));
return self::toPersianDigits($jd.' '.self::MONTHS[$jm].' '.$jy);
}
private static function normalize(DateTimeInterface|CarbonInterface|string|null $value): ?DateTimeInterface
{
if ($value === null || $value === '') {
return null;
}
if ($value instanceof DateTimeInterface) {
return $value;
}
try {
return new \DateTimeImmutable((string) $value);
} catch (\Throwable) {
return null;
}
}
private static function toPersianDigits(string $value): string
{
return strtr($value, [
'0' => '۰', '1' => '۱', '2' => '۲', '3' => '۳', '4' => '۴',
'5' => '۵', '6' => '۶', '7' => '۷', '8' => '۸', '9' => '۹',
]);
}
}