Validate ISO 8601 date string
Asked Answered
J

6

23

How do you validate ISO 8601 date string (ex: 2011-10-02T23:25:42Z).

I know that there are several possible representations of ISO 8601 dates, but I'm only interested in validating the format I gave as an example above.

Janniejanos answered 3/11, 2011 at 23:57 Comment(2)
That doesn't look correct. Shouldn't the "day" part be two digits?Ditmore
@Ditmore - I guess you are right. I've updated the post to reflect that.Janniejanos
F
18

This worked for me, it uses a regular expression to make sure the date is in the format you want, and then tries to parse the date and recreate it to make sure the output matches the input:

<?php

$date = '2011-10-02T23:25:42Z';
var_dump(validateDate($date));

$date = '2011-17-17T23:25:42Z';
var_dump(validateDate($date));

function validateDate($date)
{
    if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/', $date, $parts) == true) {
        $time = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);

        $input_time = strtotime($date);
        if ($input_time === false) return false;

        return $input_time == $time;
    } else {
        return false;
    }
}

You could expand further to use checkdate to make sure the month day and year are valid as well.

Flaxman answered 4/11, 2011 at 0:54 Comment(2)
a) Don't use the case-insensitive flag; T and Z are the only applicable alpha characters and should both be uppercase. b) Z isn't the only valid time zone designator. See en.wikipedia.org/wiki/ISO_8601#DatesDitmore
You should also allow an optional + or - in front of the date string (en.wikipedia.org/wiki/ISO_8601#Years). probably a ([+|-]?) will sufficePhyllys
I
18

I want to share my lightweight solution. It is not ideal, but might be helpful for someone.

function validISO8601Date($value)
{
    if (!is_string($value)) {
        return false;
    }

    $dateTime = \DateTime::createFromFormat(\DateTime::ISO8601, $value);

    if ($dateTime) {
        return $dateTime->format(\DateTime::ISO8601) === $value;
    }

    return false;
}

Atention!

Some valid ISO8601 dates will fail Look at the list below

NOT VALID  --> '' // Correct
NOT VALID  --> 'string' // Correct
VALID      --> '2000-01-01T01:00:00+1200' // This is the only format function returns as valid
NOT VALID  --> '2015-01 first' // Correct
NOT VALID  --> '2000-01-01T01:00:00Z' // Must be valid!
NOT VALID  --> '2000-01-01T01:00:00+01' // Must be valid!
Incommunicable answered 21/1, 2014 at 14:19 Comment(7)
This seems like the most clean approach, except the timezone offset can include a colon or not in the standard, but the date function uses it or doesn't depending on whether DATE_ISO8601 or "c" is used to specify the format.Laquitalar
strtotime($value); returns false if the $value is not parsable. $date = date(DATE_ISO8601, $timestamp); with $timestamp = false; contains the minimum date (1970-01-01T01:00:00+0100). So this example is bad. Do not use.Fairchild
@MrBoolean, Right. And then "1970-01-01T01:00:00+0100" will be compared with $value and false will be returnedIncommunicable
Jep. But strtotime will also parse something like a day ago or 2015-01 first and many more. At this point this function fails.Fairchild
I've fixed the function and now it works for all types correct.Incommunicable
From the people at Zend: "This format (DateTime::ISO8601) is not compatible with ISO-8601, but is left this way for backward compatibility reasons. Use DateTime::ATOM or DATE_ATOM for compatibility with ISO-8601 instead."Pomade
It should be the best answerUnadvised
D
14

Edit: By far the easiest method is to simply try to create a DateTime object using the string, eg

$dt = new DateTime($dateTimeString);

If the DateTime constructor cannot parse the string, it will throw an exception, eg

DateTime::__construct(): Failed to parse time string (2011-10-02T23:25:72Z) at position 18 (2): Unexpected character

Note that if you leave off the time zone designator, it will use the configured default timezone.

Second easiest method is to use a regular expression. Something like this aught to cover it

if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(\+|-)\d{2}(:?\d{2})?)$/', $dateString, $parts)) {
    // valid string format, can now check parts

    $year  = $parts[1];
    $month = $parts[2];
    $day   = $parts[3];

    // etc
}
Ditmore answered 4/11, 2011 at 0:54 Comment(4)
Nice idea, but DateTime constructor is not limited to ISO 8601 input.Laquitalar
@Laquitalar I'm not really sure what you mean. I didn't say anything about it being limitedDitmore
@Ditmore well if you want to check if it is a valid iso date, then having it allow other date formats that are not iso makes it not a validation for the iso format.Silva
This is not sufficient for validation of the ISO8601 standard. Example: new DateTime("22\t12.78"); will return a truthy value. Any one of these formats will be accepted.Pomade
G
6

See http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/. It gives this regex to use:

^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$

I suppose this does not answer your question exactly, since it will match any valid ISO 8601 date, but if that is alright then this works perfectly.

Georganngeorge answered 4/11, 2011 at 0:55 Comment(1)
This regex even considers string like 3401 as valid date as well. Use at your own risk.Hyperventilation
S
5

This is the function I use. It is similar to the answer of drew010 but works also for timestamps ending with "+01:00" or "-01:00".

function isTimestampIsoValid($timestamp)
{
    if (preg_match('/^'.
            '(\d{4})-(\d{2})-(\d{2})T'. // YYYY-MM-DDT ex: 2014-01-01T
            '(\d{2}):(\d{2}):(\d{2})'.  // HH-MM-SS  ex: 17:00:00
            '(Z|((-|\+)\d{2}:\d{2}))'.  // Z or +01:00 or -01:00
            '$/', $timestamp, $parts) == true)
    {
        try {
            new \DateTime($timestamp);
            return true;
        }
        catch ( \Exception $e)
        {
            return false;
        }
    } else {
        return false;
    }
}
Shuman answered 21/1, 2014 at 9:18 Comment(0)
K
-1

Carbon is handling this part really well

use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
...
/** @test */
public function checking_iso8601_date()
{
    $this->expectException(InvalidFormatException::class);
    Carbon::createFromFormat('c', '2021-05-11 20:03:45+02:00');

    $this->assertInstanceOf(Carbon::class, Carbon::createFromFormat('c', '2021-05-11T20:03:45+02:00'));
}

You may do the same with DateTime

use DateTime;
...
/** @test */
public function checking_iso8601_date()
{
    $this->assertInstanceOf(DateTime::class, DateTime::createFromFormat('Y-m-d\TH:i:sP', '2021-05-11T20:03:45+02:00'));
    $this->assertFalse(DateTime::createFromFormat('Y-m-d\TH:i:sP', '2021-05-11 20:03:45+02:00'));
}
Keewatin answered 11/5, 2021 at 18:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.