Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 95037ca

Browse filesBrowse files
committed
[Form] Add a DateInterval form type
Also add dateinterval widget to twig templates.
1 parent 582f475 commit 95037ca
Copy full SHA for 95037ca

File tree

Expand file treeCollapse file tree

12 files changed

+1401
-0
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+1401
-0
lines changed

‎src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@
8787
{% endif %}
8888
{%- endblock time_widget %}
8989

90+
{% block dateinterval_widget %}
91+
{% if widget == 'single_text' %}
92+
{{- block('form_widget_simple') -}}
93+
{% else %}
94+
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) %}
95+
<div {{ block('widget_container_attributes') }}>
96+
{{ form_errors(form) }}
97+
{% if with_years %}{{ form_widget(form.years) }}{% endif %}
98+
{% if with_months %}{{ form_widget(form.months) }}{% endif %}
99+
{% if with_weeks %}{{ form_widget(form.weeks) }}{% endif %}
100+
{% if with_days %}{{ form_widget(form.days) }}{% endif %}
101+
{% if with_hours %}{{ form_widget(form.hours) }}{% endif %}
102+
{% if with_minutes %}{{ form_widget(form.minutes) }}{% endif %}
103+
{% if with_seconds %}{{ form_widget(form.seconds) }}{% endif %}
104+
{% if with_invert %}{{ form_widget(form.invert) }}{% endif %}
105+
</div>
106+
{% endif %}
107+
{% endblock dateinterval_widget %}
108+
90109
{% block choice_widget_collapsed -%}
91110
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
92111
{{- parent() -}}

‎src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@
131131
{%- endif -%}
132132
{%- endblock time_widget -%}
133133

