PHP DateTime::createFromFormat doesn't parse ISO 8601 date time
PhpDatetimePhp 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.