57

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.

4
  • 3
    Your fractional seconds aren't part of the format. DateTime::ISO8601 is a string with the value Y-m-d\TH:i:sO. Commented Dec 10, 2010 at 17:53
  • 4
    WikiPedia disagrees: Decimal fractions may also be added to any of the three time elements. A decimal point, either a comma or a dot (without any preference as stated most recently in resolution 10 of the 22nd General Conference CGPM in 2003), is used as a separator between the time element and its fraction. A fraction may only be added to the lowest order time element in the representation. Commented Dec 10, 2010 at 18:07
  • public static DateTime DateTime::createFromFormat ( string $format , string $time [, DateTimeZone $timezone ] ), maybe is caused by inclusive of the timezone Commented Dec 10, 2010 at 18:11
  • 3
    Seems like "c" and DateTime::ISO8601 aren't the same either: php > var_dump(DateTime::createFromFormat('c', "2010-12-10T19:02:09+01:00")); bool(false) php > var_dump(DateTime::createFromFormat('c', '2010-12-10T19:02:09+01:00')); bool(false) php > var_dump(DateTime::createFromFormat(DateTime::ISO8601, '2010-12-10T19:02:09+01:00')); object(DateTime)#26 (3) { ["date"]=> string(19) "2010-12-10 19:02:09" ["timezone_type"]=> int(1) ["timezone"]=> string(6) "+01:00" } Commented Dec 10, 2010 at 18:20

12 Answers 12

48

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Indeed disappointing. Why name a constant "ISO8601" if it's not actually compliant to that standard. This is why I don't like PHP much.
Bloody 7 years later and this still exists.... and is even marked as not a bug? If you want to accept different ISO-8601 date/time formats. Different ISO-8601 formats?? Why I dislike php in a nutshell.
Feeling the pain too
TBF, within PHP's design constraints, this is indeed not a bug. createFromFormat does not support optional date parts. That would be stupid to implement as well IMO, because the format specifiers use a simple syntax and a naive parser. PHP would have to support something like this: Y-m-d\TH:i:s[?v]P where the square brackets mean optional. That then probably has to be translated internally to regex. I'll have the naive parser, thanks. If you really want microseconds and you know that's the format you'll receive, use Y-m-d\TH:i:s?vP. If you want multiple formats just use new DateTime
19

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'

5 Comments

copied your code and it prints: "2016-07-27T19:30:00+0000" using PHP 5.6.10
@AlexAngelico What is the output for echo DateTime::ISO8601;? It prints Y-m-d\TH:i:sO for me.
yes, DateTime::ISO8601 prints Y-m-d\TH:i:sO I'm using this code to change the time to the correct Timezone. I'm in GMT-5 $fecha_str = '2017-04-12T21:43:54+02:00'; $date = strtotime($fecha_str); echo date("Y-m-d h:i:s", $date); /output=> 2017-04-12 07:43:54
Just as a heads up, don't use format(\DateTime::ISO8601) because it is not a valid ISO8601 format. Instead use either c, \DateTime::ATOM or DATE_ATOM. The reason is because the \DateTime:ISO8601 way would wrongly stores as 2005-08-15T15:52:01+0000 when in actuality a valid ISO8601 date string should have a colon between the time zone designator like 2005-08-15T15:52:01+00:00. This is mentioned in the PHP docs but is not immediately clear. Read the docs here: ca3.php.net/manual/en/…
Neither \DateTime::ISO8601 nor \DateTime::ATOM work for me for dates like '2020-02-04T00:00:00.000Z'. The 'Y-m-d\TH:i:s+' format works great.
16

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.

2 Comments

You’re the man! I was googling something like "php parse all rfc 8601 formats" and accidentally found your solution. It very helped me.
Why? This is basically what new DateTime does under the hood...
6

try this:

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

1 Comment

This value equals DateTimeInterface::ATOM or DATE_ATOM. Use a constant instead of a magic value.
6

Simply :

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

Comments

6

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"
}

Comments

5

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)

2 Comments

Actually, this work only if the decimal part of the seconds is present. Moreover, it works only if there are only 1 to 6 precision digits. If the decimal part is omitted or if it is more precise, false is returned.
Its work for me thx!
3

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));

Comments

2

This one works for me:

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

1 Comment

This is the only answer that interpretes the Z as UTC time and corrects it to the timezone php is in.
2

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"
}

Comments

1

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

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.