Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for AM/PM markers format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class AmPmTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
return $dateTime->format('A');
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 'AM|PM';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'marker' => $matched,
];
}
}

View File

@@ -0,0 +1,65 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for day of week format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class DayOfWeekTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$dayOfWeek = $dateTime->format('l');
switch ($length) {
case 4:
return $dayOfWeek;
case 5:
return $dayOfWeek[0];
case 6:
return substr($dayOfWeek, 0, 2);
default:
return substr($dayOfWeek, 0, 3);
}
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
switch ($length) {
case 4:
return 'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday';
case 5:
return '[MTWFS]';
case 6:
return 'Mo|Tu|We|Th|Fr|Sa|Su';
default:
return 'Mon|Tue|Wed|Thu|Fri|Sat|Sun';
}
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [];
}
}

View File

@@ -0,0 +1,48 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for day of year format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class DayOfYearTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$dayOfYear = (int) $dateTime->format('z') + 1;
return $this->padLeft($dayOfYear, $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return '\d{'.$length.'}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [];
}
}

View File

@@ -0,0 +1,48 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for day format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class DayTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
return $this->padLeft($dateTime->format('j'), $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 1 === $length ? '\d{1,2}' : '\d{1,'.$length.'}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'day' => (int) $matched,
];
}
}

View File

