├── .gitignore ├── src └── Zaman │ ├── Facades │ └── Zaman.php │ ├── Helpers │ └── ZamanDateHelper.php │ └── IntlDatetime.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.git 3 | .idea/ 4 | /.idea 5 | composer.lock 6 | -------------------------------------------------------------------------------- /src/Zaman/Facades/Zaman.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Zaman extends Facade 13 | { 14 | const CAL_GREGORIAN = 0; 15 | const CAL_JALALI = 1; 16 | const CAL_HIJRI = 2; 17 | /** 18 | * Get the registered name of the component. 19 | * 20 | * @return string 21 | */ 22 | protected static function getFacadeAccessor () 23 | { 24 | return 'PhpMonsters\Zaman\Helpers\ZamanDateHelper'; 25 | } 26 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-monsters/laravel-jalali-date", 3 | "description": "Laravel Jalali datetime component known as ZAMAN", 4 | "keywords": [ 5 | "datetime", 6 | "laravel", 7 | "jalali", 8 | "shamsi", 9 | "hijri", 10 | "jDatetime", 11 | "Gregorian", 12 | "moment" 13 | ], 14 | "type": "library", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Aboozar Ghaffari", 19 | "email": "aboozar.ghf@gmail.com" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=8.1", 24 | "ext-ctype": "*", 25 | "ext-intl": "*", 26 | "illuminate/support": ">=8.0.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "PhpMonsters\\Zaman\\": "src/Zaman" 31 | } 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "aliases": { 36 | "Zaman": "PhpMonsters\\Zaman\\Facades\\Zaman" 37 | } 38 | } 39 | }, 40 | "minimum-stability": "stable" 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZAMAN - Jalali datetime component for Laravel: 2 | This component is based on native PHP International (php-intl) extension, 3 | So php-intl extension must be installed on your server. 4 | 5 | known as: 6 | 7 | - Hijri Shamsi 8 | - Jalali Date 9 | - JDatetime 10 | - هجری شمسی 11 | - تقویم خورشیدی 12 | - تاریخ شمسی 13 | 14 | Supprts Laravel 5+ and PHP 8.1+ 15 | 16 | ## php-intl extension installation 17 | 18 | - On windows servers, open your php.ini (which should be in Program Files/PHP), and simply uncomment the extension. 19 | ``` 20 | extension=php_intl.dll 21 | ``` 22 | 23 | - Debian based Linux (Debian/Ubuntu/Mint/ ...) 24 | ``` 25 | sudo apt-get install php-intl 26 | ``` 27 | 28 | - Redhat based Linux (Redhat/Centos/ ...) 29 | ``` 30 | sudo dnf -y install php-intl 31 | ``` 32 | Restart your webserver - done. 33 | 34 | ### Composer installation 35 | 36 | ```php 37 | composer require php-monsters/laravel-jalali-date 38 | ``` 39 | 40 | ### Integration with Laravel 5.* 41 | 42 | Add package alias to your app aliases (only for Laravel < 5.5): 43 | 44 | ```php 45 | // aliases 46 | 'Zaman' => PhpMonsters\Zaman\Facades\Zaman::class, 47 | ``` 48 | 49 | ### Usage samples 50 | 51 | ```php 52 | 53 | // Jalali to Gregorian samples 54 | echo Zaman::jTog('next week'); 55 | echo Zaman::jTog('now'); 56 | echo Zaman::jTog('1396-06-30 05:30:10'); 57 | echo Zaman::jTog ('۱۳۹۱/۱۰/۱۲ ۲۰:۳۰:۵۵', 'yyyy/MM/dd H:m:s', 'fa', 'en', 'Asia/Tehran'); 58 | 59 | // Gregorian to Jalali samples 60 | echo Zaman::gToj('2 days ago'); 61 | echo Zaman::gToj('2010-10-24 22:50:14'); 62 | echo Zaman::gToj('2014-09-21 07:12:54', 'EEEE yyyy/MMMM/dd H:m:s'); 63 | 64 | // Moment samples 65 | // JALALI moment 66 | echo Zaman::moment(strtotime('3 hours ago'), Zaman::CAL_JALALI); // "3 ساعت قبل" 67 | echo Zaman::momentJalali(strtotime('3 hours ago')); // "3 ساعت قبل" 68 | echo Zaman::moment(strtotime('2017-01-02 00:10:20'), Zaman::CAL_JALALI); // "2 هفته قبل" 69 | echo Zaman::momentJalali(strtotime('2017-01-02 00:10:20')); // "2 هفته قبل" 70 | 71 | // GREGORIAN moment 72 | echo Zaman::moment('now', Zaman::CAL_GREGORIAN); // "May 2017" 73 | echo Zaman::momentGregorian(1494328806); // "May 2017" 74 | echo Zaman::moment('1 month ago', Zaman::CAL_GREGORIAN); // "last month" 75 | echo Zaman::momentGregorian(1494334506); // "last month" 76 | 77 | // HIJRI moment 78 | // ISN'T implemented yet!!! 79 | // echo Zaman::moment('now', Zaman::CAL_HIJRI); 80 | // echo Zaman::momentHijri(1494328806); 81 | 82 | // Blade usage example 83 | {{ Zaman::gToj('2011-11-20 19:12:19') }} 84 | 85 | ``` 86 | 87 | ### Date/Time formats 88 | [Supported Formats Documentation](http://userguide.icu-project.org/formatparse/datetime) 89 | 90 | ## Team 91 | 92 | This component is developed by the following person(s) and a bunch of [awesome contributors](https://github.com/php-monsters/laravel-jalali-date/graphs/contributors). 93 | 94 | [![Aboozar Ghaffari](https://avatars1.githubusercontent.com/u/502961?s=130&v=4)](https://github.com/samuraee) | 95 | --- | 96 | [Aboozar Ghaffari](https://github.com/samuraee) | 97 | 98 | 99 | ## Support this project 100 | 101 | Please support the package by giving it :star: and contributing to its development. 102 | 103 | ## License 104 | 105 | The Laravel Jalali Datetime is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 106 | -------------------------------------------------------------------------------- /src/Zaman/Helpers/ZamanDateHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ZamanDateHelper 16 | { 17 | /** 18 | * Gregorian to Jalali 19 | * 20 | * @param string $date 21 | * @param string $format 22 | * @param string $locale 23 | * @param string|null $timezone 24 | * @return string 25 | * @throws Exception 26 | * @link http://userguide.icu-project.org/formatparse/datetime 27 | */ 28 | public function gToj( 29 | mixed $date, 30 | string $format = 'yyyy/MM/dd H:m:s', 31 | string $locale = 'fa', 32 | ?string $timezone = null 33 | ): string 34 | { 35 | $date = new IntlDatetime($date, $timezone, 'gregorian'); 36 | $date->setCalendar('persian'); 37 | $date->setLocale($locale); 38 | 39 | return $date->format($format); 40 | } 41 | 42 | /** 43 | * Jalali to Gregorian 44 | * 45 | * @param mixed $date 46 | * @param string $format 47 | * @param string $inputLocale 48 | * @param string $locale 49 | * @param string $timezone 50 | * @return string 51 | * @throws Exception 52 | * @link http://userguide.icu-project.org/formatparse/datetime 53 | */ 54 | public function jTog( 55 | string $date, 56 | string $format = 'yyyy/MM/dd H:m:s', 57 | string $inputLocale = 'fa', 58 | string $locale = 'en', 59 | string $timezone = 'Asia/Tehran' 60 | ): string 61 | { 62 | $date = new IntlDatetime($date, $timezone, 'persian', $inputLocale); 63 | 64 | $date->setCalendar('Gregorian'); 65 | $date->setLocale($locale); 66 | 67 | return $date->format($format); 68 | } 69 | 70 | /** 71 | * @param string $timestamp timestamp 72 | * @param int $calendar 73 | * @return string 74 | */ 75 | public function moment(string $timestamp, int $calendar): string 76 | { 77 | return match ($calendar) { 78 | Zaman::CAL_JALALI => $this->momentJalali($timestamp), 79 | Zaman::CAL_HIJRI => $this->momentHijri($timestamp), 80 | default => $this->momentGregorian($timestamp), 81 | }; 82 | } 83 | 84 | /** 85 | * @param string $timestamp timestamp 86 | * 87 | * @return string 88 | */ 89 | public function momentJalali(string $timestamp): string 90 | { 91 | if (!ctype_digit($timestamp)) { 92 | $timestamp = strtotime($timestamp); 93 | } 94 | $diff = time() - $timestamp; 95 | if ($diff === 0) { 96 | return 'اکنون'; 97 | } 98 | 99 | $dayDiff = (int)floor($diff / 86400); 100 | 101 | if ($diff > 0) { 102 | if ($dayDiff === 0) { 103 | if ($diff < 60) { 104 | return 'اکنون'; 105 | } 106 | if ($diff < 120) { 107 | return 'یک دقیقه قبل'; 108 | } 109 | if ($diff < 3600) { 110 | return floor($diff / 60) . ' دقیقه قبل'; 111 | } 112 | if ($diff < 7200) { 113 | return 'یک ساعت پیش'; 114 | } 115 | if ($diff < 86400) { 116 | return floor($diff / 3600) . ' ساعت قبل'; 117 | } 118 | } 119 | if ($dayDiff === 1) { 120 | return 'دیروز'; 121 | } 122 | if ($dayDiff < 7) { 123 | return $dayDiff . ' روز قبل'; 124 | } 125 | if ($dayDiff < 31) { 126 | return ceil($dayDiff / 7) . ' هفته قبل'; 127 | } 128 | if ($dayDiff < 60) { 129 | return 'ماه گذشته'; 130 | } 131 | 132 | return date('F Y', $timestamp); 133 | } 134 | 135 | $diff = abs($diff); 136 | if ($dayDiff === 0) { 137 | if ($diff < 120) { 138 | return 'یک دقیقه پیش'; 139 | } 140 | if ($diff < 3600) { 141 | return floor($diff / 60) . ' دقیقه پیش'; 142 | } 143 | if ($diff < 7200) { 144 | return 'یک ساعت پیش'; 145 | } 146 | if ($diff < 86400) { 147 | return floor($diff / 3600) . ' ساعت پیش'; 148 | } 149 | } 150 | if ($dayDiff === 1) { 151 | return 'فردا'; 152 | } 153 | if ($dayDiff < 4) { 154 | return date('l', $timestamp); 155 | } 156 | if ($dayDiff < 7 + (7 - date('w'))) { 157 | return 'هفته بعد'; 158 | } 159 | if (ceil($dayDiff / 7) < 4) { 160 | return 'در ' . ceil($dayDiff / 7) . ' هفته'; 161 | } 162 | if ((int)date('n', $timestamp) === (int)date('n') + 1) { 163 | return 'ماه بعد'; 164 | } 165 | 166 | return date('F Y', $timestamp); 167 | } 168 | 169 | /** 170 | * @param string $timestamp timestamp 171 | * 172 | * @return string 173 | */ 174 | public function momentGregorian(string $timestamp): string 175 | { 176 | if (!ctype_digit($timestamp)) { 177 | $timestamp = strtotime($timestamp); 178 | } 179 | $diff = time() - $timestamp; 180 | if ($diff === 0) { 181 | return 'now'; 182 | } 183 | 184 | $dayDiff = (int)floor($diff / 86400); 185 | 186 | if ($diff > 0) { 187 | if ($dayDiff === 0) { 188 | if ($diff < 60) { 189 | return 'just now'; 190 | } 191 | if ($diff < 120) { 192 | return '1 minute ago'; 193 | } 194 | if ($diff < 3600) { 195 | return floor($diff / 60) . ' minutes ago'; 196 | } 197 | if ($diff < 7200) { 198 | return '1 hour ago'; 199 | } 200 | if ($diff < 86400) { 201 | return floor($diff / 3600) . ' hours ago'; 202 | } 203 | } 204 | if ($dayDiff === 1) { 205 | return 'Yesterday'; 206 | } 207 | if ($dayDiff < 7) { 208 | return $dayDiff . ' days ago'; 209 | } 210 | if ($dayDiff < 31) { 211 | return ceil($dayDiff / 7) . ' weeks ago'; 212 | } 213 | if ($dayDiff < 60) { 214 | return 'last month'; 215 | } 216 | 217 | return date('F Y', $timestamp); 218 | } 219 | 220 | $diff = abs($diff); 221 | if ($dayDiff === 0) { 222 | if ($diff < 120) { 223 | return 'in a minute'; 224 | } 225 | if ($diff < 3600) { 226 | return 'in ' . floor($diff / 60) . ' minutes'; 227 | } 228 | if ($diff < 7200) { 229 | return 'in an hour'; 230 | } 231 | if ($diff < 86400) { 232 | return 'in ' . floor($diff / 3600) . ' hours'; 233 | } 234 | } 235 | if ($dayDiff === 1) { 236 | return 'Tomorrow'; 237 | } 238 | if ($dayDiff < 4) { 239 | return date('l', $timestamp); 240 | } 241 | if ($dayDiff < 7 + (7 - date('w'))) { 242 | return 'next week'; 243 | } 244 | if (ceil($dayDiff / 7) < 4) { 245 | return 'in ' . ceil($dayDiff / 7) . ' weeks'; 246 | } 247 | if ((int)date('n', $timestamp) === (int)date('n') + 1) { 248 | return 'next month'; 249 | } 250 | 251 | return date('F Y', $timestamp); 252 | } 253 | 254 | /** 255 | * @param string $timestamp timestamp 256 | * 257 | * @return string 258 | */ 259 | private function momentHijri(string $timestamp): string 260 | { 261 | throw new RuntimeException('Hijri moment has not implemented yet!'); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/Zaman/IntlDatetime.php: -------------------------------------------------------------------------------- 1 | = 5.3.0 with php-intl extension) 6 | * However, this class is not compatible with DateTime class because it uses ICU 7 | * pattern syntax for formatting and parsing date strings. 8 | * (@link http://userguide.icu-project.org/formatparse/datetime) 9 | * 10 | * @copyright Copyright 2010, PhpMonsters (https://github.com/samuraee/) 11 | * @license GNU General Public License 3.0 (http://www.gnu.org/licenses/gpl.html) 12 | * @package PhpMonsters\Zaman 13 | * @author Aboozar Ghaffari 14 | */ 15 | 16 | namespace PhpMonsters\Zaman; 17 | 18 | use DateTime; 19 | use DateTimeZone; 20 | use Exception; 21 | use IntlDateFormatter; 22 | use NumberFormatter; 23 | use ReturnTypeWillChange; 24 | 25 | class IntlDatetime extends DateTime 26 | { 27 | 28 | /** 29 | * @var string The current locale in use 30 | */ 31 | protected string $locale; 32 | 33 | /** 34 | * @var string The current calendar in use 35 | */ 36 | protected string $calendar; 37 | 38 | /** 39 | * Creates a new instance of IntlDateTime 40 | * 41 | * @param mixed $time Unix timestamp or strtotime() compatible string or another DateTime object 42 | * @param mixed $timezone DateTimeZone object or timezone identifier as full name (e.g. Asia/Tehran) or abbreviation (e.g. IRDT). 43 | * @param string $calendar any calendar supported by ICU (e.g. gregorian, persian, islamic, ...) 44 | * @param string $locale any locale supported by ICU 45 | * @param string|null $pattern the date pattern in which $time is formatted. 46 | * 47 | * @return void 48 | * @throws Exception 49 | */ 50 | public function __construct( 51 | $time = 'now', 52 | $timezone = null, 53 | string $calendar = 'gregorian', 54 | string $locale = 'en_US', 55 | ?string $pattern = null 56 | ) 57 | { 58 | if (!isset($timezone)) { 59 | $timezone = new DateTimeZone(date_default_timezone_get()); 60 | } elseif (!$timezone instanceof DateTimeZone) { 61 | $timezone = new DateTimeZone($timezone); 62 | } 63 | 64 | parent::__construct($time, $timezone); 65 | $this->setLocale($locale); 66 | $this->setCalendar($calendar); 67 | if (isset($time)) { 68 | $this->set($time, $timezone, $pattern); 69 | } 70 | } 71 | 72 | /** 73 | * Alters object's internal timestamp with a string acceptable by strtotime() or a Unix timestamp or a DateTime object. 74 | * 75 | * @param mixed $time Unix timestamp or strtotime() compatible string or another DateTime object 76 | * @param mixed|null $timezone DateTimeZone object or timezone identifier as full name (e.g. Asia/Tehran) or abbreviation (e.g. IRDT). 77 | * @param string|null $pattern the date pattern in which $time is formatted. 78 | * 79 | * @return IntlDateTime The modified DateTime. 80 | * @throws Exception 81 | */ 82 | public function set(mixed $time, mixed $timezone = null, ?string $pattern = null): static 83 | { 84 | if ($time instanceof DateTime) { 85 | $time = $time->format('U'); 86 | } elseif (!is_numeric($time) || $pattern) { 87 | if (!$pattern) { 88 | $pattern = $this->guessPattern($time); 89 | } 90 | 91 | if (!$pattern && preg_match('/((?:[+-]?\d+)|next|last|previous)\s*(year|month)s?/i', $time)) { 92 | if (isset($timezone)) { 93 | $tempTimezone = $this->getTimezone(); 94 | $this->setTimezone($timezone); 95 | } 96 | 97 | $this->setTimestamp(time()); 98 | $this->modify($time); 99 | 100 | if (isset($timezone)) { 101 | $this->setTimezone($tempTimezone); 102 | } 103 | 104 | return $this; 105 | } 106 | 107 | $timezone = empty($timezone) ? $this->getTimezone() : $timezone; 108 | if ($timezone instanceof DateTimeZone) { 109 | $timezone = $timezone->getName(); 110 | } 111 | $defaultTimezone = @date_default_timezone_get(); 112 | date_default_timezone_set($timezone); 113 | 114 | if ($pattern) { 115 | $time = $this->getFormatter(array('timezone' => 'GMT', 'pattern' => $pattern))->parse($time); 116 | $time -= date('Z', $time); 117 | } else { 118 | $time = strtotime($time); 119 | } 120 | 121 | date_default_timezone_set($defaultTimezone); 122 | } 123 | 124 | $this->setTimestamp($time); 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Returns date formatted according to given pattern. 131 | * 132 | * @param string $format Date pattern in ICU syntax (@link http://userguide.icu-project.org/formatparse/datetime) 133 | * @param mixed|null $timezone DateTimeZone object or timezone identifier as full name (e.g. Asia/Tehran) or abbreviation (e.g. IRDT). 134 | * 135 | * @return string Formatted date on success or FALSE on failure. 136 | * @throws Exception 137 | */ 138 | #[ReturnTypeWillChange] public function format(string $format, mixed $timezone = null): string 139 | { 140 | if (isset($timezone)) { 141 | $tempTimezone = $this->getTimezone(); 142 | $this->setTimezone($timezone); 143 | } 144 | 145 | // Timezones DST data in ICU are not as accurate as PHP. 146 | // So we get timezone offset from php and pass it to ICU. 147 | $result = $this->getFormatter(array( 148 | 'timezone' => 'GMT' . (parent::format('Z') ? parent::format('P') : ''), 149 | 'pattern' => $format 150 | ))->format($this->getTimestamp()); 151 | 152 | if (isset($timezone)) { 153 | $this->setTimezone($tempTimezone); 154 | } 155 | 156 | return $result; 157 | } 158 | 159 | /** 160 | * Sets the timezone for the object. 161 | * 162 | * @param mixed $timezone DateTimeZone object or timezone identifier as full name (e.g. Asia/Tehran) or abbreviation (e.g. IRDT). 163 | * 164 | * @return IntlDateTime The modified DateTime. 165 | * @throws Exception 166 | */ 167 | #[ReturnTypeWillChange] public function setTimezone($timezone): static 168 | { 169 | if (!$timezone instanceof DateTimeZone) { 170 | $timezone = new DateTimeZone($timezone); 171 | } 172 | parent::setTimezone($timezone); 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Returns an instance of IntlDateFormatter with specified options. 179 | * 180 | * @param array $options 181 | * 182 | * @return IntlDateFormatter 183 | */ 184 | protected function getFormatter(array $options = []): IntlDateFormatter 185 | { 186 | $locale = empty($options['locale']) ? $this->locale : $options['locale']; 187 | $calendar = empty($options['calendar']) ? $this->calendar : $options['calendar']; 188 | $timezone = empty($options['timezone']) ? $this->getTimezone() : $options['timezone']; 189 | if ($timezone instanceof DateTimeZone) { 190 | $timezone = $timezone->getName(); 191 | } 192 | $pattern = empty($options['pattern']) ? null : $options['pattern']; 193 | 194 | return new IntlDateFormatter($locale . '@calendar=' . $calendar, 195 | IntlDateFormatter::FULL, IntlDateFormatter::FULL, $timezone, 196 | $calendar === 'gregorian' ? IntlDateFormatter::GREGORIAN : IntlDateFormatter::TRADITIONAL, $pattern); 197 | } 198 | 199 | /** 200 | * Overrides the getTimestamp method to support timestamps out of the integer range. 201 | * 202 | * @return float Unix timestamp representing the date. 203 | */ 204 | #[ReturnTypeWillChange] public function getTimestamp(): float 205 | { 206 | return floatval(parent::format('U')); 207 | } 208 | 209 | /** 210 | * Tries to guess the date pattern in which $time is formatted. 211 | * 212 | * @param string $time The date string 213 | * 214 | * @return bool|string Detected ICU pattern on success, FALSE otherwise. 215 | */ 216 | protected function guessPattern(string $time): bool|string 217 | { 218 | $time = $this->latinizeDigits(trim($time)); 219 | 220 | $shortDateRegex = '(\d{2,4})(-|\\\\|/)\d{1,2}\2\d{1,2}'; 221 | $longDateRegex = '([^\d]*\s)?\d{1,2}(-| )[^-\s\d]+\4(\d{2,4})'; 222 | $timeRegex = '\d{1,2}:\d{1,2}(:\d{1,2})?(\s.*)?'; 223 | 224 | if (preg_match("@^(?:(?:$shortDateRegex)|(?:$longDateRegex))(\s+$timeRegex)?$@", $time, $match)) { 225 | if (!empty($match[1])) { 226 | $separator = $match[2]; 227 | $pattern = strlen($match[1]) === 2 ? 'yy' : 'yyyy'; 228 | $pattern .= $separator . 'MM' . $separator . 'dd'; 229 | } else { 230 | $separator = $match[4]; 231 | $pattern = 'dd' . $separator . 'LLL' . $separator; 232 | $pattern .= strlen($match[5]) === 2 ? 'yy' : 'yyyy'; 233 | if (!empty($match[3])) { 234 | $pattern = (preg_match('/,\s+$/', $match[3]) ? 'E, ' : 'E ') . $pattern; 235 | } 236 | } 237 | if (!empty($match[6])) { 238 | $pattern .= !empty($match[8]) ? ' hh:mm' : ' HH:mm'; 239 | if (!empty($match[7])) { 240 | $pattern .= ':ss'; 241 | } 242 | if (!empty($match[8])) { 243 | $pattern .= ' a'; 244 | } 245 | } 246 | 247 | return $pattern; 248 | } 249 | 250 | return false; 251 | } 252 | 253 | /** 254 | * Replaces localized digits in $str with latin digits. 255 | * 256 | * @param string $str 257 | * 258 | * @return string Platinized string 259 | */ 260 | protected function latinizeDigits(string $str): string 261 | { 262 | $result = ''; 263 | $num = new NumberFormatter($this->locale, NumberFormatter::DECIMAL); 264 | preg_match_all('/.[\x80-\xBF]*/', $str, $matches); 265 | foreach ($matches[0] as $char) { 266 | $pos = 0; 267 | $parsedChar = $num->parse($char, NumberFormatter::TYPE_INT32, $pos); 268 | $result .= $pos ? $parsedChar : $char; 269 | } 270 | 271 | return $result; 272 | } 273 | 274 | /** 275 | * Overrides the setTimestamp method to support timestamps out of the integer range. 276 | * 277 | * @param float $timestamp Unix timestamp representing the date. 278 | * 279 | * @return IntlDateTime the modified DateTime. 280 | * @throws Exception 281 | */ 282 | #[ReturnTypeWillChange] public function setTimestamp($timestamp): static 283 | { 284 | $diff = $timestamp - $this->getTimestamp(); 285 | $days = floor($diff / 86400); 286 | $seconds = $diff - $days * 86400; 287 | $timezone = $this->getTimezone(); 288 | $this->setTimezone('UTC'); 289 | parent::modify("$days days $seconds seconds"); 290 | $this->setTimezone($timezone); 291 | 292 | return $this; 293 | } 294 | 295 | /** 296 | * Alter the timestamp by incrementing or decrementing in a format accepted by strtotime(). 297 | * 298 | * @param string $modifier a string in a relative format accepted by strtotime(). 299 | * 300 | * @return IntlDateTime The modified DateTime. 301 | */ 302 | #[ReturnTypeWillChange] public function modify(string $modifier): static 303 | { 304 | $modifier = $this->latinizeDigits(trim($modifier)); 305 | $modifier = preg_replace_callback('/(.*?)((?:[+-]?\d+)|next|last|previous)\s*(year|month)s?/i', 306 | array($this, 'modifyCallback'), $modifier); 307 | if ($modifier) { 308 | parent::modify($modifier); 309 | } 310 | 311 | return $this; 312 | } 313 | 314 | /** 315 | * Gets the current locale used by the object. 316 | * 317 | * @return string 318 | */ 319 | public function getLocale(): string 320 | { 321 | return $this->locale; 322 | } 323 | 324 | /** 325 | * Sets the locale used by the object. 326 | * 327 | * @param string $locale 328 | * 329 | * @return IntlDateTime The modified DateTime. 330 | */ 331 | public function setLocale(string $locale): static 332 | { 333 | $this->locale = $locale; 334 | 335 | return $this; 336 | } 337 | 338 | /** 339 | * Gets the current calendar used by the object. 340 | * 341 | * @return string 342 | */ 343 | public function getCalendar(): string 344 | { 345 | return $this->calendar; 346 | } 347 | 348 | /** 349 | * Sets the calendar used by the object. 350 | * 351 | * @param string $calendar 352 | * 353 | * @return IntlDateTime The modified DateTime. 354 | */ 355 | public function setCalendar(string $calendar): static 356 | { 357 | $this->calendar = strtolower($calendar); 358 | 359 | return $this; 360 | } 361 | 362 | /** 363 | * Preserve original DateTime::format functionality 364 | * 365 | * @param string $format Format accepted by date(). 366 | * @param mixed|null $timezone DateTimeZone object or timezone identifier as full name (e.g. Asia/Tehran) or abbreviation (e.g. IRDT). 367 | * 368 | * @return string Formatted date on success or FALSE on failure. 369 | * @throws Exception 370 | */ 371 | public function classicFormat(string $format, mixed $timezone = null): string 372 | { 373 | if ($timezone) { 374 | $tempTimezone = $this->getTimezone(); 375 | $this->setTimezone($timezone); 376 | } 377 | 378 | $result = parent::format($format); 379 | 380 | if ($timezone) { 381 | $this->setTimezone($tempTimezone); 382 | } 383 | 384 | return $result; 385 | } 386 | 387 | /** 388 | * echo object as string 389 | * 390 | * @throws Exception 391 | */ 392 | public function __toString() 393 | { 394 | return $this->format('yyyy/MM/dd H:m:s'); 395 | } 396 | 397 | /** 398 | * Internally used by modify method to calculate calendar-aware modifications 399 | * 400 | * @param array $matches 401 | * 402 | * @return string An empty string 403 | * @throws Exception 404 | */ 405 | protected function modifyCallback(array $matches): string 406 | { 407 | if (!empty($matches[1])) { 408 | parent::modify($matches[1]); 409 | } 410 | 411 | [$y, $m, $d] = explode('-', $this->format('y-M-d')); 412 | $change = strtolower($matches[2]); 413 | $unit = strtolower($matches[3]); 414 | 415 | switch ($change) { 416 | case "next": 417 | $change = 1; 418 | break; 419 | 420 | case "last": 421 | case "previous": 422 | $change = -1; 423 | break; 424 | } 425 | 426 | switch ($unit) { 427 | case "month": 428 | $m += $change; 429 | if ($m > 12) { 430 | $y += floor($m / 12); 431 | $m %= 12; 432 | } elseif ($m < 1) { 433 | $y += ceil($m / 12) - 1; 434 | $m = $m % 12 + 12; 435 | } 436 | break; 437 | 438 | case "year": 439 | $y += $change; 440 | break; 441 | } 442 | 443 | $this->setDate($y, $m, $d); 444 | 445 | return ''; 446 | } 447 | 448 | /** 449 | * Resets the current date of the object. 450 | * 451 | * @param integer $year 452 | * @param integer $month 453 | * @param integer $day 454 | * 455 | * @return IntlDateTime The modified DateTime. 456 | * @throws Exception 457 | */ 458 | #[ReturnTypeWillChange] public function setDate(int $year, int $month, int $day): static 459 | { 460 | $this->set("$year/$month/$day " . $this->format('HH:mm:ss'), null, 'yyyy/MM/dd HH:mm:ss'); 461 | 462 | return $this; 463 | } 464 | } 465 | --------------------------------------------------------------------------------