Skip to content

Commit

Permalink
added addDate(), addTime() & addDateTime()
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 23, 2023
1 parent d27d857 commit a6eee1f
Show file tree
Hide file tree
Showing 7 changed files with 529 additions and 9 deletions.
30 changes: 30 additions & 0 deletions src/Forms/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,36 @@ public function addFloat(string $name, $label = null): Controls\TextInput
}


/**
* Adds input for date selection.
* @param string|object|null $label
*/
public function addDate(string $name, $label = null): Controls\DateTimeControl
{
return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::Date);
}


/**
* Adds input for time selection.
* @param string|object|null $label
*/
public function addTime(string $name, $label = null, bool $withSeconds = false): Controls\DateTimeControl
{
return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::Time, $withSeconds);
}


/**
* Adds input for date and time selection.
* @param string|object|null $label
*/
public function addDateTime(string $name, $label = null, bool $withSeconds = false): Controls\DateTimeControl
{
return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::DateTime, $withSeconds);
}


/**
* Adds control that allows the user to upload files.
* @param string|object|null $label
Expand Down
164 changes: 164 additions & 0 deletions src/Forms/Controls/DateTimeControl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Forms\Controls;

use Nette;
use Nette\Forms\Form;


/**
* Selects date or time or date & time.
*/
class DateTimeControl extends BaseControl
{
public const
Date = 1,
Time = 2,
DateTime = 3;

public const
FormatObject = 'object',
FormatTimestamp = 'timestamp';

/** @var int */
private $mode;

/** @var bool */
private $withSeconds;

/** @var string */
private $format = self::FormatObject;


public function __construct($label = null, int $mode = self::Date, bool $withSeconds = false)
{
$this->mode = $mode;
$this->withSeconds = $withSeconds;
parent::__construct($label);
$this->control->step = $withSeconds ? 1 : null;
}


/**
* Format of returned value. Allowed values are string (ie 'Y-m-d'), self::FormatObject and self::FormatTimestamp.
* @return static
*/
public function setFormat(string $format)
{
$this->format = $format;
return $this;
}


/**
* @param \DateTimeInterface|string|int|null $value
* @return static
*/
public function setValue($value)
{
$this->value = $value === null ? null : $this->normalize($value);
return $this;
}


/**
* @return \DateTimeImmutable|string|int|null
*/
public function getValue()
{
if ($this->format === self::FormatObject) {
return $this->value;
} elseif ($this->format === self::FormatTimestamp) {
return $this->value ? $this->value->getTimestamp() : null;
} else {
return $this->value ? $this->value->format($this->format) : null;
}
}


/**
* @param \DateTimeInterface|string|int $value
*/
private function normalize($value): \DateTimeImmutable
{
if (is_numeric($value)) {
$dt = (new \DateTimeImmutable)->setTimestamp((int) $value);
} elseif (is_string($value)) {
$dt = new \DateTimeImmutable($value); // createFromFormat() must not be used because it allows invalid values
} elseif ($value instanceof \DateTime) {
$dt = \DateTimeImmutable::createFromMutable($value);
} elseif ($value instanceof \DateTimeImmutable) {
$dt = $value;
} elseif (!$value instanceof \DateTimeInterface) {
throw new Nette\InvalidArgumentException('Value must be DateTimeInterface or string or null, ' . gettype($value) . ' given.');
}

[$h, $m, $s] = [(int) $dt->format('H'), (int) $dt->format('i'), $this->withSeconds ? (int) $dt->format('s') : 0];
if ($this->mode === self::Date) {
return $dt->setTime(0, 0);
} elseif ($this->mode === self::Time) {
return $dt->setDate(0, 1, 1)->setTime($h, $m, $s);
} elseif ($this->mode === self::DateTime) {
return $dt->setTime($h, $m, $s);
}
}


public function loadHttpData(): void
{
$value = $this->getHttpData(Nette\Forms\Form::DataText);
try {
$this->value = is_string($value) && preg_match('~^(\d{4}-\d{2}-\d{2})?T?(\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?$~', $value)
? $this->normalize($value)
: null;
} catch (\Throwable $e) {
$this->value = null;
}
}


public function getControl(): Nette\Utils\Html
{
return parent::getControl()->addAttributes([
'value' => $this->value ? $this->formatHtmlValue($this->value) : null,
'type' => [self::Date => 'date', self::Time => 'time', self::DateTime => 'datetime-local'][$this->mode],
]);
}


private function formatHtmlValue(\DateTimeInterface $dt): string
{
return $dt->format([
self::Date => 'Y-m-d',
self::Time => $this->withSeconds ? 'H:i:s' : 'H:i',
self::DateTime => $this->withSeconds ? 'Y-m-d\\TH:i:s' : 'Y-m-d\\TH:i',
][$this->mode]);
}


/** @return static */
public function addRule($validator, $errorMessage = null, $arg = null)
{
if ($validator === Form::Min) {
$this->control->min = $arg = $this->formatHtmlValue($this->normalize($arg));
} elseif ($validator === Form::Max) {
$this->control->max = $arg = $this->formatHtmlValue($this->normalize($arg));
} elseif ($validator === Form::Range) {
$this->control->min = isset($arg[0])
? $arg[0] = $this->formatHtmlValue($this->normalize($arg[0]))
: null;
$this->control->max = isset($arg[1])
? $arg[1] = $this->formatHtmlValue($this->normalize($arg[1]))
: null;
}

return parent::addRule($validator, $errorMessage, $arg);
}
}
44 changes: 44 additions & 0 deletions tests/Forms/Controls.DateTimeControl.format.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/**
* Test: Nette\Forms\Controls\DateTimeControl.
*/