@@ -0,0 +1,351 @@
<?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\Component\Intl\DateFormatter\DateFormat;
use Symfony\Component\Intl\Exception\NotImplementedException;
use Symfony\Component\Intl\Globals\IntlGlobals;
/**
* Parser and formatter for date formats.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class FullTransformer
{
private $quoteMatch = "'(?:[^']+|'')*'";
private $implementedChars = 'MLydQqhDEaHkKmsz';
private $notImplementedChars = 'GYuwWFgecSAZvVW';
private $regExp;
/**
* @var Transformer[]
*/
private $transformers;
private $pattern;
private $timezone;
/**
* @param string $pattern The pattern to be used to format and/or parse values
* @param string $timezone The timezone to perform the date/time calculations
*/
public function __construct($pattern, $timezone)
{
$this->pattern = $pattern;
$this->timezone = $timezone;
$implementedCharsMatch = $this->buildCharsMatch($this->implementedChars);
$notImplementedCharsMatch = $this->buildCharsMatch($this->notImplementedChars);
$this->regExp = "/($this->quoteMatch|$implementedCharsMatch|$notImplementedCharsMatch)/";
$this->transformers = [
'M' => new MonthTransformer(),
'L' => new MonthTransformer(),
'y' => new YearTransformer(),
'd' => new DayTransformer(),
'q' => new QuarterTransformer(),
'Q' => new QuarterTransformer(),
'h' => new Hour1201Transformer(),
'D' => new DayOfYearTransformer(),
'E' => new DayOfWeekTransformer(),
'a' => new AmPmTransformer(),
'H' => new Hour2400Transformer(),
'K' => new Hour1200Transformer(),
'k' => new Hour2401Transformer(),
'm' => new MinuteTransformer(),
's' => new SecondTransformer(),
'z' => new TimezoneTransformer(),
];
}
/**
* Return the array of Transformer objects.
*
* @return Transformer[] Associative array of Transformer objects (format char => Transformer)
*/
public function getTransformers()
{
return $this->transformers;
}
/**
* Format a DateTime using ICU dateformat pattern.
*
* @param \DateTime $dateTime A DateTime object to be used to generate the formatted value
*
* @return string The formatted value
*/
public function format(\DateTime $dateTime)
{
$formatted = preg_replace_callback($this->regExp, function ($matches) use ($dateTime) {
return $this->formatReplace($matches[0], $dateTime);
}, $this->pattern);
return $formatted;
}
/**
* Return the formatted ICU value for the matched date characters.
*
* @param string $dateChars The date characters to be replaced with a formatted ICU value
* @param \DateTime $dateTime A DateTime object to be used to generate the formatted value
*
* @return string|null The formatted value
*
* @throws NotImplementedException When it encounters a not implemented date character
*/
public function formatReplace($dateChars, $dateTime)
{
$length = \strlen($dateChars);
if ($this->isQuoteMatch($dateChars)) {
return $this->replaceQuoteMatch($dateChars);
}
if (isset($this->transformers[$dateChars[0]])) {
$transformer = $this->transformers[$dateChars[0]];
return $transformer->format($dateTime, $length);
}
// handle unimplemented characters
if (false !== strpos($this->notImplementedChars, $dateChars[0])) {
throw new NotImplementedException(sprintf('Unimplemented date character "%s" in format "%s".', $dateChars[0], $this->pattern));
}
return null;
}
/**
* Parse a pattern based string to a timestamp value.
*
* @param \DateTime $dateTime A configured DateTime object to use to perform the date calculation
* @param string $value String to convert to a time value
*
* @return int|false The corresponding Unix timestamp
*
* @throws \InvalidArgumentException When the value can not be matched with pattern
*/
public function parse(\DateTime $dateTime, $value)
{
$reverseMatchingRegExp = $this->getReverseMatchingRegExp($this->pattern);
$reverseMatchingRegExp = '/^'.$reverseMatchingRegExp.'$/';
$options = [];
if (preg_match($reverseMatchingRegExp, $value, $matches)) {
$matches = $this->normalizeArray($matches);
foreach ($this->transformers as $char => $transformer) {
if (isset($matches[$char])) {
$length = \strlen($matches[$char]['pattern']);
$options = array_merge($options, $transformer->extractDateOptions($matches[$char]['value'], $length));
}
}
// reset error code and message
IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR);
return $this->calculateUnixTimestamp($dateTime, $options);
}
// behave like the intl extension
IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed');
return false;
}
/**
* Retrieve a regular expression to match with a formatted value.
*
* @param string $pattern The pattern to create the reverse matching regular expression
*
* @return string The reverse matching regular expression with named captures being formed by the
* transformer index in the $transformer array
*/
public function getReverseMatchingRegExp($pattern)
{
$escapedPattern = preg_quote($pattern, '/');
// ICU 4.8 recognizes slash ("/") in a value to be parsed as a dash ("-") and vice-versa
// when parsing a date/time value
$escapedPattern = preg_replace('/\\\[\-|\/]/', '[\/\-]', $escapedPattern);
$reverseMatchingRegExp = preg_replace_callback($this->regExp, function ($matches) {
$length = \strlen($matches[0]);
$transformerIndex = $matches[0][0];
$dateChars = $matches[0];
if ($this->isQuoteMatch($dateChars)) {
return $this->replaceQuoteMatch($dateChars);
}
$transformers = $this->getTransformers();
if (isset($transformers[$transformerIndex])) {
$transformer = $transformers[$transformerIndex];
$captureName = str_repeat($transformerIndex, $length);
return "(?P<$captureName>".$transformer->getReverseMatchingRegExp($length).')';
}
return null;
}, $escapedPattern);
return $reverseMatchingRegExp;
}
/**
* Check if the first char of a string is a single quote.
*
* @param string $quoteMatch The string to check
*
* @return bool true if matches, false otherwise
*/
public function isQuoteMatch($quoteMatch)
{
return "'" === $quoteMatch[0];
}
/**
* Replaces single quotes at the start or end of a string with two single quotes.
*
* @param string $quoteMatch The string to replace the quotes
*
* @return string A string with the single quotes replaced
*/
public function replaceQuoteMatch($quoteMatch)
{
if (preg_match("/^'+$/", $quoteMatch)) {
return str_replace("''", "'", $quoteMatch);
}
return str_replace("''", "'", substr($quoteMatch, 1, -1));
}
/**
* Builds a chars match regular expression.
*
* @param string $specialChars A string of chars to build the regular expression
*
* @return string The chars match regular expression
*/
protected function buildCharsMatch($specialChars)
{
$specialCharsArray = str_split($specialChars);
$specialCharsMatch = implode('|', array_map(function ($char) {
return $char.'+';
}, $specialCharsArray));
return $specialCharsMatch;
}
/**
* Normalize a preg_replace match array, removing the numeric keys and returning an associative array
* with the value and pattern values for the matched Transformer.
*
* @return array
*/
protected function normalizeArray(array $data)
{
$ret = [];
foreach ($data as $key => $value) {
if (!\is_string($key)) {
continue;
}
$ret[$key[0]] = [
'value' => $value,
'pattern' => $key,
];
}
return $ret;
}
/**
* Calculates the Unix timestamp based on the matched values by the reverse matching regular
* expression of parse().
*
* @param \DateTime $dateTime The DateTime object to be used to calculate the timestamp
* @param array $options An array with the matched values to be used to calculate the timestamp
*
* @return bool|int The calculated timestamp or false if matched date is invalid
*/
protected function calculateUnixTimestamp(\DateTime $dateTime, array $options)
{
$options = $this->getDefaultValueForOptions($options);
$year = $options['year'];
$month = $options['month'];
$day = $options['day'];
$hour = $options['hour'];
$hourInstance = $options['hourInstance'];
$minute = $options['minute'];
$second = $options['second'];
$marker = $options['marker'];
$timezone = $options['timezone'];
// If month is false, return immediately (intl behavior)
if (false === $month) {
IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Date parsing failed');
return false;
}
// Normalize hour
if ($hourInstance instanceof HourTransformer) {
$hour = $hourInstance->normalizeHour($hour, $marker);
}
// Set the timezone if different from the default one
if (null !== $timezone && $timezone !== $this->timezone) {
$dateTime->setTimezone(new \DateTimeZone($timezone));
}
// Normalize yy year
preg_match_all($this->regExp, $this->pattern, $matches);
if (\in_array('yy', $matches[0])) {
$dateTime->setTimestamp(time());
$year = $year > (int) $dateTime->format('y') + 20 ? 1900 + $year : 2000 + $year;
}
$dateTime->setDate($year, $month, $day);
$dateTime->setTime($hour, $minute, $second);
return $dateTime->getTimestamp();
}
/**
* Add sensible default values for missing items in the extracted date/time options array. The values
* are base in the beginning of the Unix era.
*
* @return array
*/
private function getDefaultValueForOptions(array $options)
{
return [
'year' => isset($options['year']) ? $options['year'] : 1970,
'month' => isset($options['month']) ? $options['month'] : 1,
'day' => isset($options['day']) ? $options['day'] : 1,
'hour' => isset($options['hour']) ? $options['hour'] : 0,
'hourInstance' => isset($options['hourInstance']) ? $options['hourInstance'] : null,
'minute' => isset($options['minute']) ? $options['minute'] : 0,
'second' => isset($options['second']) ? $options['second'] : 0,
'marker' => isset($options['marker']) ? $options['marker'] : null,
'timezone' => isset($options['timezone']) ? $options['timezone'] : null,
];
}
}

