Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

New DateTimeFormatter Filter (#3617) #3632

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions library/Zend/Filter/DateTimeFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace Zend\Filter;

use DateTime;
use Exception;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please use an exception that is part of the Zend\Filter component? If no suitable exception exists you could create it, and have it extend the appropriate SPL exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second the recommendation from @Freeaqingme

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi both, I am "using" \Exception to catch the Exception thrown by DateTime (http://www.php.net/manual/en/datetime.construct.php) which is just \Exception. Are you saying I should create an Exception in Filter\Exception that Extends \Exception?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I'd remove the import, and use FQCN when catching:

catch (\Exception $e)

This will make it more clear that you actually intend to catch a global exception -- which is a rare occurrence in the framework.


class DateTimeFormatter extends AbstractFilter
{
/**
* A valid format string accepted by date()
*
* @var string
*/
protected $format = DateTime::ISO8601;

/**
* Sets filter options
*
* @param string|array|\Zend\Config\Config $options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param array|Traversable $options

*/
public function __construct($options = null)
{
if ($options) {
$this->setOptions($options);
}
}

/**
* Set the format string accepted by date() to use when formatting a string
*
* @param string $format
* @return \Zend\Filter\DateTimeFormatter
*/
public function setFormat($format)
{
$this->format = $format;
return $this;
}

/**
* Filter a datetime string by normalizing it to the filters specified format
*
* @param string $value
* @return string
*/
public function filter($value)
{
try {
$result = $this->normalizeDateTime($value);
} catch (Exception $ex) {
// DateTime threw an exception, an invalid date string was provided
return $value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filter should throw the exception else it returns not normalized dates

$filter->filter('now'); // 2013-03-13 10:00
$filter->filter('2013-03-13 10:00'); // 2013-03-13 10:00
$filter->filter('abcdefg'); // abcdefg ? -> please throw an Exception

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marc-mabe this is not the behaviour of other filters, see https://github.com/zendframework/zf2/blob/master/library/Zend/Filter/UriNormalize.php#L105

Filters should not throw exceptions or they will cause problems with InputFilters

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats interesting because other filters throw exceptions - see https://github.com/zendframework/zf2/blob/master/library/Zend/Filter/HtmlEntities.php

@weierophinney What's the right way to go here? For me a filter should throw exceptions on failures else it's impossible to detect such failures (see my example above). Only implementations of Zend\Validator\ValidatorInterface::isValid shouldn't throw exceptions but the filters should do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now re throwing the blanket DateTime \Exception as a InvalidArgumentException

}

return $result;
}

/**
* Attempt to convert a string into a valid DateTime object
*
* @param string $value
* @returns DateTime
* @throws Exception
*/
protected function normalizeDateTime($value)
{
if (is_int($value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing the case of $value instanceof DateTime

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

$dateTime = new DateTime('@' . $value);
} else {
$dateTime = new DateTime($value);
}

return $dateTime->format($this->format);
}
}
10 changes: 7 additions & 3 deletions library/Zend/Form/Element/DateTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ class DateTime extends Element implements InputProviderInterface
);

/**
*
* Opera and mobile browsers support datetime input, and display a datepicker control
* But the submitted value does not include seconds.
* A valid format string accepted by date()
*
* @var string
*/
Expand Down Expand Up @@ -189,6 +187,12 @@ public function getInputSpecification()
'required' => true,
'filters' => array(
array('name' => 'Zend\Filter\StringTrim'),
array(
'name' => 'Zend\Filter\DateTimeFormatter',
'options' => array(
'format' => $this->getFormat(),
)
)
),
'validators' => $this->getValidators(),
);
Expand Down
7 changes: 1 addition & 6 deletions library/Zend/Form/Element/DateTimeLocal.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

namespace Zend\Form\Element;

use Zend\Form\Element;
use Zend\Validator\DateStep as DateStepValidator;

class DateTimeLocal extends DateTime
Expand All @@ -26,11 +25,7 @@ class DateTimeLocal extends DateTime
);

/**
*
* Opera and mobile browsers support datetime input, and display a datepicker control
* But the submitted value does not include seconds.
*
* @var string
* {@inheritDoc}
*/
protected $format = self::DATETIME_LOCAL_FORMAT;

Expand Down
57 changes: 57 additions & 0 deletions tests/ZendTest/Filter/DateTimeFormatterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @package Zend_Filter
*/

namespace ZendTest\Filter;

use DateTime;
use Zend\Filter\DateTimeFormatter;

/**
* @category Zend
* @package Zend_Filter
* @subpackage UnitTests
* @group Zend_Filter
*/
class DateTimeFormatterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
date_default_timezone_set('UTC');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also have a few tests with other timezones? Just to ensure the code isn't fixated on UTC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this is potentially destructive if the timezone is already set, and other tests rely on it. I'd recommend storing the current setting, and during tearDown(), resetting to that value.

}

public function testDateTimeFormatted()
{
$filter = new DateTimeFormatter();
$result = $filter->filter('2012-01-01');
$this->assertEquals('2012-01-01T00:00:00+0000', $result);
}

public function testSetFormat()
{
$filter = new DateTimeFormatter();
$filter->setFormat(DateTime::RFC1036);
$result = $filter->filter('2012-01-01');
$this->assertEquals('Sun, 01 Jan 12 00:00:00 +0000', $result);
}

public function testFormatDateTimeFromTimestamp()
{
$filter = new DateTimeFormatter();
$result = $filter->filter(1359739801);
$this->assertEquals('2013-02-01T17:30:01+0000', $result);
}

public function testOriginalValueReturnedOnInvalidInput()
{
$filter = new DateTimeFormatter();
$result = $filter->filter('2013-31-31');
$this->assertEquals('2013-31-31', $result);
}
}
21 changes: 21 additions & 0 deletions tests/ZendTest/Form/Element/DateTimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ public function testProvidesInputSpecificationThatIncludesValidatorsBasedOnAttri
}
}

public function testProvidesInputSpecificationThatIncludesDateTimeFormatterBasedOnAttributes()
{
$element = new DateTimeElement('foo');
$element->setFormat(DateTime::W3C);

$inputSpec = $element->getInputSpecification();
$this->assertArrayHasKey('filters', $inputSpec);
$this->assertInternalType('array', $inputSpec['filters']);

foreach ($inputSpec['filters'] as $filter) {
switch ($filter['name']) {
case 'Zend\Filter\DateTimeFormatter':
$this->assertEquals($filter['options']['format'], DateTime::W3C);
$this->assertEquals($filter['options']['format'], $element->getFormat());
break;
default:
break;
}
}
}

public function testUsesBrowserFormatByDefault()
{
$element = new DateTimeElement('foo');
Expand Down