PHP DateTime::createFromFormat doesn't parse ISO 8601 date time

PhpDatetime

Php Problem Overview


Code speaks a million words:

php > echo strtotime("2010-12-07T23:00:00.000Z");
1291762800
echo date('c', 1291762800);
2010-12-08T00:00:00+01:00
php > var_dump(DateTime::createFromFormat('c', "2010-12-07T23:00:00.000Z"));
bool(false)
php > var_dump(DateTime::createFromFormat(DateTime::ISO8601, "2010-12-07T23:00:00.000Z"));
bool(false)

Any idea what's going on?

Btw, yes, new DateTime("2010-12-07T23:00:00.000Z") works fine. But I prefer to know what input I am getting.

Php Solutions


Solution 1 - Php

There's a bug report that exactly describes your problem :)

https://bugs.php.net/bug.php?id=51950

Since 2016-08-07, the bug report has been marked as "not a bug". You need to use strtotime or new DateTime instead.

The constants that have been defined apply to both formatting and parsing in the same way, which forces your ways.

Solution 2 - Php

Parsing ISO8601 date, and also switching timezone:

// create ISO8601 dateTime 
$date = DateTime::createFromFormat(DateTime::ISO8601, '2016-07-27T19:30:00Z');

// set to user's timezone
$date -> setTimeZone('Asia/Singapore');

echo $date -> format(DateTime::ISO8601);
// prints '2016-07-28T03:30:00+0800'

Solution 3 - Php

Nobody mentioned to use DATE_ATOM which is as far as i know phps most correct implementation of ISO 8601. It should at least work for the last 3 of these:

<?php

$dates = array(
    "2010-12-07T23:00:00.000Z",
    "2010-12-07T23:00:00",
    "2010-12-07T23:00:00Z",
    "2010-12-07T23:00:00+01:00",
    (new \DateTime("now"))->format(DATE_ATOM)
);

foreach($dates as $d) {

    $res = \DateTime::createFromFormat(DATE_ATOM, $d);

    echo "try $d: \n";
    var_dump($res);
    echo "\n\n";
}

?>

To be able to parse all of them i wrote a tiny function:

<?php

function parse_iso_8601($iso_8601_string) {
    $results = array();
    $results[] = \DateTime::createFromFormat("Y-m-d\TH:i:s",$iso_8601_string);
    $results[] = \DateTime::createFromFormat("Y-m-d\TH:i:s.u",$iso_8601_string);
    $results[] = \DateTime::createFromFormat("Y-m-d\TH:i:s.uP",$iso_8601_string);
    $results[] = \DateTime::createFromFormat("Y-m-d\TH:i:sP",$iso_8601_string);
    $results[] = \DateTime::createFromFormat(DATE_ATOM,$iso_8601_string);

    $success = array_values(array_filter($results));
    if(count($success) > 0) {
        return $success[0];
    }
    return false;
}

// Test
$dates = array(
    "2010-12-07T23:00:00.000Z",
    "2010-12-07T23:00:00",
    "2010-12-07T23:00:00Z",
    "2010-12-07T23:00:00+01:00",
    (new \DateTime("now"))->format(DATE_ATOM)
);

foreach($dates as $d) {

    $res = parse_iso_8601($d);

    echo "try $d: \n";
    var_dump($res);
    echo "\n\n";
}

?>

As @Glutexo mentioned it works only if there are only 1 to 6 precision digits for the decimal part, too. Feel free to improve it.

Solution 4 - Php

try this:

DateTime::createFromFormat('Y-m-d\TH:i:sP', $date)

Solution 5 - Php

It is very strange and disappointing that this bug is still actual. Here is a right pattern for parsing date with microseconds in decimal part of seconds:

Y-m-d\TH:i:s.uO

Usage:

$dateStr = '2015-04-29T11:42:56.000+0400'
$ISO = 'Y-m-d\TH:i:s.uO'
$date = DateTime::createFromFormat($ISO, $dateStr)

