[Yaml] support to parse and dump DateTime objects
This commit is contained in:
parent
3e9c268f02
commit
7e1c6c4871
@ -4,6 +4,14 @@ CHANGELOG
|
|||||||
3.1.0
|
3.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* Added support for parsing timestamps as `\DateTime` objects:
|
||||||
|
|
||||||
|
```php
|
||||||
|
Yaml::parse('2001-12-15 21:59:43.10 -5', Yaml::PARSE_DATETIME);
|
||||||
|
```
|
||||||
|
|
||||||
|
* `\DateTime` and `\DateTimeImmutable` objects are dumped as YAML timestamps.
|
||||||
|
|
||||||
* Deprecated usage of `%` at the beginning of an unquoted string.
|
* Deprecated usage of `%` at the beginning of an unquoted string.
|
||||||
|
|
||||||
* Added support for customizing the YAML parser behavior through an optional bit field:
|
* Added support for customizing the YAML parser behavior through an optional bit field:
|
||||||
|
@ -92,15 +92,15 @@ class Inline
|
|||||||
$i = 0;
|
$i = 0;
|
||||||
switch ($value[0]) {
|
switch ($value[0]) {
|
||||||
case '[':
|
case '[':
|
||||||
$result = self::parseSequence($value, $i, $references);
|
$result = self::parseSequence($value, $flags, $i, $references);
|
||||||
++$i;
|
++$i;
|
||||||
break;
|
break;
|
||||||
case '{':
|
case '{':
|
||||||
$result = self::parseMapping($value, $i, $references);
|
$result = self::parseMapping($value, $flags, $i, $references);
|
||||||
++$i;
|
++$i;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
|
$result = self::parseScalar($value, $flags, null, array('"', "'"), $i, true, $references);
|
||||||
}
|
}
|
||||||
|
|
||||||
// some comments are allowed at the end
|
// some comments are allowed at the end
|
||||||
@ -152,6 +152,8 @@ class Inline
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 'null';
|
return 'null';
|
||||||
|
case $value instanceof \DateTimeInterface:
|
||||||
|
return $value->format('c');
|
||||||
case is_object($value):
|
case is_object($value):
|
||||||
if (Yaml::DUMP_OBJECT & $flags) {
|
if (Yaml::DUMP_OBJECT & $flags) {
|
||||||
return '!php/object:'.serialize($value);
|
return '!php/object:'.serialize($value);
|
||||||
@ -243,6 +245,7 @@ class Inline
|
|||||||
* Parses a scalar to a YAML string.
|
* Parses a scalar to a YAML string.
|
||||||
*
|
*
|
||||||
* @param string $scalar
|
* @param string $scalar
|
||||||
|
* @param int $flags
|
||||||
* @param string $delimiters
|
* @param string $delimiters
|
||||||
* @param array $stringDelimiters
|
* @param array $stringDelimiters
|
||||||
* @param int &$i
|
* @param int &$i
|
||||||
@ -255,7 +258,7 @@ class Inline
|
|||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
|
public static function parseScalar($scalar, $flags = 0, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
|
||||||
{
|
{
|
||||||
if (in_array($scalar[$i], $stringDelimiters)) {
|
if (in_array($scalar[$i], $stringDelimiters)) {
|
||||||
// quoted scalar
|
// quoted scalar
|
||||||
@ -294,7 +297,7 @@ class Inline
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($evaluate) {
|
if ($evaluate) {
|
||||||
$output = self::evaluateScalar($output, $references);
|
$output = self::evaluateScalar($output, $flags, $references);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,6 +338,7 @@ class Inline
|
|||||||
* Parses a sequence to a YAML string.
|
* Parses a sequence to a YAML string.
|
||||||
*
|
*
|
||||||
* @param string $sequence
|
* @param string $sequence
|
||||||
|
* @param int $flags
|
||||||
* @param int &$i
|
* @param int &$i
|
||||||
* @param array $references
|
* @param array $references
|
||||||
*
|
*
|
||||||
@ -342,7 +346,7 @@ class Inline
|
|||||||
*
|
*
|
||||||
* @throws ParseException When malformed inline YAML string is parsed
|
* @throws ParseException When malformed inline YAML string is parsed
|
||||||
*/
|
*/
|
||||||
private static function parseSequence($sequence, &$i = 0, $references = array())
|
private static function parseSequence($sequence, $flags, &$i = 0, $references = array())
|
||||||
{
|
{
|
||||||
$output = array();
|
$output = array();
|
||||||
$len = strlen($sequence);
|
$len = strlen($sequence);
|
||||||
@ -353,11 +357,11 @@ class Inline
|
|||||||
switch ($sequence[$i]) {
|
switch ($sequence[$i]) {
|
||||||
case '[':
|
case '[':
|
||||||
// nested sequence
|
// nested sequence
|
||||||
$output[] = self::parseSequence($sequence, $i, $references);
|
$output[] = self::parseSequence($sequence, $flags, $i, $references);
|
||||||
break;
|
break;
|
||||||
case '{':
|
case '{':
|
||||||
// nested mapping
|
// nested mapping
|
||||||
$output[] = self::parseMapping($sequence, $i, $references);
|
$output[] = self::parseMapping($sequence, $flags, $i, $references);
|
||||||
break;
|
break;
|
||||||
case ']':
|
case ']':
|
||||||
return $output;
|
return $output;
|
||||||
@ -366,14 +370,14 @@ class Inline
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$isQuoted = in_array($sequence[$i], array('"', "'"));
|
$isQuoted = in_array($sequence[$i], array('"', "'"));
|
||||||
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
|
$value = self::parseScalar($sequence, $flags, array(',', ']'), array('"', "'"), $i, true, $references);
|
||||||
|
|
||||||
// the value can be an array if a reference has been resolved to an array var
|
// the value can be an array if a reference has been resolved to an array var
|
||||||
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
|
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
|
||||||
// embedded mapping?
|
// embedded mapping?
|
||||||
try {
|
try {
|
||||||
$pos = 0;
|
$pos = 0;
|
||||||
$value = self::parseMapping('{'.$value.'}', $pos, $references);
|
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
// no, it's not
|
// no, it's not
|
||||||
}
|
}
|
||||||
@ -394,6 +398,7 @@ class Inline
|
|||||||
* Parses a mapping to a YAML string.
|
* Parses a mapping to a YAML string.
|
||||||
*
|
*
|
||||||
* @param string $mapping
|
* @param string $mapping
|
||||||
|
* @param int $flags
|
||||||
* @param int &$i
|
* @param int &$i
|
||||||
* @param array $references
|
* @param array $references
|
||||||
*
|
*
|
||||||
@ -401,7 +406,7 @@ class Inline
|
|||||||
*
|
*
|
||||||
* @throws ParseException When malformed inline YAML string is parsed
|
* @throws ParseException When malformed inline YAML string is parsed
|
||||||
*/
|
*/
|
||||||
private static function parseMapping($mapping, &$i = 0, $references = array())
|
private static function parseMapping($mapping, $flags, &$i = 0, $references = array())
|
||||||
{
|
{
|
||||||
$output = array();
|
$output = array();
|
||||||
$len = strlen($mapping);
|
$len = strlen($mapping);
|
||||||
@ -423,7 +428,7 @@ class Inline
|
|||||||
}
|
}
|
||||||
|
|
||||||
// key
|
// key
|
||||||
$key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
|
$key = self::parseScalar($mapping, $flags, array(':', ' '), array('"', "'"), $i, false);
|
||||||
|
|
||||||
// value
|
// value
|
||||||
$done = false;
|
$done = false;
|
||||||
@ -432,7 +437,7 @@ class Inline
|
|||||||
switch ($mapping[$i]) {
|
switch ($mapping[$i]) {
|
||||||
case '[':
|
case '[':
|
||||||
// nested sequence
|
// nested sequence
|
||||||
$value = self::parseSequence($mapping, $i, $references);
|
$value = self::parseSequence($mapping, $flags, $i, $references);
|
||||||
// Spec: Keys MUST be unique; first one wins.
|
// Spec: Keys MUST be unique; first one wins.
|
||||||
// Parser cannot abort this mapping earlier, since lines
|
// Parser cannot abort this mapping earlier, since lines
|
||||||
// are processed sequentially.
|
// are processed sequentially.
|
||||||
@ -443,7 +448,7 @@ class Inline
|
|||||||
break;
|
break;
|
||||||
case '{':
|
case '{':
|
||||||
// nested mapping
|
// nested mapping
|
||||||
$value = self::parseMapping($mapping, $i, $references);
|
$value = self::parseMapping($mapping, $flags, $i, $references);
|
||||||
// Spec: Keys MUST be unique; first one wins.
|
// Spec: Keys MUST be unique; first one wins.
|
||||||
// Parser cannot abort this mapping earlier, since lines
|
// Parser cannot abort this mapping earlier, since lines
|
||||||
// are processed sequentially.
|
// are processed sequentially.
|
||||||
@ -456,7 +461,7 @@ class Inline
|
|||||||
case ' ':
|
case ' ':
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
|
$value = self::parseScalar($mapping, $flags, array(',', '}'), array('"', "'"), $i, true, $references);
|
||||||
// Spec: Keys MUST be unique; first one wins.
|
// Spec: Keys MUST be unique; first one wins.
|
||||||
// Parser cannot abort this mapping earlier, since lines
|
// Parser cannot abort this mapping earlier, since lines
|
||||||
// are processed sequentially.
|
// are processed sequentially.
|
||||||
@ -482,13 +487,14 @@ class Inline
|
|||||||
* Evaluates scalars and replaces magic values.
|
* Evaluates scalars and replaces magic values.
|
||||||
*
|
*
|
||||||
* @param string $scalar
|
* @param string $scalar
|
||||||
|
* @param int $flags
|
||||||
* @param array $references
|
* @param array $references
|
||||||
*
|
*
|
||||||
* @return string A YAML string
|
* @return string A YAML string
|
||||||
*
|
*
|
||||||
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
|
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
|
||||||
*/
|
*/
|
||||||
private static function evaluateScalar($scalar, $references = array())
|
private static function evaluateScalar($scalar, $flags, $references = array())
|
||||||
{
|
{
|
||||||
$scalar = trim($scalar);
|
$scalar = trim($scalar);
|
||||||
$scalarLower = strtolower($scalar);
|
$scalarLower = strtolower($scalar);
|
||||||
@ -527,7 +533,7 @@ class Inline
|
|||||||
case 0 === strpos($scalar, '!str'):
|
case 0 === strpos($scalar, '!str'):
|
||||||
return (string) substr($scalar, 5);
|
return (string) substr($scalar, 5);
|
||||||
case 0 === strpos($scalar, '! '):
|
case 0 === strpos($scalar, '! '):
|
||||||
return (int) self::parseScalar(substr($scalar, 2));
|
return (int) self::parseScalar(substr($scalar, 2), $flags);
|
||||||
case 0 === strpos($scalar, '!php/object:'):
|
case 0 === strpos($scalar, '!php/object:'):
|
||||||
if (self::$objectSupport) {
|
if (self::$objectSupport) {
|
||||||
return unserialize(substr($scalar, 12));
|
return unserialize(substr($scalar, 12));
|
||||||
@ -573,6 +579,10 @@ class Inline
|
|||||||
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
|
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
|
||||||
return (float) str_replace(',', '', $scalar);
|
return (float) str_replace(',', '', $scalar);
|
||||||
case preg_match(self::getTimestampRegex(), $scalar):
|
case preg_match(self::getTimestampRegex(), $scalar):
|
||||||
|
if (Yaml::PARSE_DATETIME & $flags) {
|
||||||
|
return new \DateTime($scalar,new \DateTimeZone('UTC'));
|
||||||
|
}
|
||||||
|
|
||||||
$timeZone = date_default_timezone_get();
|
$timeZone = date_default_timezone_get();
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
$time = strtotime($scalar);
|
$time = strtotime($scalar);
|
||||||
|
@ -373,7 +373,7 @@ class InlineTest extends \PHPUnit_Framework_TestCase
|
|||||||
array("'#cfcfcf'", '#cfcfcf'),
|
array("'#cfcfcf'", '#cfcfcf'),
|
||||||
array('::form_base.html.twig', '::form_base.html.twig'),
|
array('::form_base.html.twig', '::form_base.html.twig'),
|
||||||
|
|
||||||
array('2007-10-30', mktime(0, 0, 0, 10, 30, 2007)),
|
array('2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)),
|
||||||
array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
|
array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
|
||||||
array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
|
array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
|
||||||
array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
|
array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
|
||||||
@ -481,4 +481,55 @@ class InlineTest extends \PHPUnit_Framework_TestCase
|
|||||||
array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')),
|
array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getTimestampTests
|
||||||
|
*/
|
||||||
|
public function testParseTimestampAsUnixTimestampByDefault($yaml, $year, $month, $day, $hour, $minute, $second)
|
||||||
|
{
|
||||||
|
$this->assertSame(gmmktime($hour, $minute, $second, $month, $day, $year), Inline::parse($yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getTimestampTests
|
||||||
|
*/
|
||||||
|
public function testParseTimestampAsDateTimeObject($yaml, $year, $month, $day, $hour, $minute, $second)
|
||||||
|
{
|
||||||
|
$expected = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||||
|
$expected->setDate($year, $month, $day);
|
||||||
|
$expected->setTime($hour, $minute, $second);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, Inline::parse($yaml, Yaml::PARSE_DATETIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimestampTests()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'canonical' => array('2001-12-15T02:59:43.1Z', 2001, 12, 15, 2, 59, 43),
|
||||||
|
'ISO-8601' => array('2001-12-15t21:59:43.10-05:00', 2001, 12, 16, 2, 59, 43),
|
||||||
|
'spaced' => array('2001-12-15 21:59:43.10 -5', 2001, 12, 16, 2, 59, 43),
|
||||||
|
'date' => array('2001-12-15', 2001, 12, 15, 0, 0, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getDateTimeDumpTests
|
||||||
|
*/
|
||||||
|
public function testDumpDateTime($dateTime, $expected)
|
||||||
|
{
|
||||||
|
$this->assertSame($expected, Inline::dump($dateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeDumpTests()
|
||||||
|
{
|
||||||
|
$tests = array();
|
||||||
|
|
||||||
|
$dateTime = new \DateTime('2001-12-15 21:59:43', new \DateTimeZone('UTC'));
|
||||||
|
$tests['date-time-utc'] = array($dateTime, '2001-12-15T21:59:43+00:00');
|
||||||
|
|
||||||
|
$dateTime = new \DateTimeImmutable('2001-07-15 21:59:43', new \DateTimeZone('Europe/Berlin'));
|
||||||
|
$tests['immutable-date-time-europe-berlin'] = array($dateTime, '2001-07-15T21:59:43+02:00');
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ class Yaml
|
|||||||
const PARSE_OBJECT = 4;
|
const PARSE_OBJECT = 4;
|
||||||
const PARSE_OBJECT_FOR_MAP = 8;
|
const PARSE_OBJECT_FOR_MAP = 8;
|
||||||
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
|
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
|
||||||
|
const PARSE_DATETIME = 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses YAML into a PHP value.
|
* Parses YAML into a PHP value.
|
||||||
|
Reference in New Issue
Block a user