Validating dates

I discovered recently that Zend Framework 1's Zend_Date has two operating modes when it comes to format specifiers: iso and php, where iso is the default.

When using Zend_Validate_Date in forms, I like to use the php format specifiers as they are what I'm used to and so can easily know what they mean when reviewing code that I wrote months ago.

My code looks something like this:
$subForm->addElement('text', 'start_date', array(
'filters' => array('StringTrim', 'StripTags'),
'required' => true,
'label' => 'Start date',
'validators' => array(
array('Date', true, array('format'=>'j F Y')),
),
));

As you can see, I want the text field to be filled in with a date like "8 November 2010".

This is easy to achieve using this code in your Bootstrap.php like this:
function _initDateFormat()
{
Zend_Date::setOptions(array('format_type' => 'php'));
}

Note that this is a static method call and so it affects all instances of Zend_Date.

I also discovered that when you are in php formatting mode, then all the Zend_Date formatting constants like Zend_Date::MONTH do not work. This was a problem as I have other code in the project that uses them.

There are a number of choices.

One option is to change the formatting mode when you need to. As it is a static, you need to keep track of what you're doing, so the code looks like this:

$currentOptions = Zend_Date::setOptions();
$currentFormatType = $currentOptions['format_type'];
Zend_Date::setOptions(array('format_type' => 'iso'));

// You can now use Zend_Date::MONTH, ZEND_DATE::ISO etc

// After use, reset the format type back to what it was originally set to
Zend_Date::setOptions(array('format_type' => $currentFormatType));

Similarly, we can override Zend_Validate_Date with the same logic:
class App_Validate_Date extends Zend_Validate_Date
{
public function isValid ($value)
{
$currentOptions = Zend_Date::setOptions();
$currentFormatType = $currentOptions['format_type'];
Zend_Date::setOptions(array('format_type' => 'php'));

$valid = parent::isValid($value);

Zend_Date::setOptions(array('format_type' => $currentFormatType));
}
}

I also have two other requirements for date validation in this project:

  1. An empty $value fails validation.
  2. Regardless of the format that's been set, it would be helpful to always allow a valid Y-m-d formatted date.

Whilst talking on IRC about date validation issues that I was having, I also found out that a $value of 1-1-1TEST passes validation! This is noted in issue ZF-7583.

Allowing an empty $value is easy to add to my App_Validate_Date. Similarly, allowing a Y-m-d format is also fairly easy by resetting the formatting and then calling parent::isValid() again.

However, I really don't want '1111' or '1-1-1TEST' or to be considered a valid date! I couldn't see an easy way to fix this, so I went for the easy way out and wrote my own validator:

class App_Validate_Date extends Zend_Validate_Date
{
public function isValid ($value)
{
$this->_setValue($value);

if (empty($value)) {
return true;
}

$valid = $this->_testDateAgainstFormat($value, $this->getFormat());
if (!$valid) {
// re-test for Y-m-d as this format is always a valid option
$valid = $this->_testDateAgainstFormat($value, 'Y-m-d');
}

if ($valid) {
return true;
}
$this->_error(self::INVALID_DATE);
return false;
}

protected function _testDateAgainstFormat($value, $format)
{
$ts = strtotime($value);
if ($ts !== false) {
$testValue = date($format, $ts);
if ($testValue == $value) {
return true;
}
}
return false;
}
}

Obviously this code is highly unlikely to work if you need to validate localised dates! However, it solves my needs and it's useful to document here for when I forget how I solved these issues!

2 Responses to “Validating dates”

  1. 1 Gerard


    function _initDateFormat()
    { Zend_Date::setOptions(array('format_type' => 'php'));
    }

    be careful: if Zend_date is not used throughout your application your application will be needlessly including not one but many files.
    e.g. with ZFv1.10 You'll be including a minimum of 5 files:

    require_once 'Zend/Date/DateObject.php';
    require_once 'Zend/Locale.php';
    require_once 'Zend/Locale/Format.php'; + 'Zend/Locale/Data.php';
    require_once 'Zend/Locale/Math.php';

  2. 2 David

    That's a pretty clever way of getting around Zend_Validate_Date's mindless validation. I've used date_parse() to do something similar.

The views expressed in these comments are not the views of the publisher. However, we believe in the rights of others to express their legitimate views and concerns. Any legitimate complaint emailed to rob@akrabat.com will be seriously considered and the post reviewed as desirable and necessary.