1<?php 2 3 4namespace ComboStrap; 5 6 7use DateTime; 8use IntlDateFormatter; 9 10 11/** 12 * Class Is8601Date 13 * @package ComboStrap 14 * Format used by Google, Sqlite and others 15 * 16 * This is the date class of Combostrap 17 * that takes a valid input string 18 * and output an iso string 19 */ 20class Iso8601Date 21{ 22 public const CANONICAL = "date"; 23 public const TIME_FORMATTER_TYPE = IntlDateFormatter::NONE; 24 public const DATE_FORMATTER_TYPE = IntlDateFormatter::TRADITIONAL; 25 /** 26 * @var DateTime|false 27 */ 28 private $dateTime; 29 30 /** 31 * ATOM = IS08601 32 * See {@link Iso8601Date::getFormat()} for more information 33 */ 34 private const VALID_FORMATS = [ 35 \DateTimeInterface::ATOM, 36 'Y-m-d H:i:sP', 37 'Y-m-d H:i:s', 38 'Y-m-d H:i', 39 'Y-m-d H', 40 'Y-m-d', 41 ]; 42 43 44 /** 45 * Date constructor. 46 */ 47 public function __construct($dateTime = null) 48 { 49 50 if ($dateTime == null) { 51 52 $this->dateTime = new DateTime(); 53 54 } else { 55 56 $this->dateTime = $dateTime; 57 58 } 59 60 } 61 62 /** 63 * @param $dateString 64 * @return Iso8601Date 65 * @throws ExceptionBadSyntax if the format is not supported 66 */ 67 public static function createFromString(string $dateString): Iso8601Date 68 { 69 70 $original = $dateString; 71 72 $dateString = trim($dateString); 73 74 /** 75 * Time ? 76 * (ie only YYYY-MM-DD) 77 */ 78 if (strlen($dateString) <= 10) { 79 /** 80 * We had the time to 00:00:00 81 * because {@link DateTime::createFromFormat} with a format of 82 * Y-m-d will be using the actual time otherwise 83 * 84 */ 85 $dateString .= "T00:00:00"; 86 } 87 88 /** 89 * Space as T 90 */ 91 $dateString = str_replace(" ", "T", $dateString); 92 93 94 if (strlen($dateString) <= 13) { 95 /** 96 * We had the time to 00:00:00 97 * because {@link DateTime::createFromFormat} with a format of 98 * Y-m-d will be using the actual time otherwise 99 * 100 */ 101 $dateString .= ":00:00"; 102 } 103 104 if (strlen($dateString) <= 16) { 105 /** 106 * We had the time to 00:00:00 107 * because {@link DateTime::createFromFormat} with a format of 108 * Y-m-d will be using the actual time otherwise 109 * 110 */ 111 $dateString .= ":00"; 112 } 113 114 /** 115 * Timezone 116 */ 117 if (strlen($dateString) <= 19) { 118 /** 119 * Because this text metadata may be used in other part of the application 120 * We add the timezone to make it whole 121 * And to have a consistent value 122 */ 123 $dateString .= date('P'); 124 } 125 126 127 $dateTime = DateTime::createFromFormat(self::getFormat(), $dateString); 128 if ($dateTime === false) { 129 $message = "The date string ($original) is not in a valid date format. (" . join(", ", self::VALID_FORMATS) . ")"; 130 throw new ExceptionBadSyntax($message, self::CANONICAL); 131 } 132 return new Iso8601Date($dateTime); 133 134 } 135 136 public static function createFromTimestamp($timestamp): Iso8601Date 137 { 138 $dateTime = new DateTime(); 139 $dateTime->setTimestamp($timestamp); 140 return new Iso8601Date($dateTime); 141 } 142 143 /** 144 * And note {@link DATE_ISO8601} 145 * because it's not the compliant IS0-8601 format 146 * as explained here 147 * https://www.php.net/manual/en/class.datetimeinterface.php#datetime.constants.iso8601 148 * ATOM is 149 * 150 * This format is used by Sqlite, Google and is pretty the standard everywhere 151 * https://www.w3.org/TR/NOTE-datetime 152 */ 153 public static function getFormat(): string 154 { 155 return DATE_ATOM; 156 } 157 158 /** 159 * 160 */ 161 public static function isValid($value): bool 162 { 163 try { 164 $dateObject = Iso8601Date::createFromString($value); 165 return $dateObject->isValidDateEntry(); // ??? Validation seems to be at construction 166 } catch (ExceptionBadSyntax $e) { 167 return false; 168 } 169 } 170 171 public function isValidDateEntry(): bool 172 { 173 if ($this->dateTime !== false) { 174 return true; 175 } else { 176 return false; 177 } 178 } 179 180 public static function createFromDateTime(DateTime $dateTime): Iso8601Date 181 { 182 return new Iso8601Date($dateTime); 183 } 184 185 public static function createFromNow(): Iso8601Date 186 { 187 return new Iso8601Date(); 188 } 189 190 /** 191 * @throws ExceptionNotFound 192 */ 193 public static function getInternationalFormatter($constant): int 194 { 195 $constantNormalized = trim(strtolower($constant)); 196 switch ($constantNormalized) { 197 case "none": 198 return IntlDateFormatter::NONE; 199 case "full": 200 return IntlDateFormatter::FULL; 201 case "relativefull": 202 return IntlDateFormatter::RELATIVE_FULL; 203 case "long": 204 return IntlDateFormatter::LONG; 205 case "relativelong": 206 return IntlDateFormatter::RELATIVE_LONG; 207 case "medium": 208 return IntlDateFormatter::MEDIUM; 209 case "relativemedium": 210 return IntlDateFormatter::RELATIVE_MEDIUM; 211 case "short": 212 return IntlDateFormatter::SHORT; 213 case "relativeshort": 214 return IntlDateFormatter::RELATIVE_SHORT; 215 case "traditional": 216 return IntlDateFormatter::TRADITIONAL; 217 default: 218 throw new ExceptionNotFound("The constant ($constant) is not a valid constant", self::CANONICAL); 219 } 220 } 221 222 public function getDateTime() 223 { 224 return $this->dateTime; 225 } 226 227 public function __toString() 228 { 229 return $this->getDateTime()->format(self::getFormat()); 230 } 231 232 public function toIsoStringMs() 233 { 234 return $this->getDateTime()->format("Y-m-d\TH:i:s.u"); 235 } 236 237 /** 238 * Shortcut to {@link DateTime::format()} 239 * Format only in English 240 * @param $string 241 * @return string 242 * @link https://php.net/manual/en/datetime.format.php 243 */ 244 public function format($string): string 245 { 246 return $this->getDateTime()->format($string); 247 } 248 249 public function toString() 250 { 251 return $this->__toString(); 252 } 253 254 /** 255 * @throws ExceptionBadSyntax 256 */ 257 public function formatLocale($pattern = null, $locale = null) 258 { 259 260 /** 261 * https://www.php.net/manual/en/function.strftime.php 262 * As been deprecated 263 * The only alternative with local is 264 * https://www.php.net/manual/en/intldateformatter.format.php 265 * 266 * Based on ISO date 267 * ICU Date formatter: https://unicode-org.github.io/icu-docs/#/icu4c/udat_8h.html 268 * ICU Date formats: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax 269 * ICU User Guide: https://unicode-org.github.io/icu/userguide/ 270 * ICU Formatting Dates and Times: https://unicode-org.github.io/icu/userguide/format_parse/datetime/ 271 */ 272 if (strpos($pattern, "%") !== false) { 273 LogUtility::warning("The date format ($pattern) is no more supported. Why ? Because Php has deprecated <a href=\"https://www.php.net/manual/en/function.strftime.php\">strftime</a>. You need to use the <a href=\"https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax\">Unicode Date Time format</a>", self::CANONICAL); 274 return strftime($pattern, $this->dateTime->getTimestamp()); 275 } 276 277 /** 278 * This parameters 279 * are used to format date with the locale 280 * when the pattern is null 281 * Doc: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#producing-normal-date-formats-for-a-locale 282 * 283 * They may be null by the way. 284 * 285 */ 286 $dateType = self::DATE_FORMATTER_TYPE; 287 $timeType = self::TIME_FORMATTER_TYPE; 288 if ($pattern !== null) { 289 $normalFormat = explode(" ", $pattern); 290 if (sizeof($normalFormat) === 2) { 291 try { 292 $dateType = self::getInternationalFormatter($normalFormat[0]); 293 $timeType = self::getInternationalFormatter($normalFormat[1]); 294 $pattern = null; 295 } catch (ExceptionNotFound $e) { 296 // ok 297 } 298 } 299 } 300 301 /** 302 * Formatter instantiation 303 */ 304 $formatter = datefmt_create( 305 $locale, 306 $dateType, 307 $timeType, 308 $this->dateTime->getTimezone(), 309 IntlDateFormatter::GREGORIAN, 310 $pattern 311 ); 312 $formatted = datefmt_format($formatter, $this->dateTime); 313 if ($formatted === false) { 314 if ($locale === null) { 315 $locale = ""; 316 } 317 if ($pattern === null) { 318 $pattern = ""; 319 } 320 throw new ExceptionBadSyntax("Unable to format the date with the pattern ($pattern) and locale ($locale)"); 321 } 322 return $formatted; 323 } 324 325 public function olderThan(DateTime $rightTime): bool 326 { 327 328 $internalMs = DataType::toMilliSeconds($this->dateTime); 329 $externalMilliSeconds = DataType::toMilliSeconds($rightTime); 330 if ($externalMilliSeconds > $internalMs) { 331 return true; 332 } 333 return false; 334 335 } 336 337 public function diff(DateTime $rightTime): \DateInterval 338 { 339 // example get the s part of the diff (even if there is day of diff) 340 // $seconds = $diff->format('%s'); 341 return $this->dateTime->diff($rightTime, true); 342 } 343 344 345} 346