declare(strict_types=1);

use Nette\Forms\Controls\DateTimeControl;
use Nette\Forms\Form;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


test('string format', function () {
$form = new Form;
$input = $form->addDate('date')
->setValue('2023-10-22 10:30')
->setFormat('j.n.Y');

Assert::same('22.10.2023', $input->getValue());
});


test('timestamp format', function () {
$form = new Form;
$input = $form->addDate('date')
->setValue('2023-10-22 10:30')
->setFormat(DateTimeControl::FormatTimestamp);

Assert::same(1697925600, $input->getValue());
});


test('object format', function () {
$form = new Form;
$input = $form->addDate('date')
->setValue('2023-10-22 10:30')
->setFormat(DateTimeControl::FormatObject);

Assert::equal(new DateTimeImmutable('2023-10-22 00:00'), $input->getValue());
});
110 changes: 110 additions & 0 deletions tests/Forms/Controls.DateTimeControl.loadData.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

/**
* Test: Nette\Forms\Controls\DateTimeControl.
*/

declare(strict_types=1);

use Nette\Forms\Form;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


before(function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $_FILES = [];
$_COOKIE[Nette\Http\Helpers::STRICT_COOKIE_NAME] = '1';
Form::initialize(true);
});


test('not present', function () {
$form = new Form;
$input = $form->addDate('unknown');
Assert::null($input->getValue());
Assert::false($input->isFilled());
});


test('invalid data', function () {
$_POST = ['malformed' => ['']];
$form = new Form;
$input = $form->addDate('malformed');
Assert::null($input->getValue());
Assert::false($input->isFilled());
});


test('invalid format', function () {
$_POST = ['text' => 'invalid'];
$form = new Form;
$input = $form->addDate('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
});


test('invalid date', function () {
$_POST = ['date' => '2023-13-22'];
$form = new Form;
$input = $form->addDate('date');
Assert::null($input->getValue());
Assert::false($input->isFilled());
});


test('invalid time', function () {
$_POST = ['time' => '10:60'];
$form = new Form;
$input = $form->addTime('time');
Assert::null($input->getValue());
Assert::false($input->isFilled());
});


test('valid date', function () {
$_POST = ['date' => '2023-10-22'];
$form = new Form;
$input = $form->addDate('date');
Assert::equal(new DateTimeImmutable('2023-10-22 00:00'), $input->getValue());
Assert::true($input->isFilled());
});


test('valid time', function () {
$_POST = ['time' => '10:22:33.44'];
$form = new Form;
$input = $form->addTime('time');
Assert::equal(new DateTimeImmutable('0000-01-01 10:22'), $input->getValue());
Assert::true($input->isFilled());
});


test('valid time with seconds', function () {
$_POST = ['time' => '10:22:33.44'];
$form = new Form;
$input = $form->addTime('time', null, true);
Assert::equal(new DateTimeImmutable('0000-01-01 10:22:33'), $input->getValue());
Assert::true($input->isFilled());
});


test('valid date-time', function () {
$_POST = ['date' => '2023-10-22T10:23:11.123'];
$form = new Form;
$input = $form->addDateTime('date');
Assert::equal(new DateTimeImmutable('2023-10-22 10:23:00'), $input->getValue());
Assert::true($input->isFilled());
});


test('valid date-time with seconds', function () {
$_POST = ['date' => '2023-10-22T10:23:11.123'];
$form = new Form;
$input = $form->addDateTime('date', null, true);
Assert::equal(new DateTimeImmutable('2023-10-22 10:23:11'), $input->getValue());
Assert::true($input->isFilled());
});
Loading

0 comments on commit a6eee1f

Please sign in to comment.