View File

@@ -0,0 +1,64 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for 12 hour format (0-11).
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class Hour1200Transformer extends HourTransformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$hourOfDay = $dateTime->format('g');
$hourOfDay = '12' === $hourOfDay ? '0' : $hourOfDay;
return $this->padLeft($hourOfDay, $length);
}
/**
* {@inheritdoc}
*/
public function normalizeHour($hour, $marker = null)
{
if ('PM' === $marker) {
$hour += 12;
}
return $hour;
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return '\d{1,2}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'hour' => (int) $matched,
'hourInstance' => $this,
];
}
}

View File

@@ -0,0 +1,64 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for 12 hour format (1-12).
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class Hour1201Transformer extends HourTransformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
return $this->padLeft($dateTime->format('g'), $length);
}
/**
* {@inheritdoc}
*/
public function normalizeHour($hour, $marker = null)
{
if ('PM' !== $marker && 12 === $hour) {
$hour = 0;
} elseif ('PM' === $marker && 12 !== $hour) {
// If PM and hour is not 12 (1-12), sum 12 hour
$hour += 12;
}
return $hour;
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return '\d{1,2}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'hour' => (int) $matched,
'hourInstance' => $this,
];
}
}

View File

@@ -0,0 +1,65 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for 24 hour format (0-23).
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class Hour2400Transformer extends HourTransformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
return $this->padLeft($dateTime->format('G'), $length);
}
/**
* {@inheritdoc}
*/
public function normalizeHour($hour, $marker = null)
{
$marker = (string) $marker;
if ('AM' === $marker) {
$hour = 0;
} elseif ('PM' === $marker) {
$hour = 12;
}
return $hour;
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return '\d{1,2}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'hour' => (int) $matched,
'hourInstance' => $this,
];
}
}

View File

@@ -0,0 +1,66 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for 24 hour format (1-24).
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class Hour2401Transformer extends HourTransformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$hourOfDay = $dateTime->format('G');
$hourOfDay = '0' === $hourOfDay ? '24' : $hourOfDay;
return $this->padLeft($hourOfDay, $length);
}
/**
* {@inheritdoc}
*/
public function normalizeHour($hour, $marker = null)
{
if ((null === $marker && 24 == $hour) || 'AM' == $marker) {
$hour = 0;
} elseif ('PM' == $marker) {
$hour = 12;
}
return $hour;
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return '\d{1,2}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'hour' => (int) $matched,
'hourInstance' => $this,
];
}
}

