Skip to content

Commit 7e1c6c4

Browse files
committed
[Yaml] support to parse and dump DateTime objects
1 parent 3e9c268 commit 7e1c6c4

File tree

4 files changed

+88
-18
lines changed

4 files changed

+88
-18
lines changed

src/Symfony/Component/Yaml/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ CHANGELOG
44
3.1.0
55
-----
66

7+
* Added support for parsing timestamps as `\DateTime` objects:
8+
9+
```php
10+
Yaml::parse('2001-12-15 21:59:43.10 -5', Yaml::PARSE_DATETIME);
11+
```
12+
13+
* `\DateTime` and `\DateTimeImmutable` objects are dumped as YAML timestamps.
14+
715
* Deprecated usage of `%` at the beginning of an unquoted string.
816

917
* Added support for customizing the YAML parser behavior through an optional bit field:

src/Symfony/Component/Yaml/Inline.php

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,15 @@ public static function parse($value, $flags = 0, $references = array())
9292
$i = 0;
9393
switch ($value[0]) {
9494
case '[':
95-
$result = self::parseSequence($value, $i, $references);
95+
$result = self::parseSequence($value, $flags, $i, $references);
9696
++$i;
9797
break;
9898
case '{':
99-
$result = self::parseMapping($value, $i, $references);
99+
$result = self::parseMapping($value, $flags, $i, $references);
100100
++$i;
101101
break;
102102
default:
103-
$result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
103+
$result = self::parseScalar($value, $flags, null, array('"', "'"), $i, true, $references);
104104
}
105105

106106
// some comments are allowed at the end
@@ -152,6 +152,8 @@ public static function dump($value, $flags = 0)
152152
}
153153