134+
{% block dateinterval_widget %}
135+
{% if widget == 'single_text' %}
136+
{{- block('form_widget_simple') -}}
137+
{% else %}
138+
<div {{ block('widget_container_attributes') }}>
139+
{{ form_errors(form) }}
140+
{% if with_years %}{{ form_widget(form.years) }}{% endif %}
141+
{% if with_months %}{{ form_widget(form.months) }}{% endif %}
142+
{% if with_weeks %}{{ form_widget(form.weeks) }}{% endif %}
143+
{% if with_days %}{{ form_widget(form.days) }}{% endif %}
144+
{% if with_hours %}{{ form_widget(form.hours) }}{% endif %}
145+
{% if with_minutes %}{{ form_widget(form.minutes) }}{% endif %}
146+
{% if with_seconds %}{{ form_widget(form.seconds) }}{% endif %}
147+
{% if with_invert %}{{ form_widget(form.invert) }}{% endif %}
148+
</div>
149+
{% endif %}
150+
{% endblock dateinterval_widget %}
151+
134152
{%- block number_widget -%}
135153
{# type="number" doesn't work with floats #}
136154
{%- set type = type|default('text') -%}

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
<service id="form.type.datetime" class="Symfony\Component\Form\Extension\Core\Type\DateTimeType">
7474
<tag name="form.type" />
7575
</service>
76+
<service id="form.type.dateinterval" class="Symfony\Component\Form\Extension\Core\Type\DateIntervalType">
77+
<tag name="form.type" alias="dateinterval" />
78+
</service>
7679
<service id="form.type.email" class="Symfony\Component\Form\Extension\Core\Type\EmailType">
7780
<tag name="form.type" />
7881
</service>

‎src/Symfony/Component/Form/Extension/Core/CoreExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/CoreExtension.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ protected function loadTypes()
5252
new Type\CollectionType(),
5353
new Type\CountryType(),
5454
new Type\DateType(),
55+
new Type\DateIntervalType(),
5556
new Type\DateTimeType(),
5657
new Type\EmailType(),
5758
new Type\HiddenType(),
+179Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\DataTransformerInterface;
15+
use Symfony\Component\Form\Exception\TransformationFailedException;
16+
17+
/**
18+
* Transforms between a normalized date interval and an interval string/array.
19+
*
20+
* @author Steffen Roßkamp <steffen.rosskamp@gimmickmedia.de>
21+
*/
22+
class DateIntervalToArrayTransformer implements DataTransformerInterface
23+
{
24+
const YEARS = 'years';
25+
const MONTHS = 'months';
26+
const DAYS = 'days';
27+
const HOURS = 'hours';
28+
const MINUTES = 'minutes';
29+
const SECONDS = 'seconds';
30+
const INVERT = 'invert';
31+
32+
private static $availableFields = array(
33+
self::YEARS => 'y',
34+
self::MONTHS => 'm',
35+
self::DAYS => 'd',
36+
self::HOURS => 'h',
37+
self::MINUTES => 'i',
38+
self::SECONDS => 's',
39+
self::INVERT => 'r',
40+
);
41+
private $fields;
42+
43+
/**
44+
* Constructor.
45+
*
46+
* @param array $fields The date fields
47+
* @param bool $pad Whether to use padding
48+
*/
49+
public function __construct(array $fields = null, $pad = false)
50+
{
51+
if (null === $fields) {
52+
$fields = array('years', 'months', 'days', 'hours', 'minutes', 'seconds', 'invert');
53+
}
54+
$this->fields = $fields;
55+
$this->pad = (bool) $pad;
56+
}
57+
58+
/**
59+
* Transforms a normalized date interval into an interval array.
60+
*
61+
* @param \DateInterval $dateInterval Normalized date interval.
62+
*
63+
* @return array Interval array.
64+
*
65+
* @throws TransformationFailedException If the given value is not a \DateInterval instance.
66+
*/
67+
public function transform($dateInterval)
68+
{
69+
if (null === $dateInterval) {
70+
return array_intersect_key(
71+
array(
72+
'years' => '',
73+
'months' => '',
74+
'weeks' => '',
75+
'days' => '',
76+
'hours' => '',
77+
'minutes' => '',
78+
'seconds' => '',
79+
'invert' => false,
80+
),
81+
array_flip($this->fields)
82+
);
83+
}
84+
if (!$dateInterval instanceof \DateInterval) {
85+
throw new TransformationFailedException('Expected a \DateInterval.');
86+
}
87+
$result = array();
88+
foreach (self::$availableFields as $field => $char) {
89+
$result[$field] = $dateInterval->format('%'.($this->pad ? strtoupper($char) : $char));
90+
}
91+
if (in_array('weeks', $this->fields, true)) {
92+
$result['weeks'] = 0;
93+
if (isset($result['days']) && (int) $result['days'] >= 7) {
94+
$result['weeks'] = (string) floor($result['days'] / 7);
95+
$result['days'] = (string) ($result['days'] % 7);
96+
}
97+
}
98+
$result['invert'] = '-' === $result['invert'];
99+
$result = array_intersect_key($result, array_flip($this->fields));
100+
101+
return $result;
102+
}
103+
104+
/**
105+
* Transforms an interval array into a normalized date interval.
106+
*
107+
* @param array $value Interval array
108+
*
109+
* @return \DateInterval Normalized date interval
110+
*
111+
* @throws TransformationFailedException If the given value is not an array or
112+
* if the value could not be transformed.
113+
*/
114+
public function reverseTransform($value)
115+
{
116+
if (null === $value) {
117+
return;
118+
}
119+
if (!is_array($value)) {
120+
throw new TransformationFailedException('Expected an array.');
121+
}
122+
if ('' === implode('', $value)) {
123+
return;
124+
}
125+
$emptyFields = array();
126+
foreach ($this->fields as $field) {
127+
if (!isset($value[$field])) {
128+
$emptyFields[] = $field;
129+
}
130+
}
131+
if (count($emptyFields) > 0) {
132+
throw new TransformationFailedException(
133+
sprintf(
134+
'The fields "%s" should not be empty',
135+
implode('", "', $emptyFields)
136+
)
137+
);
138+
}
139+
if (isset($value['invert']) && !is_bool($value['invert'])) {
140+
throw new TransformationFailedException('The value of "invert" must be boolean');
141+
}
142+
foreach (self::$availableFields as $field => $char) {
143+
if ($field !== 'invert' && isset($value[$field]) && !ctype_digit((string) $value[$field])) {
144+
throw new TransformationFailedException(sprintf('This amount of "%s" is invalid', $field));
145+
}
146+
}
147+
try {
148+
if (!empty($value['weeks'])) {
149+
$interval = sprintf(
150+
'P%sY%sM%sWT%sH%sM%sS',
151+
empty($value['years']) ? '0' : $value['years'],
152+
empty($value['months']) ? '0' : $value['months'],
153+
empty($value['weeks']) ? '0' : $value['weeks'],
154+
empty($value['hours']) ? '0' : $value['hours'],
155+
empty($value['minutes']) ? '0' : $value['minutes'],
156+
empty($value['seconds']) ? '0' : $value['seconds']
157+
);
158+
} else {
159+
$interval = sprintf(
160+
'P%sY%sM%sDT%sH%sM%sS',
161+
empty($value['years']) ? '0' : $value['years'],
162+
empty($value['months']) ? '0' : $value['months'],
163+
empty($value['days']) ? '0' : $value['days'],
164+
empty($value['hours']) ? '0' : $value['hours'],
165+
empty($value['minutes']) ? '0' : $value['minutes'],
166+
empty($value['seconds']) ? '0' : $value['seconds']
167+
);
168+
}
169+
$dateInterval = new \DateInterval($interval);
170+
if (!empty($value['invert'])) {
171+
$dateInterval->invert = $value['invert'] ? 1 : 0;
172+
}
173+
} catch (\Exception $e) {
174+
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
175+
}
176+
177+
return $dateInterval;
178+
}
179+
}
+124Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\DataTransformerInterface;
15+
use Symfony\Component\Form\Exception\TransformationFailedException;
16+
17+
/**
18+
* Transforms between a date string and a DateInterval object.
19+
*
20+
* @author Steffen Roßkamp <steffen.rosskamp@gimmickmedia.de>
21+
*/
22+
class DateIntervalToStringTransformer implements DataTransformerInterface
23+
{
24+
/**
25+
* Format used for generating strings.
26+
*
27+
* @var string
28+
*/
29+
private $format;
30+
31+
/**
32+
* Whether to parse by as a signed interval.
33+
*
34+
* @var bool
35+
*/
36+
private $parseSigned;
37+
38+
/**
39+
* Transforms a \DateInterval instance to a string.
40+
*
41+
* @see \DateInterval::format() for supported formats
42+
*
43+
* @param string $format The date format
44+
* @param bool $parseSigned Whether to parse as a signed interval
45+
*/
46+
public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS', $parseSigned = false)
47+
{
48+
$this->format = $format;
49+
$this->parseSigned = $parseSigned;
50+
}
51+
52+
/**
53+
* Transforms a DateInterval object into a date string with the configured format
54+
* and timezone.
55+
*
56+
* @param \DateInterval $value A DateInterval object
57+
*
58+
* @return string An ISO 8601 or relative date string like date interval presentation
59+
*
60+
* @throws TransformationFailedException If the given value is not a \DateInterval instance.
61+
*/
62+
public function transform($value)
63+
{
64+
if (null === $value) {
65+
return '';
66+
}
67+
if (!$value instanceof \DateInterval) {
68+
throw new TransformationFailedException('Expected a \DateInterval.');
69+
}
70+
71+
return $value->format($this->format);
72+
}
73+
74+
/**
75+
* Transforms a date string in the configured into a DateInterval object.
76+
*
77+
* @param string $value An ISO 8601 or date string like date interval presentation
78+
*
79+
* @return \DateInterval An instance of \DateInterval
80+
*
81+
* @throws TransformationFailedException If the given value is not a string or
82+
* if the date interval could not be parsed.
83+
*/
84+
public function reverseTransform($value)
85+
{
86+
if (empty($value)) {
87+
return;
88+
}
89+
if (!is_string($value)) {
90+
throw new TransformationFailedException('Expected a string.');
91+
}
92+
if (!$this->isISO8601($value)) {
93+
throw new TransformationFailedException('Non ISO 8601 date strings are not supported yet');
94+
}
95+
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $this->format).'$/';
96+
if (!preg_match($valuePattern, $value)) {
97+
throw new TransformationFailedException(
98+
sprintf('Value "%s" contains intervals not accepted by format "%s".', $value, $this->format)
99+
);
100+
}
101+
try {
102+
$dateInterval = new \DateInterval($value);
103+
} catch (\Exception $e) {
104+
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
105+
}
106+
107+
return $dateInterval;
108+
}
109+
110+
/**
111+
* Checks if a string is a valid ISO 8601 duration string.
112+
*
113+
* @param string $string A string
114+
*
115+
* @return int
116+
*/
117+
private function isISO8601($string)
118+
{
119+
return preg_match(
120+
'/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/',
121+
$string
122+
);
123+
}
124+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.