137748cd8SNickeau<?php 237748cd8SNickeau 337748cd8SNickeau 437748cd8SNickeaunamespace ComboStrap; 537748cd8SNickeau 637748cd8SNickeau 737748cd8SNickeauuse DateTime; 804fd306cSNickeauuse 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"; 2304fd306cSNickeau public const TIME_FORMATTER_TYPE = IntlDateFormatter::NONE; 2404fd306cSNickeau 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 /** 6304fd306cSNickeau * @param $dateString 64c3437056SNickeau * @return Iso8601Date 6504fd306cSNickeau * @throws ExceptionBadSyntax if the format is not supported 66c3437056SNickeau */ 6704fd306cSNickeau public static function createFromString(string $dateString): Iso8601Date 6837748cd8SNickeau { 691fa8c418SNickeau 701fa8c418SNickeau $original = $dateString; 711fa8c418SNickeau 7204fd306cSNickeau $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) . ")"; 13004fd306cSNickeau 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 15804fd306cSNickeau /** 15904fd306cSNickeau * 16004fd306cSNickeau */ 161c3437056SNickeau public static function isValid($value): bool 162c3437056SNickeau { 16304fd306cSNickeau try { 164c3437056SNickeau $dateObject = Iso8601Date::createFromString($value); 16504fd306cSNickeau return $dateObject->isValidDateEntry(); // ??? Validation seems to be at construction 16604fd306cSNickeau } catch (ExceptionBadSyntax $e) { 16704fd306cSNickeau return false; 16804fd306cSNickeau } 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 19004fd306cSNickeau /** 19104fd306cSNickeau * @throws ExceptionNotFound 19204fd306cSNickeau */ 19304fd306cSNickeau public static function getInternationalFormatter($constant): int 19404fd306cSNickeau { 19504fd306cSNickeau $constantNormalized = trim(strtolower($constant)); 19604fd306cSNickeau switch ($constantNormalized) { 19704fd306cSNickeau case "none": 19804fd306cSNickeau return IntlDateFormatter::NONE; 19904fd306cSNickeau case "full": 20004fd306cSNickeau return IntlDateFormatter::FULL; 20104fd306cSNickeau case "relativefull": 20204fd306cSNickeau return IntlDateFormatter::RELATIVE_FULL; 20304fd306cSNickeau case "long": 20404fd306cSNickeau return IntlDateFormatter::LONG; 20504fd306cSNickeau case "relativelong": 20604fd306cSNickeau return IntlDateFormatter::RELATIVE_LONG; 20704fd306cSNickeau case "medium": 20804fd306cSNickeau return IntlDateFormatter::MEDIUM; 20904fd306cSNickeau case "relativemedium": 21004fd306cSNickeau return IntlDateFormatter::RELATIVE_MEDIUM; 21104fd306cSNickeau case "short": 21204fd306cSNickeau return IntlDateFormatter::SHORT; 21304fd306cSNickeau case "relativeshort": 21404fd306cSNickeau return IntlDateFormatter::RELATIVE_SHORT; 21504fd306cSNickeau case "traditional": 21604fd306cSNickeau return IntlDateFormatter::TRADITIONAL; 21704fd306cSNickeau default: 21804fd306cSNickeau throw new ExceptionNotFound("The constant ($constant) is not a valid constant", self::CANONICAL); 21904fd306cSNickeau } 22004fd306cSNickeau } 22104fd306cSNickeau 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 23204fd306cSNickeau public function toIsoStringMs() 23304fd306cSNickeau { 23404fd306cSNickeau return $this->getDateTime()->format("Y-m-d\TH:i:s.u"); 23504fd306cSNickeau } 23604fd306cSNickeau 23737748cd8SNickeau /** 23837748cd8SNickeau * Shortcut to {@link DateTime::format()} 23904fd306cSNickeau * 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 25404fd306cSNickeau /** 25504fd306cSNickeau * @throws ExceptionBadSyntax 25604fd306cSNickeau */ 25704fd306cSNickeau public function formatLocale($pattern = null, $locale = null) 25804fd306cSNickeau { 25904fd306cSNickeau /** 26004fd306cSNickeau * https://www.php.net/manual/en/function.strftime.php 26104fd306cSNickeau * As been deprecated 26204fd306cSNickeau * The only alternative with local is 26304fd306cSNickeau * https://www.php.net/manual/en/intldateformatter.format.php 26404fd306cSNickeau * 26504fd306cSNickeau * Based on ISO date 26604fd306cSNickeau * ICU Date formatter: https://unicode-org.github.io/icu-docs/#/icu4c/udat_8h.html 26704fd306cSNickeau * ICU Date formats: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax 26804fd306cSNickeau * ICU User Guide: https://unicode-org.github.io/icu/userguide/ 26904fd306cSNickeau * ICU Formatting Dates and Times: https://unicode-org.github.io/icu/userguide/format_parse/datetime/ 27004fd306cSNickeau */ 27104fd306cSNickeau if (strpos($pattern, "%") !== false) { 27204fd306cSNickeau 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); 27304fd306cSNickeau return strftime($pattern, $this->dateTime->getTimestamp()); 27404fd306cSNickeau } 27504fd306cSNickeau 27604fd306cSNickeau /** 27704fd306cSNickeau * This parameters 27804fd306cSNickeau * are used to format date with the locale 27904fd306cSNickeau * when the pattern is null 28004fd306cSNickeau * Doc: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#producing-normal-date-formats-for-a-locale 28104fd306cSNickeau * 28204fd306cSNickeau * They may be null by the way. 28304fd306cSNickeau * 28404fd306cSNickeau */ 28504fd306cSNickeau $dateType = self::DATE_FORMATTER_TYPE; 28604fd306cSNickeau $timeType = self::TIME_FORMATTER_TYPE; 28704fd306cSNickeau if ($pattern !== null) { 28804fd306cSNickeau $normalFormat = explode(" ", $pattern); 28904fd306cSNickeau if (sizeof($normalFormat) === 2) { 29004fd306cSNickeau try { 29104fd306cSNickeau $dateType = self::getInternationalFormatter($normalFormat[0]); 29204fd306cSNickeau $timeType = self::getInternationalFormatter($normalFormat[1]); 29304fd306cSNickeau $pattern = null; 29404fd306cSNickeau } catch (ExceptionNotFound $e) { 29504fd306cSNickeau // ok 29604fd306cSNickeau } 29704fd306cSNickeau } 29804fd306cSNickeau } 29904fd306cSNickeau 30004fd306cSNickeau /** 30104fd306cSNickeau * Formatter instantiation 302*313de40aSNicolas GERARD * https://www.php.net/manual/en/intldateformatter.create.php 303*313de40aSNicolas GERARD * List of local: with ResourceBundle::getLocales('') 30404fd306cSNickeau */ 305d8add9b6SNicolas GERARD $intlDateFormatter = datefmt_create( 30604fd306cSNickeau $locale, 30704fd306cSNickeau $dateType, 30804fd306cSNickeau $timeType, 30904fd306cSNickeau $this->dateTime->getTimezone(), 31004fd306cSNickeau IntlDateFormatter::GREGORIAN, 31104fd306cSNickeau $pattern 31204fd306cSNickeau ); 313d8add9b6SNicolas GERARD try { 314d8add9b6SNicolas GERARD $formatted = datefmt_format($intlDateFormatter, $this->dateTime); 315d8add9b6SNicolas GERARD } catch (\Error $e) { 316d8add9b6SNicolas GERARD // Found unconstructed IntlDateFormatter 317d8add9b6SNicolas GERARD // No idea how I can check that before formatting 318*313de40aSNicolas GERARD LogUtility::warning("Locale value ($locale) is an unknown ICU locale. Using default locale instead", self::CANONICAL); 319d8add9b6SNicolas GERARD $intlDateFormatter = datefmt_create( 320d8add9b6SNicolas GERARD null, 321d8add9b6SNicolas GERARD $dateType, 322d8add9b6SNicolas GERARD $timeType, 323d8add9b6SNicolas GERARD $this->dateTime->getTimezone(), 324d8add9b6SNicolas GERARD IntlDateFormatter::GREGORIAN, 325d8add9b6SNicolas GERARD $pattern 326d8add9b6SNicolas GERARD ); 327d8add9b6SNicolas GERARD $formatted = datefmt_format($intlDateFormatter, $this->dateTime); 328d8add9b6SNicolas GERARD } 32904fd306cSNickeau if ($formatted === false) { 33004fd306cSNickeau if ($locale === null) { 33104fd306cSNickeau $locale = ""; 33204fd306cSNickeau } 33304fd306cSNickeau if ($pattern === null) { 33404fd306cSNickeau $pattern = ""; 33504fd306cSNickeau } 33604fd306cSNickeau throw new ExceptionBadSyntax("Unable to format the date with the pattern ($pattern) and locale ($locale)"); 33704fd306cSNickeau } 33804fd306cSNickeau return $formatted; 33904fd306cSNickeau } 34004fd306cSNickeau 34104fd306cSNickeau public function olderThan(DateTime $rightTime): bool 34204fd306cSNickeau { 34304fd306cSNickeau 34404fd306cSNickeau $internalMs = DataType::toMilliSeconds($this->dateTime); 34504fd306cSNickeau $externalMilliSeconds = DataType::toMilliSeconds($rightTime); 34604fd306cSNickeau if ($externalMilliSeconds > $internalMs) { 34704fd306cSNickeau return true; 34804fd306cSNickeau } 34904fd306cSNickeau return false; 35004fd306cSNickeau 35104fd306cSNickeau } 35204fd306cSNickeau 35304fd306cSNickeau public function diff(DateTime $rightTime): \DateInterval 35404fd306cSNickeau { 35504fd306cSNickeau // example get the s part of the diff (even if there is day of diff) 35604fd306cSNickeau // $seconds = $diff->format('%s'); 35704fd306cSNickeau return $this->dateTime->diff($rightTime, true); 35804fd306cSNickeau } 359c3437056SNickeau 36037748cd8SNickeau 36137748cd8SNickeau} 362