EthikTag/vendor/symfony/polyfill-intl-icu/IntlDateFormatter.php

646 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Intl\Icu;
use Symfony\Polyfill\Intl\Icu\DateFormat\FullTransformer;
use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentNotImplementedException;
use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentValueNotImplementedException;
use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
/**
* Replacement for PHP's native {@link \IntlDateFormatter} class.
*
* The only methods currently supported in this class are:
*
* - {@link __construct}
* - {@link create}
* - {@link format}
* - {@link getCalendar}
* - {@link getDateType}
* - {@link getErrorCode}
* - {@link getErrorMessage}
* - {@link getLocale}
* - {@link getPattern}
* - {@link getTimeType}
* - {@link getTimeZoneId}
* - {@link isLenient}
* - {@link parse}
* - {@link setLenient}
* - {@link setPattern}
* - {@link setTimeZoneId}
* - {@link setTimeZone}
*
* @author Igor Wiedler <igor@wiedler.ch>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
abstract class IntlDateFormatter
{
/**
* The error code from the last operation.
*
* @var int
*/
protected $errorCode = Icu::U_ZERO_ERROR;
/**
* The error message from the last operation.
*
* @var string
*/
protected $errorMessage = 'U_ZERO_ERROR';
/* date/time format types */
public const NONE = -1;
public const FULL = 0;
public const LONG = 1;
public const MEDIUM = 2;
public const SHORT = 3;
/* date format types */
public const RELATIVE_FULL = 128;
public const RELATIVE_LONG = 129;
public const RELATIVE_MEDIUM = 130;
public const RELATIVE_SHORT = 131;
/* calendar formats */
public const TRADITIONAL = 0;
public const GREGORIAN = 1;
/**
* Patterns used to format the date when no pattern is provided.
*/
private $defaultDateFormats = [
self::NONE => '',
self::FULL => 'EEEE, MMMM d, y',
self::LONG => 'MMMM d, y',
self::MEDIUM => 'MMM d, y',
self::SHORT => 'M/d/yy',
self::RELATIVE_FULL => 'EEEE, MMMM d, y',
self::RELATIVE_LONG => 'MMMM d, y',
self::RELATIVE_MEDIUM => 'MMM d, y',
self::RELATIVE_SHORT => 'M/d/yy',
];
/**
* Patterns used to format the time when no pattern is provided.
*/
private $defaultTimeFormats = [
self::FULL => 'h:mm:ss a zzzz',
self::LONG => 'h:mm:ss a z',
self::MEDIUM => 'h:mm:ss a',
self::SHORT => 'h:mm a',
];
private $dateType;
private $timeType;
/**
* @var string
*/
private $pattern;
/**
* @var \DateTimeZone
*/
private $dateTimeZone;
/**
* @var bool
*/
private $uninitializedTimeZoneId = false;
/**
* @var string
*/
private $timezoneId;
/**
* @var bool
*/
private $isRelativeDateType = false;
/**
* @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en")
* @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier
* @param \IntlCalendar|int|null $calendar Calendar to use for formatting or parsing. The only currently
* supported value is IntlDateFormatter::GREGORIAN (or null using the default calendar, i.e. "GREGORIAN")
*
* @see https://php.net/intldateformatter.create
* @see http://userguide.icu-project.org/formatparse/datetime
*
* @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed
* @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed
*/
public function __construct(?string $locale, ?int $dateType, ?int $timeType, $timezone = null, $calendar = null, ?string $pattern = '')
{
if ('en' !== $locale && null !== $locale) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported');
}
if (self::GREGORIAN !== $calendar && null !== $calendar) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'calendar', $calendar, 'Only the GREGORIAN calendar is supported');
}
if (\PHP_VERSION_ID >= 80100) {
if (null === $dateType) {
@trigger_error('Passing null to parameter #2 ($dateType) of type int is deprecated', \E_USER_DEPRECATED);
}
if (null === $timeType) {
@trigger_error('Passing null to parameter #3 ($timeType) of type int is deprecated', \E_USER_DEPRECATED);
}
}
$this->dateType = $dateType ?? self::FULL;
$this->timeType = $timeType ?? self::FULL;
if ('' === ($pattern ?? '')) {
$pattern = $this->getDefaultPattern();
}
$this->setPattern($pattern);
$this->setTimeZone($timezone);
if (\in_array($this->dateType, [self::RELATIVE_FULL, self::RELATIVE_LONG, self::RELATIVE_MEDIUM, self::RELATIVE_SHORT], true)) {
$this->isRelativeDateType = true;
}
}
/**
* Static constructor.
*
* @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en")
* @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier
* @param \IntlCalendar|int|null $calendar Calendar to use for formatting or parsing; default is Gregorian
* One of the calendar constants
*
* @return static
*
* @see https://php.net/intldateformatter.create
* @see http://userguide.icu-project.org/formatparse/datetime
*
* @throws MethodArgumentValueNotImplementedException When $locale different than "en" or null is passed
* @throws MethodArgumentValueNotImplementedException When $calendar different than GREGORIAN is passed
*/
public static function create(?string $locale, ?int $dateType, ?int $timeType, $timezone = null, int $calendar = null, ?string $pattern = '')
{
return new static($locale, $dateType, $timeType, $timezone, $calendar, $pattern);
}
/**
* Format the date/time value (timestamp) as a string.
*
* @param int|string|\DateTimeInterface $datetime The timestamp to format
*
* @return string|bool The formatted value or false if formatting failed
*
* @see https://php.net/intldateformatter.format
*
* @throws MethodArgumentValueNotImplementedException If one of the formatting characters is not implemented
*/
public function format($datetime)
{
// intl allows timestamps to be passed as arrays - we don't
if (\is_array($datetime)) {
$message = 'Only Unix timestamps and DateTime objects are supported';
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'datetime', $datetime, $message);
}
if (\is_string($datetime) && $dt = \DateTime::createFromFormat('U', $datetime)) {
$datetime = $dt;
}
// behave like the intl extension
$argumentError = null;
if (!\is_int($datetime) && !$datetime instanceof \DateTimeInterface) {
$argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $datetime);
}
if (null !== $argumentError) {
Icu::setError(Icu::U_ILLEGAL_ARGUMENT_ERROR, $argumentError);
$this->errorCode = Icu::getErrorCode();
$this->errorMessage = Icu::getErrorMessage();
return false;
}
if ($datetime instanceof \DateTimeInterface) {
$datetime = $datetime->format('U');
}
$pattern = $this->getPattern();
$formatted = '';
if ($this->isRelativeDateType && $formatted = $this->getRelativeDateFormat($datetime)) {
if (self::NONE === $this->timeType) {
$pattern = '';
} else {
$pattern = $this->defaultTimeFormats[$this->timeType];
if (\in_array($this->dateType, [self::RELATIVE_MEDIUM, self::RELATIVE_SHORT], true)) {
$formatted .= ', ';
} else {
$formatted .= ' at ';
}
}
}
$transformer = new FullTransformer($pattern, $this->getTimeZoneId());
$formatted .= $transformer->format($this->createDateTime($datetime));
// behave like the intl extension
Icu::setError(Icu::U_ZERO_ERROR);
$this->errorCode = Icu::getErrorCode();
$this->errorMessage = Icu::getErrorMessage();
return $formatted;
}
/**
* Not supported. Formats an object.
*
* @return string The formatted value
*
* @see https://php.net/intldateformatter.formatobject
*
* @throws MethodNotImplementedException
*/
public static function formatObject($datetime, $format = null, string $locale = null)
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Returns the formatter's calendar.
*
* @return int The calendar being used by the formatter. Currently always returns
* IntlDateFormatter::GREGORIAN.
*
* @see https://php.net/intldateformatter.getcalendar
*/
public function getCalendar()
{
return self::GREGORIAN;
}
/**
* Not supported. Returns the formatter's calendar object.
*
* @return object The calendar's object being used by the formatter
*
* @see https://php.net/intldateformatter.getcalendarobject
*
* @throws MethodNotImplementedException
*/
public function getCalendarObject()
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Returns the formatter's datetype.
*
* @return int The current value of the formatter
*
* @see https://php.net/intldateformatter.getdatetype
*/
public function getDateType()
{
return $this->dateType;
}
/**
* Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value.
*
* @return int The error code from last formatter call
*
* @see https://php.net/intldateformatter.geterrorcode
*/
public function getErrorCode()
{
return $this->errorCode;
}
/**
* Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value.
*
* @return string The error message from last formatter call
*
* @see https://php.net/intldateformatter.geterrormessage
*/
public function getErrorMessage()
{
return $this->errorMessage;
}
/**
* Returns the formatter's locale.
*
* @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE)
*
* @return string The locale used to create the formatter. Currently always
* returns "en".
*
* @see https://php.net/intldateformatter.getlocale
*/
public function getLocale(int $type = Locale::ACTUAL_LOCALE)
{
return 'en';
}
/**
* Returns the formatter's pattern.
*
* @return string The pattern string used by the formatter
*
* @see https://php.net/intldateformatter.getpattern
*/
public function getPattern()
{
return $this->pattern;
}
/**
* Returns the formatter's time type.
*
* @return int The time type used by the formatter
*
* @see https://php.net/intldateformatter.gettimetype
*/
public function getTimeType()
{
return $this->timeType;
}
/**
* Returns the formatter's timezone identifier.
*
* @return string The timezone identifier used by the formatter
*
* @see https://php.net/intldateformatter.gettimezoneid
*/
public function getTimeZoneId()
{
if (!$this->uninitializedTimeZoneId) {
return $this->timezoneId;
}
return date_default_timezone_get();
}
/**
* Not supported. Returns the formatter's timezone.
*
* @return mixed The timezone used by the formatter
*
* @see https://php.net/intldateformatter.gettimezone
*
* @throws MethodNotImplementedException
*/
public function getTimeZone()
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Returns whether the formatter is lenient.
*
* @return bool Currently always returns false
*
* @see https://php.net/intldateformatter.islenient
*
* @throws MethodNotImplementedException
*/
public function isLenient()
{
return false;
}
/**
* Not supported. Parse string to a field-based time value.
*
* @return string Localtime compatible array of integers: contains 24 hour clock value in tm_hour field
*
* @see https://php.net/intldateformatter.localtime
*
* @throws MethodNotImplementedException
*/
public function localtime(string $string, &$offset = null)
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Parse string to a timestamp value.
*
* @return int|false Parsed value as a timestamp
*
* @see https://php.net/intldateformatter.parse
*
* @throws MethodArgumentNotImplementedException When $offset different than null, behavior not implemented
*/
public function parse(string $string, &$offset = null)
{
// We don't calculate the position when parsing the value
if (null !== $offset) {
throw new MethodArgumentNotImplementedException(__METHOD__, 'offset');
}
$dateTime = $this->createDateTime(0);
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
$timestamp = $transformer->parse($dateTime, $string);
// behave like the intl extension. FullTransformer::parse() set the proper error
$this->errorCode = Icu::getErrorCode();
$this->errorMessage = Icu::getErrorMessage();
return $timestamp;
}
/**
* Not supported. Set the formatter's calendar.
*
* @param \IntlCalendar|int|null $calendar
*
* @return bool true on success or false on failure
*
* @see https://php.net/intldateformatter.setcalendar
*
* @throws MethodNotImplementedException
*/
public function setCalendar($calendar)
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Set the leniency of the parser.
*
* Define if the parser is strict or lenient in interpreting inputs that do not match the pattern
* exactly. Enabling lenient parsing allows the parser to accept otherwise flawed date or time
* patterns, parsing as much as possible to obtain a value. Extra space, unrecognized tokens, or
* invalid values ("February 30th") are not accepted.
*
* @param bool $lenient Sets whether the parser is lenient or not. Currently
* only false (strict) is supported.
*
* @return bool true on success or false on failure
*
* @see https://php.net/intldateformatter.setlenient
*
* @throws MethodArgumentValueNotImplementedException When $lenient is true
*/
public function setLenient(bool $lenient)
{
if ($lenient) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'lenient', $lenient, 'Only the strict parser is supported');
}
return true;
}
/**
* Set the formatter's pattern.
*
* @return bool true on success or false on failure
*
* @see https://php.net/intldateformatter.setpattern
* @see http://userguide.icu-project.org/formatparse/datetime
*/
public function setPattern(string $pattern)
{
$this->pattern = $pattern;
return true;
}
/**
* Sets formatterʼs timezone.
*
* @param \IntlTimeZone|\DateTimeZone|string|null $timezone
*
* @return bool true on success or false on failure
*
* @see https://php.net/intldateformatter.settimezone
*/
public function setTimeZone($timezone)
{
if ($timezone instanceof \IntlTimeZone) {
$timezone = $timezone->getID();
}
if ($timezone instanceof \DateTimeZone) {
$timezone = $timezone->getName();
// DateTimeZone returns the GMT offset timezones without the leading GMT, while our parsing requires it.
if (!empty($timezone) && ('+' === $timezone[0] || '-' === $timezone[0])) {
$timezone = 'GMT'.$timezone;
}
}
if (null === $timezone) {
$timezone = date_default_timezone_get();
$this->uninitializedTimeZoneId = true;
}
// Backup original passed time zone
$timezoneId = $timezone;
// Get an Etc/GMT time zone that is accepted for \DateTimeZone
if ('GMT' !== $timezone && 0 === strpos($timezone, 'GMT')) {
try {
$timezone = DateFormat\TimezoneTransformer::getEtcTimeZoneId($timezone);
} catch (\InvalidArgumentException $e) {
// Does nothing, will fallback to UTC
}
}
try {
$this->dateTimeZone = new \DateTimeZone($timezone);
if ('GMT' !== $timezone && $this->dateTimeZone->getName() !== $timezone) {
$timezoneId = $this->getTimeZoneId();
}
} catch (\Exception $e) {
$timezoneId = $timezone = $this->getTimeZoneId();
$this->dateTimeZone = new \DateTimeZone($timezone);
}
$this->timezoneId = $timezoneId;
return true;
}
/**
* Create and returns a DateTime object with the specified timestamp and with the
* current time zone.
*
* @return \DateTime
*/
protected function createDateTime($timestamp)
{
$dateTime = \DateTime::createFromFormat('U', $timestamp);
$dateTime->setTimezone($this->dateTimeZone);
return $dateTime;
}
/**
* Returns a pattern string based in the datetype and timetype values.
*
* @return string
*/
protected function getDefaultPattern()
{
$pattern = '';
if (self::NONE !== $this->dateType) {
$pattern = $this->defaultDateFormats[$this->dateType];
}
if (self::NONE !== $this->timeType) {
if (\in_array($this->dateType, [self::FULL, self::LONG, self::RELATIVE_FULL, self::RELATIVE_LONG], true)) {
$pattern .= ' \'at\' ';
} elseif (self::NONE !== $this->dateType) {
$pattern .= ', ';
}
$pattern .= $this->defaultTimeFormats[$this->timeType];
}
return $pattern;
}
private function getRelativeDateFormat(int $timestamp): string
{
$today = $this->createDateTime(time());
$today->setTime(0, 0, 0);
$datetime = $this->createDateTime($timestamp);
$datetime->setTime(0, 0, 0);
$interval = $today->diff($datetime);
if (false !== $interval) {
if (0 === $interval->days) {
return 'today';
}
if (1 === $interval->days) {
return 1 === $interval->invert ? 'yesterday' : 'tomorrow';
}
}
return '';
}
}