TimeField.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form;
  11. use Symfony\Component\Form\ValueTransformer\ReversedTransformer;
  12. use Symfony\Component\Form\ValueTransformer\DateTimeToArrayTransformer;
  13. use Symfony\Component\Form\ValueTransformer\DateTimeToStringTransformer;
  14. use Symfony\Component\Form\ValueTransformer\DateTimeToTimestampTransformer;
  15. use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
  16. use Symfony\Component\Form\ChoiceList\HourChoiceList;
  17. use Symfony\Component\Form\ChoiceList\MinuteChoiceList;
  18. use Symfony\Component\Form\ChoiceList\SecondChoiceList;
  19. /**
  20. * Represents a time field.
  21. *
  22. * Available options:
  23. *
  24. * * widget: How to render the time field ("input" or "choice"). Default: "choice".
  25. * * type: The type of the date stored on the object. Default: "datetime":
  26. * * datetime: A DateTime object;
  27. * * string: A raw string (e.g. 2011-05-01 12:30:00, Y-m-d H:i:s);
  28. * * timestamp: A unix timestamp (e.g. 1304208000).
  29. * * raw: An hour, minute, second array
  30. * * with_seconds Whether or not to create a field for seconds. Default: false.
  31. *
  32. * * hours: An array of hours for the hour select tag.
  33. * * minutes: An array of minutes for the minute select tag.
  34. * * seconds: An array of seconds for the second select tag.
  35. *
  36. * * data_timezone: The timezone of the data. Default: UTC.
  37. * * user_timezone: The timezone of the user entering a new value. Default: UTC.
  38. */
  39. class TimeField extends Form
  40. {
  41. const INPUT = 'input';
  42. const CHOICE = 'choice';
  43. const DATETIME = 'datetime';
  44. const STRING = 'string';
  45. const TIMESTAMP = 'timestamp';
  46. const RAW = 'raw';
  47. protected static $widgets = array(
  48. self::INPUT,
  49. self::CHOICE,
  50. );
  51. protected static $types = array(
  52. self::DATETIME,
  53. self::STRING,
  54. self::TIMESTAMP,
  55. self::RAW,
  56. );
  57. /**
  58. * {@inheritDoc}
  59. */
  60. public function __construct($key, array $options = array())
  61. {
  62. // Override parent option
  63. // \DateTime objects are never edited by reference, because
  64. // we treat them like value objects
  65. $this->addOption('by_reference', false);
  66. parent::__construct($key, $options);
  67. }
  68. /**
  69. * {@inheritDoc}
  70. */
  71. protected function configure()
  72. {
  73. $this->addOption('widget', self::CHOICE, self::$widgets);
  74. $this->addOption('type', self::DATETIME, self::$types);
  75. $this->addOption('with_seconds', false);
  76. $this->addOption('hours', array());
  77. $this->addOption('minutes', array());
  78. $this->addOption('seconds', array());
  79. $this->addOption('data_timezone', date_default_timezone_get());
  80. $this->addOption('user_timezone', date_default_timezone_get());
  81. if ($this->getOption('widget') == self::INPUT) {
  82. $this->add(new TextField('hour', array('max_length' => 2)));
  83. $this->add(new TextField('minute', array('max_length' => 2)));
  84. if ($this->getOption('with_seconds')) {
  85. $this->add(new TextField('second', array('max_length' => 2)));
  86. }
  87. } else {
  88. $this->add(new ChoiceField('hour', array(
  89. 'choice_list' => new HourChoiceList($this->getOption('hours')),
  90. )));
  91. $this->add(new ChoiceField('minute', array(
  92. 'choice_list' => new MinuteChoiceList($this->getOption('minutes')),
  93. )));
  94. if ($this->getOption('with_seconds')) {
  95. $this->add(new ChoiceField('second', array(
  96. 'choice_list' => new SecondChoiceList($this->getOption('seconds')),
  97. )));
  98. }
  99. }
  100. $fields = array('hour', 'minute');
  101. if ($this->getOption('with_seconds')) {
  102. $fields[] = 'second';
  103. }
  104. if ($this->getOption('type') == self::STRING) {
  105. $this->setNormalizationTransformer(new ReversedTransformer(
  106. new DateTimeToStringTransformer(array(
  107. 'format' => 'H:i:s',
  108. 'input_timezone' => $this->getOption('data_timezone'),
  109. 'output_timezone' => $this->getOption('data_timezone'),
  110. ))
  111. ));
  112. } else if ($this->getOption('type') == self::TIMESTAMP) {
  113. $this->setNormalizationTransformer(new ReversedTransformer(
  114. new DateTimeToTimestampTransformer(array(
  115. 'input_timezone' => $this->getOption('data_timezone'),
  116. 'output_timezone' => $this->getOption('data_timezone'),
  117. ))
  118. ));
  119. } else if ($this->getOption('type') === self::RAW) {
  120. $this->setNormalizationTransformer(new ReversedTransformer(
  121. new DateTimeToArrayTransformer(array(
  122. 'input_timezone' => $this->getOption('data_timezone'),
  123. 'output_timezone' => $this->getOption('data_timezone'),
  124. 'fields' => $fields,
  125. ))
  126. ));
  127. }
  128. $this->setValueTransformer(new DateTimeToArrayTransformer(array(
  129. 'input_timezone' => $this->getOption('data_timezone'),
  130. 'output_timezone' => $this->getOption('user_timezone'),
  131. // if the field is rendered as choice field, the values should be trimmed
  132. // of trailing zeros to render the selected choices correctly
  133. 'pad' => $this->getOption('widget') == self::INPUT,
  134. 'fields' => $fields,
  135. )));
  136. }
  137. public function isField()
  138. {
  139. return self::INPUT === $this->getOption('widget');
  140. }
  141. public function isWithSeconds()
  142. {
  143. return $this->getOption('with_seconds');
  144. }
  145. /**
  146. * Generates an array of choices for the given values
  147. *
  148. * If the values are shorter than $padLength characters, they are padded with
  149. * zeros on the left side.
  150. *
  151. * @param array $values The available choices
  152. * @param integer $padLength The length to pad the choices
  153. * @return array An array with the input values as keys and the
  154. * padded values as values
  155. */
  156. protected function generatePaddedChoices(array $values, $padLength)
  157. {
  158. $choices = array();
  159. foreach ($values as $value) {
  160. $choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT);
  161. }
  162. return $choices;
  163. }
  164. /**
  165. * Returns whether the hour of the field's data is valid
  166. *
  167. * The hour is valid if it is contained in the list passed to the field's
  168. * option "hours".
  169. *
  170. * @return Boolean
  171. */
  172. public function isHourWithinRange()
  173. {
  174. $date = $this->getNormalizedData();
  175. return $this->isEmpty() || $this->get('hour')->isEmpty()
  176. || in_array($date->format('H'), $this->getOption('hours'));
  177. }
  178. /**
  179. * Returns whether the minute of the field's data is valid
  180. *
  181. * The minute is valid if it is contained in the list passed to the field's
  182. * option "minutes".
  183. *
  184. * @return Boolean
  185. */
  186. public function isMinuteWithinRange()
  187. {
  188. $date = $this->getNormalizedData();
  189. return $this->isEmpty() || $this->get('minute')->isEmpty()
  190. || in_array($date->format('i'), $this->getOption('minutes'));
  191. }
  192. /**
  193. * Returns whether the second of the field's data is valid
  194. *
  195. * The second is valid if it is contained in the list passed to the field's
  196. * option "seconds".
  197. *
  198. * @return Boolean
  199. */
  200. public function isSecondWithinRange()
  201. {
  202. $date = $this->getNormalizedData();
  203. return $this->isEmpty() || !$this->has('second') || $this->get('second')->isEmpty()
  204. || in_array($date->format('s'), $this->getOption('seconds'));
  205. }
  206. /**
  207. * Returns whether the field is neither completely filled (a selected
  208. * value in each dropdown) nor completely empty
  209. *
  210. * @return Boolean
  211. */
  212. public function isPartiallyFilled()
  213. {
  214. if ($this->isField()) {
  215. return false;
  216. }
  217. if ($this->isEmpty()) {
  218. return false;
  219. }
  220. if ($this->get('hour')->isEmpty() || $this->get('minute')->isEmpty()
  221. || ($this->isWithSeconds() && $this->get('second')->isEmpty())) {
  222. return true;
  223. }
  224. return false;
  225. }
  226. }