Accepting dates in common formats in PHP



When handling dates in user input, user don’t use the right format. To get the elements in the right order, separate the fields for year, month, and date. However, there are circumstances when using a commonly accepted format can be useful, such as an intranet or when you know the date is coming from a reliable source like a database.

When both the month and date elements are between 1 and 12, there is no way of telling whether they have been inputted in the correct order. The MM/DD/YYYY format interprets 04/01/2008 as April 1, 2008, while the DD/MM/YYYY format treats it as January 4, 2008.

They all follow the same steps:

1. Use the forward slash or other separator to split the input into an array.

2. Check that the array contains three elements.

3. Pass the elements (date parts) in the correct order to Pos_Date::setDate().

I pass the elements to the overridden setDate() method, rather than to the parent method, because the overridden method continues the process like this:

4. It checks that the date parts are numeric.

5. It checks the validity of the date.

6. If everything is OK, it passes the date parts to the parent setDate() method and resets the object’s $_year, $_month, and $_day properties.


This shows an important aspect of developing classes. When I originally designed these methods, all six steps were performed in each function. This involved not only a lot of typing; with four methods all doing essentially the same thing (the fourth is Pos_Date::setDate()), the possibility of mistakes was quadrupled. So, even inside a class, it is important to identify duplicated effort and eliminate it by passing subordinate tasks to specialized methods. In this case, setDate() is a public method.

Accepting a date in MM/DD/YYYY format

This is the full listing for setMDY(), which accepts dates in the standard American format MM/DD/YYYY.

public function setMDY($USDate)
{
$dateParts = preg_split('{[-/ :.]}', $USDate);
if (!is_array($dateParts) || count($dateParts) != 3) {
throw new Exception('setMDY() expects a date as "MM/DD/YYYY".');
}
$this->setDate($dateParts[2], $dateParts[0], $dateParts[1]);
}


The first line inside the setMDY() method uses preg_split() to break the input into an array. I have used preg_split() instead of explode(), because it accepts a regular expression as the first argument. The regular expression {[-/ :.]} splits the input on any of the following characters: dash, forward slash, single space, colon, or period. This allows not only MM/DD/YYYY, but variations, such as MM-DD-YYYY or MMD:YYYY.

As long as $dateParts is an array with three elements, the date parts are passed internally to the overridden setDate() method for the rest of the process. If there is anything wrong with the data, it is the responsibility of setDate() to throw an exception.

Accepting a date in DD/MM/YYYY format

The setDMY() method is identical to setMDY(), except that it passes the elements of the $dateParts array to setDate() in a different order to take account of the DD/MM/YYYY format commonly used in Europe and many other parts of the world. The full listing looks like this:

public function setDMY($EuroDate)
{
$dateParts = preg_split('{[-/ :.]}', $EuroDate);
if (!is_array($dateParts) || count($dateParts) != 3) {
throw new Exception('setDMY() expects a date as "DD/MM/YYYY".');
}
$this->setDate($dateParts[2], $dateParts[1], $dateParts[0]);
}


Accepting a date in MySQL format

This works exactly the same as the previous two methods. Although MySQL uses only a dash as the separator between date parts, I have left the regular expression unchanged, so that the setFromMySQL() method can be used with dates from other sources that follow the same ISO format as MySQL. The full listing follows:

public function setFromMySQL($MySQLDate)
{
$dateParts = preg_split('{[-/ :.]}', $MySQLDate);
if (!is_array($dateParts) || count($dateParts) != 3) {
throw new Exception('setFromMySQL() expects a date as "YYYY-MM-DD".');
}
$this->setDate($dateParts[0], $dateParts[1], $dateParts[2]);
}