PHP: Validate & Check Dates

More times than I can count I’ve had to validate dates in PHP, not only check if it is in the required format but also to make sure the date actually exists. Until recently there has not been a good, native way of doing this, yes checkdate() has been around for a while but it’s particularly fussy with it’s input, what we needed was a mixture of date() where we can specify the input format and checkdate() to validate it. Enter the DateTime class. Technically available from PHP >= 5.2.0 it isn’t really worth using until >=5.3.0 due to bugs and missing features, (DateTime::createFromFormat or DateTime::getLastErrors in particular).

The traditional approach ( PHP < 5.3.0 )

If you are still running an older version of PHP or are developing for WordPress, (WordPress’s minimum requirements are still 5.2.4 at the time of writing this), below is a very handy PHP date validator function I wrote. Simply pass the date you wish to check as the first value and the format it should be in as the second.

	/**
	 * Validates a string as a date in the inputted format.
	 * Version 1.0
	 *
	 * @param type $date The date to check.
	 * @param type $format The format the date should be in. Defaults to YYYY-MM-DD
	 * @return boolean
	 */
	function validate_date( $date = null, $format = 'YYYY-MM-DD' ){

		// Return FALSE if $date empty
		if( ! $date )
			return FALSE;

		// $date is trimed
		$date = trim( $date );

		// If $format empty return to default, else UPPERCASE and trim.
		$format = empty( $format ) ? 'YYYY-MM-DD' : strtoupper( $format );

		// $format is 10 char in length and contains all the required chars
		if( strlen( $format ) != 10 ||
		    strpos( $format, 'YYYY' ) === FALSE ||
		    strpos( $format, 'MM' ) === FALSE ||
		    strpos( $format, 'DD' ) === FALSE
		)
			return FALSE;

		// Get $format serparator
		$date_seperator = str_replace( array( 'Y', 'M', 'D' ) , '', $format );
		$date_seperator = $date_seperator[0];

		// Explode $format into parts
		$format_parts = explode( $date_seperator, $format );

		// Explode our date into parts
		$date_parts = explode( $date_seperator, $date );

		// Count $date_parts; Quicker than doing it inline
		$date_parts_count = count( $date_parts );

		// Cycle through $date_parts, compare the length to the equivilent $format_parts length, set to var
		for( $i = 0; $i &lt; $date_parts_count; $i++ ){

			// Check each datepart is a valid in
			if( ! filter_var( ( int ) $date_parts[ $i ], FILTER_VALIDATE_INT ) )
				return FALSE;

			// Compare $date_part length to equivilent $format_part length
			if( strlen( $date_parts[ $i ] ) != strlen( $format_parts[ $i ] )  )
				return FALSE;

			// Set our variables so we check it's a valid date later
			if( $format_parts[ $i ][0] == 'Y' ){

				$year = $date_parts[ $i ];

			} elseif( $format_parts[ $i ][0] == 'M' ){

				$month = $date_parts[ $i ];

			} elseif( $format_parts[ $i ][0] == 'D' ){

				$day = $date_parts[ $i ];

			}

		}

		// Finally, check it's a valid date
		if( checkdate( $month, $day, $year ) ){

			return TRUE;

		} else {

			return FALSE;

		}

	}

The modern approach ( PHP >= 5.3.0)

If you know you’re going to be using a more modern version of PHP you can take advantage of the very cool and now native DateTime class. Among it’s many cool new features the best is DateTime::createFromFormat() which allows you to re-format an inputted date. Yes it’s very similar to using date() & strtotime() together BUT this has all sorts of useful additions, the most important of which is error checking.

Christos Pontikis has done a great post on the best way to use the class and how to work around it’s current limitations. A copy of his final recommended solution is below. It basically involves explicitly checking the DateTime::getLastErrors() function to see if the date was valid or not.


/**
 * Check if a string is a valid date(time)
 *
 * DateTime::createFromFormat requires PHP >= 5.3
 *
 * @param string $str_dt
 * @param string $str_dateformat
 * @param string $str_timezone (If timezone is invalid, php will throw an exception)
 * @return bool
 */
function check_date( $str_dt, $str_dateformat, $str_timezone ) {

$date = DateTime::createFromFormat( $str_dateformat, $str_dt, new DateTimeZone( $str_timezone ) );
return $date && DateTime::getLastErrors()['warning_count'] == 0 && DateTime::getLastErrors()['error_count'] == 0;

}

N.B. There are still some errors with the DateTime class. Check out the User comments on the DateTime::createFromFormat for a more in depth discussion.