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 1298ce5

Browse filesBrowse files
committed
feature #16809 [Form][FrameworkBundle][Bridge] Add a DateInterval form type (MisatoTremor)
This PR was merged into the 3.2-dev branch. Discussion ---------- [Form][FrameworkBundle][Bridge] Add a DateInterval form type | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #13389 | License | MIT | Doc PR | symfony/symfony-docs#4817 Replaces #15030 Commits ------- f7669be [Form] Add a DateInterval form type Also add dateinterval widget to twig templates.
2 parents 24e08e9 + f7669be commit 1298ce5
Copy full SHA for 1298ce5

11 files changed

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

0 commit comments

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