friendship ended with social-app. php is my new best friend
at main 5.2 kB view raw
1<?php 2 3/** 4 * This file is part of the Nette Framework (https://nette.org) 5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com) 6 */ 7 8declare(strict_types=1); 9 10namespace Nette\Utils; 11 12use Nette; 13use function array_merge, checkdate, implode, is_numeric, is_string, preg_replace_callback, sprintf, time, trim; 14 15 16/** 17 * DateTime. 18 */ 19class DateTime extends \DateTime implements \JsonSerializable 20{ 21 use Nette\SmartObject; 22 23 /** minute in seconds */ 24 public const MINUTE = 60; 25 26 /** hour in seconds */ 27 public const HOUR = 60 * self::MINUTE; 28 29 /** day in seconds */ 30 public const DAY = 24 * self::HOUR; 31 32 /** week in seconds */ 33 public const WEEK = 7 * self::DAY; 34 35 /** average month in seconds */ 36 public const MONTH = 2_629_800; 37 38 /** average year in seconds */ 39 public const YEAR = 31_557_600; 40 41 42 /** 43 * Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object. 44 * @throws \Exception if the date and time are not valid. 45 */ 46 public static function from(string|int|\DateTimeInterface|null $time): static 47 { 48 if ($time instanceof \DateTimeInterface) { 49 return static::createFromInterface($time); 50 51 } elseif (is_numeric($time)) { 52 if ($time <= self::YEAR) { 53 $time += time(); 54 } 55 56 return (new static)->setTimestamp((int) $time); 57 58 } else { // textual or null 59 return new static((string) $time); 60 } 61 } 62 63 64 /** 65 * Creates DateTime object. 66 * @throws Nette\InvalidArgumentException if the date and time are not valid. 67 */ 68 public static function fromParts( 69 int $year, 70 int $month, 71 int $day, 72 int $hour = 0, 73 int $minute = 0, 74 float $second = 0.0, 75 ): static 76 { 77 $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); 78 if ( 79 !checkdate($month, $day, $year) 80 || $hour < 0 || $hour > 23 81 || $minute < 0 || $minute > 59 82 || $second < 0 || $second >= 60 83 ) { 84 throw new Nette\InvalidArgumentException("Invalid date '$s'"); 85 } 86 87 return new static($s); 88 } 89 90 91 /** 92 * Returns a new DateTime object formatted according to the specified format. 93 */ 94 public static function createFromFormat( 95 string $format, 96 string $datetime, 97 string|\DateTimeZone|null $timezone = null, 98 ): static|false 99 { 100 if (is_string($timezone)) { 101 $timezone = new \DateTimeZone($timezone); 102 } 103 104 $date = parent::createFromFormat($format, $datetime, $timezone); 105 return $date ? static::from($date) : false; 106 } 107 108 109 public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null) 110 { 111 $this->apply($datetime, $timezone, true); 112 } 113 114 115 public function modify(string $modifier): static 116 { 117 $this->apply($modifier); 118 return $this; 119 } 120 121 122 public function setDate(int $year, int $month, int $day): static 123 { 124 if (!checkdate($month, $day, $year)) { 125 trigger_error(sprintf(self::class . ': The date %04d-%02d-%02d is not valid.', $year, $month, $day), E_USER_WARNING); 126 } 127 return parent::setDate($year, $month, $day); 128 } 129 130 131 public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static 132 { 133 if ( 134 $hour < 0 || $hour > 23 135 || $minute < 0 || $minute > 59 136 || $second < 0 || $second >= 60 137 || $microsecond < 0 || $microsecond >= 1_000_000 138 ) { 139 trigger_error(sprintf(self::class . ': The time %02d:%02d:%08.5F is not valid.', $hour, $minute, $second + $microsecond / 1_000_000), E_USER_WARNING); 140 } 141 return parent::setTime($hour, $minute, $second, $microsecond); 142 } 143 144 145 /** 146 * Converts a relative time string (e.g. '10 minut') to seconds. 147 */ 148 public static function relativeToSeconds(string $relativeTime): int 149 { 150 return (new self('@0 ' . $relativeTime)) 151 ->getTimestamp(); 152 } 153 154 155 private function apply(string $datetime, $timezone = null, bool $ctr = false): void 156 { 157 $relPart = ''; 158 $absPart = preg_replace_callback( 159 '/[+-]?\s*\d+\s+((microsecond|millisecond|[mµu]sec)s?|[mµ]s|sec(ond)?s?|min(ute)?s?|hours?)(\s+ago)?\b/iu', 160 function ($m) use (&$relPart) { 161 $relPart .= $m[0] . ' '; 162 return ''; 163 }, 164 $datetime, 165 ); 166 167 if ($ctr) { 168 parent::__construct($absPart, $timezone); 169 $this->handleErrors($datetime); 170 } elseif (trim($absPart)) { 171 parent::modify($absPart) && $this->handleErrors($datetime); 172 } 173 174 if ($relPart) { 175 $timezone ??= $this->getTimezone(); 176 $this->setTimezone(new \DateTimeZone('UTC')); 177 parent::modify($relPart) && $this->handleErrors($datetime); 178 $this->setTimezone($timezone); 179 } 180 } 181 182 183 /** 184 * Returns JSON representation in ISO 8601 (used by JavaScript). 185 */ 186 public function jsonSerialize(): string 187 { 188 return $this->format('c'); 189 } 190 191 192 /** 193 * Returns the date and time in the format 'Y-m-d H:i:s'. 194 */ 195 public function __toString(): string 196 { 197 return $this->format('Y-m-d H:i:s'); 198 } 199 200 201 /** 202 * You'd better use: (clone $dt)->modify(...) 203 */ 204 public function modifyClone(string $modify = ''): static 205 { 206 $dolly = clone $this; 207 return $modify ? $dolly->modify($modify) : $dolly; 208 } 209 210 211 private function handleErrors(string $value): void 212 { 213 $errors = self::getLastErrors(); 214 $errors = array_merge($errors['errors'] ?? [], $errors['warnings'] ?? []); 215 if ($errors) { 216 trigger_error(self::class . ': ' . implode(', ', $errors) . " '$value'", E_USER_WARNING); 217 } 218 } 219}