php - Issue with DateTime CreateFromFormat using RFC3339

I have this date-time stamp:

"2021-06-08T13:09:00.9796129Z"

And I cannot convert it in to a DateTime object using CreateFromFormat. I am using a JanePHP Normaliser so would prefer to try and solve this strictly using CreateFromFormat. I have tried the following:

$options = [
    \DateTimeInterface::ATOM,
    \DateTimeInterface::COOKIE,
    \DateTimeInterface::ISO8601,
    \DateTimeInterface::RFC822,
    \DateTimeInterface::RFC850,
    \DateTimeInterface::RFC1036,
    \DateTimeInterface::RFC1123,
    \DateTimeInterface::RFC7231,
    \DateTimeInterface::RFC2822,
    \DateTimeInterface::RFC3339,
    \DateTimeInterface::RFC3339_EXTENDED,
    \DateTimeInterface::RSS,
    \DateTimeInterface::W3C,
    'Y-m-dTH:i:s.uP',
    'Y-m-dTH:i:s.P',
    'Y-m-dTH:i:s.vP',
];

foreach ($options as $name) {
    var_dump(\DateTime::createFromFormat($name, "2021-06-08T13:09:00.9796129Z"));
}

All result in:

bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)

What is the correct format?

Answer

Solution:

You have a simple problem, and a more complex problem.

The simple problem is that to match a literal T, you need to escape it with a backslash. That ought to allow this pattern:

'Y-m-d\TH:i:s.uP'

(Year-month-day, literal T, hour:minute:second.microsecond, time zone)

The complex problem is that your timestamp has 7 decimal places in the seconds, giving a resolution of a tenth of a microsecond. That's why the u specifier isn't matching:

u: Microseconds (up to six digits)

A workaround would be to use the ? specifier which matches any single byte:

'Y-m-d\TH:i:s.u?P'

(Year-month-day, literal T, hour:minute:second.microsecond, ignore a byte for the extra digit, time zone)

var_dump(\DateTime::createFromFormat('Y-m-d\TH:i:s.u?P', "2021-06-08T13:09:00.9796129Z"));

# object(DateTime)#1 (3) {
#   ["date"]=>
#   string(26) "2021-06-08 13:09:00.979612"
#   ["timezone_type"]=>
#   int(2)
#   ["timezone"]=>
#   string(1) "Z"
# }

Answer

Solution:

You're not escaping the T, so it's attempting to use that as a placeholder (Timezone abbreviation). The correct format would be

'Y-m-d\TH:i:s.u\Z'

Source