Solution 6 - Php

Simply :

$dt = new DateTime('2018-04-07T16:32:44Z');
$dt->format('Ymd'); // 20180407

Solution 7 - Php

Use DATE_ATOM rather than 'c' when formatting like @Steven said. This is how you work with ISO 8601 in PHP.

<?php
$now_date = new DateTime();
$now_iso_8601 = $now_date->format(DATE_ATOM);
echo "Now in ISO 8601 format: {$now_iso_8601}\n";
$date_from_string_and_format = date_create_from_format(DATE_ATOM, $now_iso_8601);
echo "ISO 8601 formatted string, back to DateTime object:\n";
var_dump($date_from_string_and_format);

prints

Now in ISO 8601 format: 2018-09-05T08:17:35-10:00
ISO 8601 formatted string, back to DateTime object:
object(DateTime)#2 (3) {
  ["date"]=>
  string(26) "2018-09-05 08:17:35.000000"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "-10:00"
}

Solution 8 - Php

For the answer listed here https://stackoverflow.com/a/14849503/2425651 we can use this format "Y-m-d\TH: i: s.u+" to keep the microseconds.

$format = 'Y-m-d\TH:i:s.u+';
$value = '2017-09-21T10:11:19.026Z'; // jsDate.toUTCString();
var_dump(\DateTime::createFromFormat($format, $value));

Solution 9 - Php

This one works for me:

$date = (new DateTime)->setTimestamp(strtotime('2017-12-31T23:00:00.000Z'));

Solution 10 - Php

I've experienced this issue with POSTGRES default Time with timezone format and this was the format that fixed it for me:

Y-m-d H:i:s.uO

Solution 11 - Php

This works for me:

$timeStamp = "2020-12-10T14:54:25.618Z";
var_dump(DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $timeStamp));
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2020-12-10 14:54:25.618000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(3) "UTC"
}

Solution 12 - Php

I am using follow function that allow multiple ISO8601 formats:

function fromISO8601($time, \DateTimeZone $timezone = null) {
	// valid ISO time 2019-04-01T00:00:00.000+02:00
	$t = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $time) or
	// ISO time without millis 2019-04-01T00:00:00+02:00
	$t = \DateTime::createFromFormat('Y-m-d\TH:i:sO', $time) or
	// ISO time without timezone 2019-04-01T00:00:00.000
	$t = \DateTime::createFromFormat('Y-m-d\TH:i:s.u', $time, $timezone) or
	// ISO time without millis and timezone 2019-04-01T00:00:00.000+02:00
	$t = \DateTime::createFromFormat('Y-m-d\TH:i:s', $time, $timezone);

	return $t;
}

here are all supported dates

var_dump(
    fromISO8601('2019-04-01T00:00:00.000+02:00'),
    fromISO8601('2019-04-01T00:00:00+02:00'),
    fromISO8601('2019-04-01T00:00:00.000'),
    fromISO8601('2019-04-01T00:00:00')
);

This code is benevolent for missing timezone and milliseconds and works in older php versions.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJakeView Question on Stackoverflow
Solution 1 - PhpJa͢ckView Answer on Stackoverflow
Solution 2 - Phpa20View Answer on Stackoverflow
Solution 3 - PhpstevenView Answer on Stackoverflow
Solution 4 - PhpEtienneView Answer on Stackoverflow
Solution 5 - PhpDmitry DavydovView Answer on Stackoverflow
Solution 6 - PhpstlocView Answer on Stackoverflow
Solution 7 - PhpJohn ErckView Answer on Stackoverflow
Solution 8 - PhpDzung NguyenView Answer on Stackoverflow
Solution 9 - PhprckdView Answer on Stackoverflow
Solution 10 - PhpSasa BlagojevicView Answer on Stackoverflow
Solution 11 - PhpNitrusCSView Answer on Stackoverflow
Solution 12 - PhpOzzyCzechView Answer on Stackoverflow