137748cd8SNickeau<?php 237748cd8SNickeau 337748cd8SNickeau 437748cd8SNickeaunamespace ComboStrap; 537748cd8SNickeau 637748cd8SNickeau 737748cd8SNickeauuse DateTime; 8*04fd306cSNickeauuse IntlDateFormatter; 937748cd8SNickeau 1037748cd8SNickeau 1137748cd8SNickeau/** 1237748cd8SNickeau * Class Is8601Date 1337748cd8SNickeau * @package ComboStrap 1437748cd8SNickeau * Format used by Google, Sqlite and others 151fa8c418SNickeau * 161fa8c418SNickeau * This is the date class of Combostrap 171fa8c418SNickeau * that takes a valid input string 181fa8c418SNickeau * and output an iso string 1937748cd8SNickeau */ 2037748cd8SNickeauclass Iso8601Date 2137748cd8SNickeau{ 221fa8c418SNickeau public const CANONICAL = "date"; 23*04fd306cSNickeau public const TIME_FORMATTER_TYPE = IntlDateFormatter::NONE; 24*04fd306cSNickeau public const DATE_FORMATTER_TYPE = IntlDateFormatter::TRADITIONAL; 2537748cd8SNickeau /** 2637748cd8SNickeau * @var DateTime|false 2737748cd8SNickeau */ 2837748cd8SNickeau private $dateTime; 2937748cd8SNickeau 301fa8c418SNickeau /** 311fa8c418SNickeau * ATOM = IS08601 321fa8c418SNickeau * See {@link Iso8601Date::getFormat()} for more information 331fa8c418SNickeau */ 341fa8c418SNickeau private const VALID_FORMATS = [ 351fa8c418SNickeau \DateTimeInterface::ATOM, 361fa8c418SNickeau 'Y-m-d H:i:sP', 371fa8c418SNickeau 'Y-m-d H:i:s', 381fa8c418SNickeau 'Y-m-d H:i', 391fa8c418SNickeau 'Y-m-d H', 401fa8c418SNickeau 'Y-m-d', 411fa8c418SNickeau ]; 421fa8c418SNickeau 4337748cd8SNickeau 4437748cd8SNickeau /** 4537748cd8SNickeau * Date constructor. 4637748cd8SNickeau */ 4737748cd8SNickeau public function __construct($dateTime = null) 4837748cd8SNickeau { 4937748cd8SNickeau 5037748cd8SNickeau if ($dateTime == null) { 5137748cd8SNickeau 5237748cd8SNickeau $this->dateTime = new DateTime(); 5337748cd8SNickeau 5437748cd8SNickeau } else { 5537748cd8SNickeau 5637748cd8SNickeau $this->dateTime = $dateTime; 5737748cd8SNickeau 5837748cd8SNickeau } 5937748cd8SNickeau 6037748cd8SNickeau } 6137748cd8SNickeau 62c3437056SNickeau /** 63*04fd306cSNickeau * @param $dateString 64c3437056SNickeau * @return Iso8601Date 65*04fd306cSNickeau * @throws ExceptionBadSyntax if the format is not supported 66c3437056SNickeau */ 67*04fd306cSNickeau public static function createFromString(string $dateString): Iso8601Date 6837748cd8SNickeau { 691fa8c418SNickeau 701fa8c418SNickeau $original = $dateString; 711fa8c418SNickeau 72*04fd306cSNickeau $dateString = trim($dateString); 7337748cd8SNickeau 7437748cd8SNickeau /** 7537748cd8SNickeau * Time ? 7637748cd8SNickeau * (ie only YYYY-MM-DD) 7737748cd8SNickeau */ 781fa8c418SNickeau if (strlen($dateString) <= 10) { 7937748cd8SNickeau /** 8037748cd8SNickeau * We had the time to 00:00:00 8137748cd8SNickeau * because {@link DateTime::createFromFormat} with a format of 8237748cd8SNickeau * Y-m-d will be using the actual time otherwise 8337748cd8SNickeau * 8437748cd8SNickeau */ 851fa8c418SNickeau $dateString .= "T00:00:00"; 861fa8c418SNickeau } 871fa8c418SNickeau 881fa8c418SNickeau /** 891fa8c418SNickeau * Space as T 901fa8c418SNickeau */ 911fa8c418SNickeau $dateString = str_replace(" ", "T", $dateString); 921fa8c418SNickeau 931fa8c418SNickeau 941fa8c418SNickeau if (strlen($dateString) <= 13) { 951fa8c418SNickeau /** 961fa8c418SNickeau * We had the time to 00:00:00 971fa8c418SNickeau * because {@link DateTime::createFromFormat} with a format of 981fa8c418SNickeau * Y-m-d will be using the actual time otherwise 991fa8c418SNickeau * 1001fa8c418SNickeau */ 1011fa8c418SNickeau $dateString .= ":00:00"; 1021fa8c418SNickeau } 1031fa8c418SNickeau 1041fa8c418SNickeau if (strlen($dateString) <= 16) { 1051fa8c418SNickeau /** 1061fa8c418SNickeau * We had the time to 00:00:00 1071fa8c418SNickeau * because {@link DateTime::createFromFormat} with a format of 1081fa8c418SNickeau * Y-m-d will be using the actual time otherwise 1091fa8c418SNickeau * 1101fa8c418SNickeau */ 1111fa8c418SNickeau $dateString .= ":00"; 11237748cd8SNickeau } 11337748cd8SNickeau 11437748cd8SNickeau /** 11537748cd8SNickeau * Timezone 11637748cd8SNickeau */ 1171fa8c418SNickeau if (strlen($dateString) <= 19) { 11837748cd8SNickeau /** 11937748cd8SNickeau * Because this text metadata may be used in other part of the application 12037748cd8SNickeau * We add the timezone to make it whole 12137748cd8SNickeau * And to have a consistent value 12237748cd8SNickeau */ 1231fa8c418SNickeau $dateString .= date('P'); 12437748cd8SNickeau } 12537748cd8SNickeau 1261fa8c418SNickeau 12726a7e0f8Sgerardnico $dateTime = DateTime::createFromFormat(self::getFormat(), $dateString); 1281fa8c418SNickeau if ($dateTime === false) { 129c3437056SNickeau $message = "The date string ($original) is not in a valid date format. (" . join(", ", self::VALID_FORMATS) . ")"; 130*04fd306cSNickeau throw new ExceptionBadSyntax($message, self::CANONICAL); 1311fa8c418SNickeau } 13237748cd8SNickeau return new Iso8601Date($dateTime); 1331fa8c418SNickeau 13437748cd8SNickeau } 13537748cd8SNickeau 1361fa8c418SNickeau public static function createFromTimestamp($timestamp): Iso8601Date 13737748cd8SNickeau { 13837748cd8SNickeau $dateTime = new DateTime(); 13937748cd8SNickeau $dateTime->setTimestamp($timestamp); 14037748cd8SNickeau return new Iso8601Date($dateTime); 14137748cd8SNickeau } 14237748cd8SNickeau 14337748cd8SNickeau /** 14437748cd8SNickeau * And note {@link DATE_ISO8601} 14537748cd8SNickeau * because it's not the compliant IS0-8601 format 14637748cd8SNickeau * as explained here 14737748cd8SNickeau * https://www.php.net/manual/en/class.datetimeinterface.php#datetime.constants.iso8601 14837748cd8SNickeau * ATOM is 14937748cd8SNickeau * 15037748cd8SNickeau * This format is used by Sqlite, Google and is pretty the standard everywhere 15137748cd8SNickeau * https://www.w3.org/TR/NOTE-datetime 15237748cd8SNickeau */ 1531fa8c418SNickeau public static function getFormat(): string 15437748cd8SNickeau { 15537748cd8SNickeau return DATE_ATOM; 15637748cd8SNickeau } 15737748cd8SNickeau 158*04fd306cSNickeau /** 159*04fd306cSNickeau * 160*04fd306cSNickeau */ 161c3437056SNickeau public static function isValid($value): bool 162c3437056SNickeau { 163*04fd306cSNickeau try { 164c3437056SNickeau $dateObject = Iso8601Date::createFromString($value); 165*04fd306cSNickeau return $dateObject->isValidDateEntry(); // ??? Validation seems to be at construction 166*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 167*04fd306cSNickeau return false; 168*04fd306cSNickeau } 169c3437056SNickeau } 170c3437056SNickeau 171c3437056SNickeau public function isValidDateEntry(): bool 17237748cd8SNickeau { 17337748cd8SNickeau if ($this->dateTime !== false) { 17437748cd8SNickeau return true; 17537748cd8SNickeau } else { 17637748cd8SNickeau return false; 17737748cd8SNickeau } 17837748cd8SNickeau } 17937748cd8SNickeau 180c3437056SNickeau public static function createFromDateTime(DateTime $dateTime): Iso8601Date 181c3437056SNickeau { 182c3437056SNickeau return new Iso8601Date($dateTime); 183c3437056SNickeau } 184c3437056SNickeau 185c3437056SNickeau public static function createFromNow(): Iso8601Date 186c3437056SNickeau { 187c3437056SNickeau return new Iso8601Date(); 188c3437056SNickeau } 189c3437056SNickeau 190*04fd306cSNickeau /** 191*04fd306cSNickeau * @throws ExceptionNotFound 192*04fd306cSNickeau */ 193*04fd306cSNickeau public static function getInternationalFormatter($constant): int 194*04fd306cSNickeau { 195*04fd306cSNickeau $constantNormalized = trim(strtolower($constant)); 196*04fd306cSNickeau switch ($constantNormalized) { 197*04fd306cSNickeau case "none": 198*04fd306cSNickeau return IntlDateFormatter::NONE; 199*04fd306cSNickeau case "full": 200*04fd306cSNickeau return IntlDateFormatter::FULL; 201*04fd306cSNickeau case "relativefull": 202*04fd306cSNickeau return IntlDateFormatter::RELATIVE_FULL; 203*04fd306cSNickeau case "long": 204*04fd306cSNickeau return IntlDateFormatter::LONG; 205*04fd306cSNickeau case "relativelong": 206*04fd306cSNickeau return IntlDateFormatter::RELATIVE_LONG; 207*04fd306cSNickeau case "medium": 208*04fd306cSNickeau return IntlDateFormatter::MEDIUM; 209*04fd306cSNickeau case "relativemedium": 210*04fd306cSNickeau return IntlDateFormatter::RELATIVE_MEDIUM; 211*04fd306cSNickeau case "short": 212*04fd306cSNickeau return IntlDateFormatter::SHORT; 213*04fd306cSNickeau case "relativeshort": 214*04fd306cSNickeau return IntlDateFormatter::RELATIVE_SHORT; 215*04fd306cSNickeau case "traditional": 216*04fd306cSNickeau return IntlDateFormatter::TRADITIONAL; 217*04fd306cSNickeau default: 218*04fd306cSNickeau throw new ExceptionNotFound("The constant ($constant) is not a valid constant", self::CANONICAL); 219*04fd306cSNickeau } 220*04fd306cSNickeau } 221*04fd306cSNickeau 22237748cd8SNickeau public function getDateTime() 22337748cd8SNickeau { 22437748cd8SNickeau return $this->dateTime; 22537748cd8SNickeau } 22637748cd8SNickeau 22737748cd8SNickeau public function __toString() 22837748cd8SNickeau { 22937748cd8SNickeau return $this->getDateTime()->format(self::getFormat()); 23037748cd8SNickeau } 23137748cd8SNickeau 232*04fd306cSNickeau public function toIsoStringMs() 233*04fd306cSNickeau { 234*04fd306cSNickeau return $this->getDateTime()->format("Y-m-d\TH:i:s.u"); 235*04fd306cSNickeau } 236*04fd306cSNickeau 23737748cd8SNickeau /** 23837748cd8SNickeau * Shortcut to {@link DateTime::format()} 239*04fd306cSNickeau * Format only in English 24037748cd8SNickeau * @param $string 24137748cd8SNickeau * @return string 24237748cd8SNickeau * @link https://php.net/manual/en/datetime.format.php 24337748cd8SNickeau */ 2441fa8c418SNickeau public function format($string): string 24537748cd8SNickeau { 24637748cd8SNickeau return $this->getDateTime()->format($string); 24737748cd8SNickeau } 24837748cd8SNickeau 249c3437056SNickeau public function toString() 250c3437056SNickeau { 251c3437056SNickeau return $this->__toString(); 252c3437056SNickeau } 253c3437056SNickeau 254*04fd306cSNickeau /** 255*04fd306cSNickeau * @throws ExceptionBadSyntax 256*04fd306cSNickeau */ 257*04fd306cSNickeau public function formatLocale($pattern = null, $locale = null) 258*04fd306cSNickeau { 259c3437056SNickeau 260*04fd306cSNickeau /** 261*04fd306cSNickeau * https://www.php.net/manual/en/function.strftime.php 262*04fd306cSNickeau * As been deprecated 263*04fd306cSNickeau * The only alternative with local is 264*04fd306cSNickeau * https://www.php.net/manual/en/intldateformatter.format.php 265*04fd306cSNickeau * 266*04fd306cSNickeau * Based on ISO date 267*04fd306cSNickeau * ICU Date formatter: https://unicode-org.github.io/icu-docs/#/icu4c/udat_8h.html 268*04fd306cSNickeau * ICU Date formats: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax 269*04fd306cSNickeau * ICU User Guide: https://unicode-org.github.io/icu/userguide/ 270*04fd306cSNickeau * ICU Formatting Dates and Times: https://unicode-org.github.io/icu/userguide/format_parse/datetime/ 271*04fd306cSNickeau */ 272*04fd306cSNickeau if (strpos($pattern, "%") !== false) { 273*04fd306cSNickeau 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*04fd306cSNickeau return strftime($pattern, $this->dateTime->getTimestamp()); 275*04fd306cSNickeau } 276*04fd306cSNickeau 277*04fd306cSNickeau /** 278*04fd306cSNickeau * This parameters 279*04fd306cSNickeau * are used to format date with the locale 280*04fd306cSNickeau * when the pattern is null 281*04fd306cSNickeau * Doc: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#producing-normal-date-formats-for-a-locale 282*04fd306cSNickeau * 283*04fd306cSNickeau * They may be null by the way. 284*04fd306cSNickeau * 285*04fd306cSNickeau */ 286*04fd306cSNickeau $dateType = self::DATE_FORMATTER_TYPE; 287*04fd306cSNickeau $timeType = self::TIME_FORMATTER_TYPE; 288*04fd306cSNickeau if ($pattern !== null) { 289*04fd306cSNickeau $normalFormat = explode(" ", $pattern); 290*04fd306cSNickeau if (sizeof($normalFormat) === 2) { 291*04fd306cSNickeau try { 292*04fd306cSNickeau $dateType = self::getInternationalFormatter($normalFormat[0]); 293*04fd306cSNickeau $timeType = self::getInternationalFormatter($normalFormat[1]); 294*04fd306cSNickeau $pattern = null; 295*04fd306cSNickeau } catch (ExceptionNotFound $e) { 296*04fd306cSNickeau // ok 297*04fd306cSNickeau } 298*04fd306cSNickeau } 299*04fd306cSNickeau } 300*04fd306cSNickeau 301*04fd306cSNickeau /** 302*04fd306cSNickeau * Formatter instantiation 303*04fd306cSNickeau */ 304*04fd306cSNickeau $formatter = datefmt_create( 305*04fd306cSNickeau $locale, 306*04fd306cSNickeau $dateType, 307*04fd306cSNickeau $timeType, 308*04fd306cSNickeau $this->dateTime->getTimezone(), 309*04fd306cSNickeau IntlDateFormatter::GREGORIAN, 310*04fd306cSNickeau $pattern 311*04fd306cSNickeau ); 312*04fd306cSNickeau $formatted = datefmt_format($formatter, $this->dateTime); 313*04fd306cSNickeau if ($formatted === false) { 314*04fd306cSNickeau if ($locale === null) { 315*04fd306cSNickeau $locale = ""; 316*04fd306cSNickeau } 317*04fd306cSNickeau if ($pattern === null) { 318*04fd306cSNickeau $pattern = ""; 319*04fd306cSNickeau } 320*04fd306cSNickeau throw new ExceptionBadSyntax("Unable to format the date with the pattern ($pattern) and locale ($locale)"); 321*04fd306cSNickeau } 322*04fd306cSNickeau return $formatted; 323*04fd306cSNickeau } 324*04fd306cSNickeau 325*04fd306cSNickeau public function olderThan(DateTime $rightTime): bool 326*04fd306cSNickeau { 327*04fd306cSNickeau 328*04fd306cSNickeau $internalMs = DataType::toMilliSeconds($this->dateTime); 329*04fd306cSNickeau $externalMilliSeconds = DataType::toMilliSeconds($rightTime); 330*04fd306cSNickeau if ($externalMilliSeconds > $internalMs) { 331*04fd306cSNickeau return true; 332*04fd306cSNickeau } 333*04fd306cSNickeau return false; 334*04fd306cSNickeau 335*04fd306cSNickeau } 336*04fd306cSNickeau 337*04fd306cSNickeau public function diff(DateTime $rightTime): \DateInterval 338*04fd306cSNickeau { 339*04fd306cSNickeau // example get the s part of the diff (even if there is day of diff) 340*04fd306cSNickeau // $seconds = $diff->format('%s'); 341*04fd306cSNickeau return $this->dateTime->diff($rightTime, true); 342*04fd306cSNickeau } 343c3437056SNickeau 34437748cd8SNickeau 34537748cd8SNickeau} 346