Refactor datetime validation

The DateTimePicker form element needs to know from now on,
how user input can be formatted when validating.

refs #4581
This commit is contained in:
Johannes Meyer 2013-08-27 16:00:45 +02:00
parent 1a003f8c8b
commit 368bd3c9c7
3 changed files with 102 additions and 39 deletions

View File

@ -37,7 +37,23 @@ class DateTimeFactory implements ConfigAwareFactory
} }
/** /**
* Return new DateTime object using the set time zone * Return new DateTime object using the given format, time and set time zone
*
* Wraps DateTime::createFromFormat()
*
* @param string $format
* @param string $time
* @param DateTimeZone $timeZone
* @return DateTime
* @see DateTime::createFromFormat()
*/
public static function parse($time, $format, DateTimeZone $timeZone = null)
{
return DateTime::createFromFormat($format, $time, $timeZone !== null ? $timeZone : self::$timeZone);
}
/**
* Return new DateTime object using the given date/time string and set time zone
* *
* Wraps DateTime::__construct() * Wraps DateTime::__construct()
* *

View File

@ -28,10 +28,9 @@
namespace Icinga\Web\Form\Element; namespace Icinga\Web\Form\Element;
use \Exception;
use \Zend_Form_Element_Xhtml; use \Zend_Form_Element_Xhtml;
use \Icinga\Application\Icinga;
use \Icinga\Util\DateTimeFactory; use \Icinga\Util\DateTimeFactory;
use \Icinga\Exception\ProgrammingError;
/** /**
* Datetime form element which returns the input as Unix timestamp after the input has been proven valid. Utilizes * Datetime form element which returns the input as Unix timestamp after the input has been proven valid. Utilizes
@ -48,14 +47,20 @@ class DateTimePicker extends Zend_Form_Element_Xhtml
public $helper = 'formDateTime'; public $helper = 'formDateTime';
/** /**
* Find whether a variable is a Unix timestamp * Valid formats to check user input against
* @var array
*/
public $patterns;
/**
* Check whether a variable is a Unix timestamp
* *
* @param mixed $timestamp * @param mixed $timestamp
* @return bool * @return bool
*/ */
public function isUnixTimestamp($timestamp) public function isUnixTimestamp($timestamp)
{ {
return ((string) (int) $timestamp === (string) $timestamp) return (is_int($timestamp) || ctype_digit($timestamp))
&& ($timestamp <= PHP_INT_MAX) && ($timestamp <= PHP_INT_MAX)
&& ($timestamp >= ~PHP_INT_MAX); && ($timestamp >= ~PHP_INT_MAX);
} }
@ -63,13 +68,12 @@ class DateTimePicker extends Zend_Form_Element_Xhtml
/** /**
* Validate filtered date/time strings * Validate filtered date/time strings
* *
* Expects formats that the php date parser understands. Sets element value as Unix timestamp if the input is * Expects one or more valid formats being set in $this->patterns. Sets element value as Unix timestamp
* considered valid. Utilizes DateTimeFactory to ensure time zone awareness * if the input is considered valid. Utilizes DateTimeFactory to ensure time zone awareness.
* *
* @param string $value * @param string $value
* @param mixed $context * @param mixed $context
* @return bool * @return bool
* @see \Icinga\Util\DateTimeFactory::create()
*/ */
public function isValid($value, $context = null) public function isValid($value, $context = null)
{ {
@ -88,15 +92,19 @@ class DateTimePicker extends Zend_Form_Element_Xhtml
$dt = DateTimeFactory::create(); $dt = DateTimeFactory::create();
$dt->setTimestamp($value); $dt->setTimestamp($value);
} else { } else {
try { if (!isset($this->patterns)) {
$dt = DateTimeFactory::create($value); throw new ProgrammingError('Cannot parse datetime string without any pattern');
} catch (Exception $e) { }
$this->addErrorMessage(
_( $match_found = false;
'Failed to parse datetime string. See ' foreach ($this->patterns as $pattern) {
. 'http://www.php.net/manual/en/datetime.formats.php for valid formats' $dt = DateTimeFactory::parse($value, $pattern);
) if ($dt !== false && $dt->format($pattern) === $value) {
); $match_found = true;
break;
}
}
if (!$match_found) {
return false; return false;
} }
} }

View File