View File

@@ -0,0 +1,32 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Base class for hour transformers.
*
* @author Eriksen Costa <eriksen.costa@infranology.com.br>
*
* @internal
*/
abstract class HourTransformer extends Transformer
{
/**
* Returns a normalized hour value suitable for the hour transformer type.
*
* @param int $hour The hour value
* @param string $marker An optional AM/PM marker
*
* @return int The normalized hour value
*/
abstract public function normalizeHour($hour, $marker = null);
}

View File

@@ -0,0 +1,50 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for minute format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class MinuteTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$minuteOfHour = (int) $dateTime->format('i');
return $this->padLeft($minuteOfHour, $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'minute' => (int) $matched,
];
}
}

View File

@@ -0,0 +1,136 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for month format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class MonthTransformer extends Transformer
{
protected static $months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
/**
* Short months names (first 3 letters).
*/
protected static $shortMonths = [];
/**
* Flipped $months array, $name => $index.
*/
protected static $flippedMonths = [];
/**
* Flipped $shortMonths array, $name => $index.
*/
protected static $flippedShortMonths = [];
public function __construct()
{
if (0 === \count(self::$shortMonths)) {
self::$shortMonths = array_map(function ($month) {
return substr($month, 0, 3);
}, self::$months);
self::$flippedMonths = array_flip(self::$months);
self::$flippedShortMonths = array_flip(self::$shortMonths);
}
}
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$matchLengthMap = [
1 => 'n',
2 => 'm',
3 => 'M',
4 => 'F',
];
if (isset($matchLengthMap[$length])) {
return $dateTime->format($matchLengthMap[$length]);
}
if (5 === $length) {
return substr($dateTime->format('M'), 0, 1);
}
return $this->padLeft($dateTime->format('m'), $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
switch ($length) {
case 1:
$regExp = '\d{1,2}';
break;
case 3:
$regExp = implode('|', self::$shortMonths);
break;
case 4:
$regExp = implode('|', self::$months);
break;
case 5:
$regExp = '[JFMASOND]';
break;
default:
$regExp = '\d{1,'.$length.'}';
break;
}
return $regExp;
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
if (!is_numeric($matched)) {
if (3 === $length) {
$matched = self::$flippedShortMonths[$matched] + 1;
} elseif (4 === $length) {
$matched = self::$flippedMonths[$matched] + 1;
} elseif (5 === $length) {
// IntlDateFormatter::parse() always returns false for MMMMM or LLLLL
$matched = false;
}
} else {
$matched = (int) $matched;
}
return [
'month' => $matched,
];
}
}

View File

@@ -0,0 +1,66 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for quarter format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class QuarterTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$month = (int) $dateTime->format('n');
$quarter = (int) floor(($month - 1) / 3) + 1;
switch ($length) {
case 1:
case 2:
return $this->padLeft($quarter, $length);
case 3:
return 'Q'.$quarter;
default:
$map = [1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter'];
return $map[$quarter];
}
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
switch ($length) {
case 1:
case 2:
return '\d{'.$length.'}';
case 3:
return 'Q\d';
default:
return '(?:1st|2nd|3rd|4th) quarter';
}
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [];
}
}

View File

@@ -0,0 +1,50 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for the second format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class SecondTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
$secondOfMinute = (int) $dateTime->format('s');
return $this->padLeft($secondOfMinute, $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'second' => (int) $matched,
];
}
}

View File