154154
return 'null';
155+
case $value instanceof \DateTimeInterface:
156+
return $value->format('c');
155157
case is_object($value):
156158
if (Yaml::DUMP_OBJECT & $flags) {
157159
return '!php/object:'.serialize($value);
@@ -243,6 +245,7 @@ private static function dumpArray($value, $flags)
243245
* Parses a scalar to a YAML string.
244246
*
245247
* @param string $scalar
248+
* @param int $flags
246249
* @param string $delimiters
247250
* @param array $stringDelimiters
248251
* @param int &$i
@@ -255,7 +258,7 @@ private static function dumpArray($value, $flags)
255258
*
256259
* @internal
257260
*/
258-
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
261+
public static function parseScalar($scalar, $flags = 0, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
259262
{
260263
if (in_array($scalar[$i], $stringDelimiters)) {
261264
// quoted scalar
@@ -294,7 +297,7 @@ public static function parseScalar($scalar, $delimiters = null, $stringDelimiter
294297
}
295298

296299
if ($evaluate) {
297-
$output = self::evaluateScalar($output, $references);
300+
$output = self::evaluateScalar($output, $flags, $references);
298301
}
299302
}
300303

@@ -335,14 +338,15 @@ private static function parseQuotedScalar($scalar, &$i)
335338
* Parses a sequence to a YAML string.
336339
*
337340
* @param string $sequence
341+
* @param int $flags
338342
* @param int &$i
339343
* @param array $references
340344
*
341345
* @return string A YAML string
342346
*
343347
* @throws ParseException When malformed inline YAML string is parsed
344348
*/
345-
private static function parseSequence($sequence, &$i = 0, $references = array())
349+
private static function parseSequence($sequence, $flags, &$i = 0, $references = array())
346350
{
347351
$output = array();
348352
$len = strlen($sequence);
@@ -353,11 +357,11 @@ private static function parseSequence($sequence, &$i = 0, $references = array())
353357
switch ($sequence[$i]) {
354358
case '[':
355359
// nested sequence
356-
$output[] = self::parseSequence($sequence, $i, $references);
360+
$output[] = self::parseSequence($sequence, $flags, $i, $references);
357361
break;
358362
case '{':
359363
// nested mapping
360-
$output[] = self::parseMapping($sequence, $i, $references);
364+
$output[] = self::parseMapping($sequence, $flags, $i, $references);
361365
break;
362366
case ']':
363367
return $output;
@@ -366,14 +370,14 @@ private static function parseSequence($sequence, &$i = 0, $references = array())
366370
break;
367371
default:
368372
$isQuoted = in_array($sequence[$i], array('"', "'"));
369-
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
373+
$value = self::parseScalar($sequence, $flags, array(',', ']'), array('"', "'"), $i, true, $references);
370374

371375
// the value can be an array if a reference has been resolved to an array var
372376
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
373377
// embedded mapping?
374378
try {
375379
$pos = 0;
376-
$value = self::parseMapping('{'.$value.'}', $pos, $references);
380+
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
377381
} catch (\InvalidArgumentException $e) {
378382
// no, it's not
379383
}
@@ -394,14 +398,15 @@ private static function parseSequence($sequence, &$i = 0, $references = array())
394398
* Parses a mapping to a YAML string.
395399
*
396400
* @param string $mapping
401+
* @param int $flags
397402
* @param int &$i
398403
* @param array $references
399404
*
400405
* @return string A YAML string
401406
*
402407
* @throws ParseException When malformed inline YAML string is parsed
403408
*/
404-
private static function parseMapping($mapping, &$i = 0, $references = array())
409+
private static function parseMapping($mapping, $flags, &$i = 0, $references = array())
405410
{
406411
$output = array();
407412
$len = strlen($mapping);
@@ -423,7 +428,7 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
423428
}
424429

425430
// key
426-
$key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
431+
$key = self::parseScalar($mapping, $flags, array(':', ' '), array('"', "'"), $i, false);
427432

428433
// value
429434
$done = false;
@@ -432,7 +437,7 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
432437
switch ($mapping[$i]) {
433438
case '[':
434439
// nested sequence
435-
$value = self::parseSequence($mapping, $i, $references);
440+
$value = self::parseSequence($mapping, $flags, $i, $references);
436441
// Spec: Keys MUST be unique; first one wins.
437442
// Parser cannot abort this mapping earlier, since lines
438443
// are processed sequentially.
@@ -443,7 +448,7 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
443448
break;
444449
case '{':
445450
// nested mapping
446-
$value = self::parseMapping($mapping, $i, $references);
451+
$value = self::parseMapping($mapping, $flags, $i, $references);
447452
// Spec: Keys MUST be unique; first one wins.
448453
// Parser cannot abort this mapping earlier, since lines
449454
// are processed sequentially.
@@ -456,7 +461,7 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
456461
case ' ':
457462
break;
458463
default:
459-
$value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
464+
$value = self::parseScalar($mapping, $flags, array(',', '}'), array('"', "'"), $i, true, $references);
460465
// Spec: Keys MUST be unique; first one wins.
461466
// Parser cannot abort this mapping earlier, since lines
462467
// are processed sequentially.
@@ -482,13 +487,14 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
482487
* Evaluates scalars and replaces magic values.
483488
*
484489
* @param string $scalar
490+
* @param int $flags
485491
* @param array $references
486492
*
487493
* @return string A YAML string
488494
*
489495
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
490496
*/
491-
private static function evaluateScalar($scalar, $references = array())
497+
private static function evaluateScalar($scalar, $flags, $references = array())
492498
{
493499
$scalar = trim($scalar);
494500
$scalarLower = strtolower($scalar);
@@ -527,7 +533,7 @@ private static function evaluateScalar($scalar, $references = array())
527533
case 0 === strpos($scalar, '!str'):
528534
return (string) substr($scalar, 5);
529535
case 0 === strpos($scalar, '! '):
530-
return (int) self::parseScalar(substr($scalar, 2));
536+
return (int) self::parseScalar(substr($scalar, 2), $flags);
531537
case 0 === strpos($scalar, '!php/object:'):
532538
if (self::$objectSupport) {
533539
return unserialize(substr($scalar, 12));
@@ -573,6 +579,10 @@ private static function evaluateScalar($scalar, $references = array())
573579
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
574580
return (float) str_replace(',', '', $scalar);
575581
case preg_match(self::getTimestampRegex(), $scalar):
582+
if (Yaml::PARSE_DATETIME & $flags) {
583+
return new \DateTime($scalar,new \DateTimeZone('UTC'));
584+
}
585+
576586
$timeZone = date_default_timezone_get();
577587
date_default_timezone_set('UTC');
578588
$time = strtotime($scalar);

src/Symfony/Component/Yaml/Tests/InlineTest.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ public function getTestsForParseWithMapObjects()
373373
array("'#cfcfcf'", '#cfcfcf'),
374374
array('::form_base.html.twig', '::form_base.html.twig'),
375375

376-
array('2007-10-30', mktime(0, 0, 0, 10, 30, 2007)),
376+
array('2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)),
377377
array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
378378
array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
379379
array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
@@ -481,4 +481,55 @@ public function getTestsForDump()
481481
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')),
482482
);
483483
}
484+
485+
/**
486+
* @dataProvider getTimestampTests
487+
*/
488+
public function testParseTimestampAsUnixTimestampByDefault($yaml, $year, $month, $day, $hour, $minute, $second)
489+
{
490+
$this->assertSame(gmmktime($hour, $minute, $second, $month, $day, $year), Inline::parse($yaml));
491+
}
492+
493+
/**
494+
* @dataProvider getTimestampTests
495+
*/
496+
public function testParseTimestampAsDateTimeObject($yaml, $year, $month, $day, $hour, $minute, $second)
497+
{
498+
$expected = new \DateTime('now', new \DateTimeZone('UTC'));
499+
$expected->setDate($year, $month, $day);
500+
$expected->setTime($hour, $minute, $second);
501+
502+
$this->assertEquals($expected, Inline::parse($yaml, Yaml::PARSE_DATETIME));
503+
}
504+
505+
public function getTimestampTests()
506+
{
507+
return array(
508+
'canonical' => array('2001-12-15T02:59:43.1Z', 2001, 12, 15, 2, 59, 43),
509+
'ISO-8601' => array('2001-12-15t21:59:43.10-05:00', 2001, 12, 16, 2, 59, 43),
510+
'spaced' => array('2001-12-15 21:59:43.10 -5', 2001, 12, 16, 2, 59, 43),
511+
'date' => array('2001-12-15', 2001, 12, 15, 0, 0, 0),
512+
);
513+
}
514+
515+
/**
516+
* @dataProvider getDateTimeDumpTests
517+
*/
518+
public function testDumpDateTime($dateTime, $expected)
519+
{
520+
$this->assertSame($expected, Inline::dump($dateTime));
521+
}
522+
523+
public function getDateTimeDumpTests()
524+
{
525+
$tests = array();
526+
527+
$dateTime = new \DateTime('2001-12-15 21:59:43', new \DateTimeZone('UTC'));
528+
$tests['date-time-utc'] = array($dateTime, '2001-12-15T21:59:43+00:00');
529+
530+
$dateTime = new \DateTimeImmutable('2001-07-15 21:59:43', new \DateTimeZone('Europe/Berlin'));
531+
$tests['immutable-date-time-europe-berlin'] = array($dateTime, '2001-07-15T21:59:43+02:00');
532+
533+
return $tests;
534+
}
484535
}

src/Symfony/Component/Yaml/Yaml.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Yaml
2525
const PARSE_OBJECT = 4;
2626
const PARSE_OBJECT_FOR_MAP = 8;
2727
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
28+
const PARSE_DATETIME = 32;
2829

2930
/**
3031
* Parses YAML into a PHP value.

0 commit comments

Comments
 (0)