@ -34,12 +34,32 @@ class DateTimeTest extends PHPUnit_Framework_TestCase
{ {
DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC'))); DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC')));
$dt = new DateTimePicker('foo'); $dt = new DateTimePicker(
'foo',
array(
'patterns' => array(
'd/m/Y g:i A',
'd.m.Y H:i:s'
)
)
);
$this->assertFalse(
$dt->isValid('08/27/2013 12:40 PM'),
'Wrong placed month/day must not be valid input'
);
$this->assertFalse( $this->assertFalse(
$dt->isValid('bar'), $dt->isValid('bar'),
'Arbitrary strings must not be valid input' 'Arbitrary strings must not be valid input'
); );
$this->assertFalse(
$dt->isValid('12:40 AM'),
'Time strings must not be valid input'
);
$this->assertFalse(
$dt->isValid('27/08/2013'),
'Date strings must not be valid input'
);
$this->assertFalse( $this->assertFalse(
$dt->isValid('13736a16223'), $dt->isValid('13736a16223'),
'Invalid Unix timestamps must not be valid input' 'Invalid Unix timestamps must not be valid input'
@ -58,20 +78,36 @@ class DateTimeTest extends PHPUnit_Framework_TestCase
{ {
DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC'))); DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC')));
$dt = new DateTimePicker('foo'); $dt = new DateTimePicker(
'foo',
array(
'patterns' => array(
'd/m/Y g:i A',
'd.m.Y H:i:s'
)
)
);
$this->assertTrue( $this->assertTrue(
$dt->isValid('2013-07-12 08:03:43'), $dt->isValid('27/08/2013 12:40 PM'),
'Using a valid date/time string must not fail'
);
$this->assertTrue(
$dt->isValid('12.07.2013 08:03:43'),
'Using a valid date/time string must not fail' 'Using a valid date/time string must not fail'
); );
$this->assertTrue( $this->assertTrue(
$dt->isValid(1373616223), $dt->isValid(1373616223),
'Using valid Unix timestamps must not fail' 'Using valid Unix timestamps must not fail'
); );
$this->assertTrue(
$dt->isValid('1373616223'),
'Using strings as Unix timestamps must not fail'
);
} }
/** /**
* Test that DateTimePicker::getValue() returns a timestamp after a call to isValid considers the input as correct * Test that DateTimePicker::getValue() returns a timestamp after a successful call to isValid
* *
* Utilizes singleton DateTimeFactory * Utilizes singleton DateTimeFactory
* *
@ -81,25 +117,26 @@ class DateTimeTest extends PHPUnit_Framework_TestCase
{ {
DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC'))); DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('UTC')));
$dt = new DateTimePicker('foo'); $dt = new DateTimePicker(
$dt->setValue('2013-07-12 08:03:43'); 'foo',
array(
$this->assertTrue( 'patterns' => array(
$dt->isValid($dt->getValue()), 'd.m.Y H:i:s'
'Using a valid date/time string must not fail' )
)
); );
$dt->isValid('12.07.2013 08:03:43');
$this->assertEquals( $this->assertEquals(
$dt->getValue(),
1373616223, 1373616223,
'getValue did not return the correct Unix timestamp according to the given date/time ' $dt->getValue(),
. 'string' 'getValue did not return the correct Unix timestamp according to the given date/time string'
); );
} }
/** /**
* Test that DateTimePicker::getValue() returns a timestamp respecting the given non-UTC time zone after a call to * Test that DateTimePicker::getValue() returns a timestamp respecting
* isValid considers the input as correct * the given non-UTC time zone after a successful call to isValid
* *
* Utilizes singleton DateTimeFactory * Utilizes singleton DateTimeFactory
* *
@ -109,17 +146,19 @@ class DateTimeTest extends PHPUnit_Framework_TestCase
{ {
DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('Europe/Berlin'))); DateTimeFactory::setConfig(array('timezone' => new DateTimeZone('Europe/Berlin')));
$dt = new DateTimePicker('foo'); $dt = new DateTimePicker(
$dt->setValue('2013-07-12 08:03:43'); 'foo',
array(
$this->assertTrue( 'patterns' => array(
$dt->isValid($dt->getValue()), 'd.m.Y H:i:s'
'Using a valid date/time string must not fail' )
)
); );
$dt->isValid('12.07.2013 08:03:43');
$this->assertEquals( $this->assertEquals(
$dt->getValue(),
1373609023, 1373609023,
$dt->getValue(),
'getValue did not return the correct Unix timestamp according to the given date/time string and time zone' 'getValue did not return the correct Unix timestamp according to the given date/time string and time zone'
); );
} }