@@ -0,0 +1,116 @@
<?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\Component\Intl\DateFormatter\DateFormat;
use Symfony\Component\Intl\Exception\NotImplementedException;
/**
* Parser and formatter for time zone format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class TimezoneTransformer extends Transformer
{
/**
* {@inheritdoc}
*
* @throws NotImplementedException When time zone is different than UTC or GMT (Etc/GMT)
*/
public function format(\DateTime $dateTime, $length)
{
$timeZone = substr($dateTime->getTimezone()->getName(), 0, 3);
if (!\in_array($timeZone, ['Etc', 'UTC', 'GMT'])) {
throw new NotImplementedException('Time zone different than GMT or UTC is not supported as a formatting output.');
}
if ('Etc' === $timeZone) {
// i.e. Etc/GMT+1, Etc/UTC, Etc/Zulu
$timeZone = substr($dateTime->getTimezone()->getName(), 4);
}
// From ICU >= 59.1 GMT and UTC are no longer unified
if (\in_array($timeZone, ['UTC', 'UCT', 'Universal', 'Zulu'])) {
// offset is not supported with UTC
return $length > 3 ? 'Coordinated Universal Time' : 'UTC';
}
$offset = (int) $dateTime->format('O');
// From ICU >= 4.8, the zero offset is no more used, example: GMT instead of GMT+00:00
if (0 === $offset) {
return $length > 3 ? 'Greenwich Mean Time' : 'GMT';
}
if ($length > 3) {
return $dateTime->format('\G\M\TP');
}
return sprintf('GMT%s%d', ($offset >= 0 ? '+' : ''), $offset / 100);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 'GMT[+-]\d{2}:?\d{2}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'timezone' => self::getEtcTimeZoneId($matched),
];
}
/**
* Get an Etc/GMT timezone identifier for the specified timezone.
*
* The PHP documentation for timezones states to not use the 'Other' time zones because them exists
* "for backwards compatibility". However all Etc/GMT time zones are in the tz database 'etcetera' file,
* which indicates they are not deprecated (neither are old names).
*
* Only GMT, Etc/Universal, Etc/Zulu, Etc/Greenwich, Etc/GMT-0, Etc/GMT+0 and Etc/GMT0 are old names and
* are linked to Etc/GMT or Etc/UTC.
*
* @param string $formattedTimeZone A GMT timezone string (GMT-03:00, e.g.)
*
* @return string A timezone identifier
*
* @see https://php.net/timezones.others
*
* @throws NotImplementedException When the GMT time zone have minutes offset different than zero
* @throws \InvalidArgumentException When the value can not be matched with pattern
*/
public static function getEtcTimeZoneId($formattedTimeZone)
{
if (preg_match('/GMT(?P<signal>[+-])(?P<hours>\d{2}):?(?P<minutes>\d{2})/', $formattedTimeZone, $matches)) {
$hours = (int) $matches['hours'];
$minutes = (int) $matches['minutes'];
$signal = '-' === $matches['signal'] ? '+' : '-';
if (0 < $minutes) {
throw new NotImplementedException(sprintf('It is not possible to use a GMT time zone with minutes offset different than zero (0). GMT time zone tried: "%s".', $formattedTimeZone));
}
return 'Etc/GMT'.(0 !== $hours ? $signal.$hours : '');
}
throw new \InvalidArgumentException(sprintf('The GMT time zone "%s" does not match with the supported formats GMT[+-]HH:MM or GMT[+-]HHMM.', $formattedTimeZone));
}
}

View File

@@ -0,0 +1,65 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for date formats.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
abstract class Transformer
{
/**
* Format a value using a configured DateTime as date/time source.
*
* @param \DateTime $dateTime A DateTime object to be used to generate the formatted value
* @param int $length The formatted value string length
*
* @return string The formatted value
*/
abstract public function format(\DateTime $dateTime, $length);
/**
* Returns a reverse matching regular expression of a string generated by format().
*
* @param int $length The length of the value to be reverse matched
*
* @return string The reverse matching regular expression
*/
abstract public function getReverseMatchingRegExp($length);
/**
* Extract date options from a matched value returned by the processing of the reverse matching
* regular expression.
*
* @param string $matched The matched value
* @param int $length The length of the Transformer pattern string
*
* @return array An associative array
*/
abstract public function extractDateOptions($matched, $length);
/**
* Pad a string with zeros to the left.
*
* @param string $value The string to be padded
* @param int $length The length to pad
*
* @return string The padded string
*/
protected function padLeft($value, $length)
{
return str_pad($value, $length, '0', \STR_PAD_LEFT);
}
}

View File

@@ -0,0 +1,52 @@
<?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\Component\Intl\DateFormatter\DateFormat;
/**
* Parser and formatter for year format.
*
* @author Igor Wiedler <igor@wiedler.ch>
*
* @internal
*/
class YearTransformer extends Transformer
{
/**
* {@inheritdoc}
*/
public function format(\DateTime $dateTime, $length)
{
if (2 === $length) {
return $dateTime->format('y');
}
return $this->padLeft($dateTime->format('Y'), $length);
}
/**
* {@inheritdoc}
*/
public function getReverseMatchingRegExp($length)
{
return 2 === $length ? '\d{2}' : '\d{1,4}';
}
/**
* {@inheritdoc}
*/
public function extractDateOptions($matched, $length)
{
return [
'year' => (int) $matched,
];
}
}

View File

@@ -0,0 +1,613 @@
<?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\Component\Intl\DateFormatter;
use Symfony\Component\Intl\DateFormatter\DateFormat\FullTransformer;
use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException;
use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException;
use Symfony\Component\Intl\Exception\MethodNotImplementedException;
use Symfony\Component\Intl\Globals\IntlGlobals;
use Symfony\Component\Intl\Locale\Locale;
/**
* 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
*/
class IntlDateFormatter
{
/**
* The error code from the last operation.
*
* @var int
*/
protected $errorCode = IntlGlobals::U_ZERO_ERROR;
/**
* The error message from the last operation.
*
* @var string
*/
protected $errorMessage = 'U_ZERO_ERROR';
/* date/time format types */
const NONE = -1;
const FULL = 0;
const LONG = 1;
const MEDIUM = 2;
const SHORT = 3;
/* calendar formats */
const TRADITIONAL = 0;
const GREGORIAN = 1;
/**
* Patterns used to format the date when no pattern is provided.
*/
private $defaultDateFormats = [
self::NONE => '',
self::FULL => 'EEEE, LLLL d, y',
self::LONG => 'LLLL d, y',
self::MEDIUM => 'LLL d, y',
self::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;
/**
* @param string|null $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en")
* @param int|null $datetype Type of date formatting, one of the format type constants
* @param int|null $timetype Type of time formatting, one of the format type constants
* @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier
* @param int $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")
* @param string|null $pattern Optional pattern to use when formatting
*
* @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($locale, $datetype, $timetype, $timezone = null, $calendar = self::GREGORIAN, $pattern = null)
{
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');
}
$this->datetype = null !== $datetype ? $datetype : self::FULL;
$this->timetype = null !== $timetype ? $timetype : self::FULL;
$this->setPattern($pattern);
$this->setTimeZone($timezone);
}
/**
* 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 int|null $datetype Type of date formatting, one of the format type constants
* @param int|null $timetype Type of time formatting, one of the format type constants
* @param \IntlTimeZone|\DateTimeZone|string|null $timezone Timezone identifier
* @param int $calendar Calendar to use for formatting or parsing; default is Gregorian
* One of the calendar constants
* @param string|null $pattern Optional pattern to use when formatting
*
* @return self
*
* @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($locale, $datetype, $timetype, $timezone = null, $calendar = self::GREGORIAN, $pattern = null)
{
return new self($locale, $datetype, $timetype, $timezone, $calendar, $pattern);
}
/**
* Format the date/time value (timestamp) as a string.
*
* @param int|\DateTimeInterface $timestamp 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($timestamp)
{
// intl allows timestamps to be passed as arrays - we don't
if (\is_array($timestamp)) {
$message = 'Only integer Unix timestamps and DateTime objects are supported';
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'timestamp', $timestamp, $message);
}
// behave like the intl extension
$argumentError = null;
if (!\is_int($timestamp) && !$timestamp instanceof \DateTimeInterface) {
$argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $timestamp);
}
if (null !== $argumentError) {
IntlGlobals::setError(IntlGlobals::U_ILLEGAL_ARGUMENT_ERROR, $argumentError);
$this->errorCode = IntlGlobals::getErrorCode();
$this->errorMessage = IntlGlobals::getErrorMessage();
return false;
}
if ($timestamp instanceof \DateTimeInterface) {
$timestamp = $timestamp->getTimestamp();
}
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
$formatted = $transformer->format($this->createDateTime($timestamp));
// behave like the intl extension
IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR);
$this->errorCode = IntlGlobals::getErrorCode();
$this->errorMessage = IntlGlobals::getErrorMessage();
return $formatted;
}
/**
* Not supported. Formats an object.
*
* @param object $object
* @param mixed $format
* @param string $locale
*
* @return string The formatted value
*
* @see https://php.net/intldateformatter.formatobject
*
* @throws MethodNotImplementedException
*/
public function formatObject($object, $format = null, $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($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.
*
* @param string $value String to convert to a time value
* @param int $position Position at which to start the parsing in $value (zero-based)
* If no error occurs before $value is consumed, $parse_pos will
* contain -1 otherwise it will contain the position at which parsing
* ended. If $parse_pos > strlen($value), the parse fails immediately.
*
* @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($value, &$position = 0)
{
throw new MethodNotImplementedException(__METHOD__);
}
/**
* Parse string to a timestamp value.
*
* @param string $value String to convert to a time value
* @param int $position Not supported. Position at which to start the parsing in $value (zero-based)
* If no error occurs before $value is consumed, $parse_pos will
* contain -1 otherwise it will contain the position at which parsing
* ended. If $parse_pos > strlen($value), the parse fails immediately.
*
* @return int|false Parsed value as a timestamp
*
* @see https://php.net/intldateformatter.parse
*
* @throws MethodArgumentNotImplementedException When $position different than null, behavior not implemented
*/
public function parse($value, &$position = null)
{
// We don't calculate the position when parsing the value
if (null !== $position) {
throw new MethodArgumentNotImplementedException(__METHOD__, 'position');
}
$dateTime = $this->createDateTime(0);
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
$timestamp = $transformer->parse($dateTime, $value);
// behave like the intl extension. FullTransformer::parse() set the proper error
$this->errorCode = IntlGlobals::getErrorCode();
$this->errorMessage = IntlGlobals::getErrorMessage();
return $timestamp;
}
/**
* Not supported. Set the formatter's calendar.
*
* @param string $calendar The calendar to use. Default is IntlDateFormatter::GREGORIAN
*
* @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($lenient)
{
if ($lenient) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'lenient', $lenient, 'Only the strict parser is supported');
}
return true;
}
/**
* Set the formatter's pattern.
*
* @param string|null $pattern A pattern string in conformance with the ICU IntlDateFormatter documentation
*
* @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($pattern)
{
if (null === $pattern) {
$pattern = $this->getDefaultPattern();
}
$this->pattern = $pattern;
return true;
}
/**
* Set the formatter's timezone identifier.
*
* @param string|null $timeZoneId The time zone ID string of the time zone to use.
* If NULL or the empty string, the default time zone for the
* runtime is used.
*
* @return bool true on success or false on failure
*
* @see https://php.net/intldateformatter.settimezoneid
*/
public function setTimeZoneId($timeZoneId)
{
if (null === $timeZoneId) {
$timeZoneId = date_default_timezone_get();
$this->uninitializedTimeZoneId = true;
}
// Backup original passed time zone
$timeZone = $timeZoneId;
// Get an Etc/GMT time zone that is accepted for \DateTimeZone
if ('GMT' !== $timeZoneId && 0 === strpos($timeZoneId, 'GMT')) {
try {
$timeZoneId = DateFormat\TimezoneTransformer::getEtcTimeZoneId($timeZoneId);
} catch (\InvalidArgumentException $e) {
// Does nothing, will fallback to UTC
}
}
try {
$this->dateTimeZone = new \DateTimeZone($timeZoneId);
if ('GMT' !== $timeZoneId && $this->dateTimeZone->getName() !== $timeZoneId) {
$timeZone = $this->getTimeZoneId();
}
} catch (\Exception $e) {
$timeZoneId = $timeZone = $this->getTimeZoneId();
$this->dateTimeZone = new \DateTimeZone($timeZoneId);
}
$this->timeZoneId = $timeZone;
return true;
}
/**
* This method was added in PHP 5.5 as replacement for `setTimeZoneId()`.
*
* @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;
}
}
return $this->setTimeZoneId($timeZone);
}
/**
* Create and returns a DateTime object with the specified timestamp and with the
* current time zone.
*
* @param int $timestamp
*
* @return \DateTime
*/
protected function createDateTime($timestamp)
{
$dateTime = new \DateTime();
$dateTime->setTimestamp($timestamp);
$dateTime->setTimezone($this->dateTimeZone);
return $dateTime;
}
/**
* Returns a pattern string based in the datetype and timetype values.
*
* @return string
*/
protected function getDefaultPattern()
{
$patternParts = [];
if (self::NONE !== $this->datetype) {
$patternParts[] = $this->defaultDateFormats[$this->datetype];
}
if (self::NONE !== $this->timetype) {
$patternParts[] = $this->defaultTimeFormats[$this->timetype];
}
return implode(', ', $patternParts);
}
}