mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
API Rewrite Date and Time fields to support HTML5
This commit is contained in:
commit
61388b153f
@ -1397,6 +1397,7 @@ The below methods have been added or had their functionality updated to `DBDate`
|
||||
* `getFormatter` method added, which returns a locale-specific date/time formatter.
|
||||
|
||||
`DBTime` specific changes:
|
||||
|
||||
* Added `DBTime::FormatFromSettings`
|
||||
|
||||
#### <a name="overview-orm-removed"></a>ORM Removed API
|
||||
@ -1670,6 +1671,17 @@ New `DatetimeField` methods replace `getConfig()` / `setConfig()`:
|
||||
* `getLocale()` / `setLocale()`
|
||||
* `datavaluefield` config is removed as internal data value is now fixed to ISO 8601 format
|
||||
|
||||
The `DatetimeField` has changed behaviour:
|
||||
|
||||
* It uses a combined input instead of a composite from `DateField` and `TimeField`
|
||||
Consequently, `getDateField()` and `getTimeField()` have been removed.
|
||||
* It returns [ISO 8601 normalised dates](https://html.spec.whatwg.org/multipage/infrastructure.html#local-dates-and-times)
|
||||
by default in `Value()`, which include a "T" separator between date and time.
|
||||
This is required to allow HTML5 input. Either use `setHTML5(false)` to set your custom format,
|
||||
or use `dataValue()` to retrieve a whitespace separated representation.
|
||||
* It no longer accepts `setValue()` as an array with 'date' and 'time' keys
|
||||
* Added `getHTML5()` / `setHTML5()`
|
||||
|
||||
New `DateField` methods replace `getConfig()` / `setConfig()`:
|
||||
|
||||
* `getDateFormat()` / `setDateFormat()`
|
||||
|
@ -57,6 +57,10 @@ en:
|
||||
VALIDDATEFORMAT2: 'Please enter a valid date format ({format})'
|
||||
VALIDDATEMAXDATE: 'Your date has to be older or matching the maximum allowed date ({date})'
|
||||
VALIDDATEMINDATE: 'Your date has to be newer or matching the minimum allowed date ({date})'
|
||||
DatetimeField:
|
||||
VALIDDATETIMEFORMAT: 'Please enter a valid date and time format ({format})'
|
||||
VALIDDATEMAXDATETIME: 'Your date has to be older or matching the maximum allowed date and time ({datetime})'
|
||||
VALIDDATEMINDATETIME: 'Your date has to be newer or matching the minimum allowed date and time ({datetime})'
|
||||
Director:
|
||||
INVALID_REQUEST: 'Invalid request'
|
||||
DropdownField:
|
||||
|
@ -7,6 +7,7 @@ use SilverStripe\i18n\i18n;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\ORM\FieldType\DBDate;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
|
||||
/**
|
||||
* Form used for editing a date stirng
|
||||
@ -194,7 +195,7 @@ class DateField extends TextField
|
||||
}
|
||||
|
||||
// Get from locale
|
||||
return $this->getFormatter()->getPattern();
|
||||
return $this->getFrontendFormatter()->getPattern();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,7 +218,7 @@ class DateField extends TextField
|
||||
* @throws \LogicException
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getFormatter()
|
||||
protected function getFrontendFormatter()
|
||||
{
|
||||
if ($this->getHTML5() && $this->dateFormat && $this->dateFormat !== DBDate::ISO_DATE) {
|
||||
throw new \LogicException(
|
||||
@ -261,7 +262,7 @@ class DateField extends TextField
|
||||
*
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getISO8601Formatter()
|
||||
protected function getInternalFormatter()
|
||||
{
|
||||
$locale = i18n::config()->uninherited('default_locale');
|
||||
$formatter = IntlDateFormatter::create(
|
||||
@ -290,6 +291,19 @@ class DateField extends TextField
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function getSchemaDataDefaults()
|
||||
{
|
||||
$defaults = parent::getSchemaDataDefaults();
|
||||
return array_merge($defaults, [
|
||||
'lang' => i18n::convert_rfc1766($this->getLocale()),
|
||||
'data' => array_merge($defaults['data'], [
|
||||
'html5' => $this->getHTML5(),
|
||||
'min' => $this->getMinDate(),
|
||||
'max' => $this->getMaxDate()
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
public function Type()
|
||||
{
|
||||
return 'date text';
|
||||
@ -314,10 +328,19 @@ class DateField extends TextField
|
||||
}
|
||||
|
||||
// Parse from submitted value
|
||||
$this->value = $this->localisedToISO8601($value);
|
||||
$this->value = $this->frontendToInternal($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign value based on {@link $datetimeFormat}, which might be localised.
|
||||
*
|
||||
* When $html5=true, assign value from ISO 8601 string.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $data
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value, $data = null)
|
||||
{
|
||||
// Save raw value for later validation
|
||||
@ -329,18 +352,14 @@ class DateField extends TextField
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
throw new InvalidArgumentException("Use setSubmittedValue to assign by array");
|
||||
}
|
||||
|
||||
// Re-run through formatter to tidy up (e.g. remove time component)
|
||||
$this->value = $this->tidyISO8601($value);
|
||||
$this->value = $this->tidyInternal($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function Value()
|
||||
{
|
||||
return $this->iso8601ToLocalised($this->value);
|
||||
return $this->internalToFrontend($this->value);
|
||||
}
|
||||
|
||||
public function performReadonlyTransformation()
|
||||
@ -385,8 +404,16 @@ class DateField extends TextField
|
||||
_t(
|
||||
'DateField.VALIDDATEMINDATE',
|
||||
"Your date has to be newer or matching the minimum allowed date ({date})",
|
||||
['date' => $this->iso8601ToLocalised($min)]
|
||||
)
|
||||
[
|
||||
'date' => sprintf(
|
||||
'<time datetime="%s">%s</time>',
|
||||
$min,
|
||||
$this->internalToFrontend($min)
|
||||
)
|
||||
]
|
||||
),
|
||||
ValidationResult::TYPE_ERROR,
|
||||
ValidationResult::CAST_HTML
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -402,8 +429,16 @@ class DateField extends TextField
|
||||
_t(
|
||||
'DateField.VALIDDATEMAXDATE',
|
||||
"Your date has to be older or matching the maximum allowed date ({date})",
|
||||
['date' => $this->iso8601ToLocalised($max)]
|
||||
)
|
||||
[
|
||||
'date' => sprintf(
|
||||
'<time datetime="%s">%s</time>',
|
||||
$max,
|
||||
$this->internalToFrontend($max)
|
||||
)
|
||||
]
|
||||
),
|
||||
ValidationResult::TYPE_ERROR,
|
||||
ValidationResult::CAST_HTML
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -457,7 +492,7 @@ class DateField extends TextField
|
||||
*/
|
||||
public function setMinDate($minDate)
|
||||
{
|
||||
$this->minDate = $this->tidyISO8601($minDate);
|
||||
$this->minDate = $this->tidyInternal($minDate);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -475,23 +510,24 @@ class DateField extends TextField
|
||||
*/
|
||||
public function setMaxDate($maxDate)
|
||||
{
|
||||
$this->maxDate = $this->tidyISO8601($maxDate);
|
||||
$this->maxDate = $this->tidyInternal($maxDate);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert date localised in the current locale to ISO 8601 date
|
||||
* Convert frontend date to the internal representation (ISO 8601).
|
||||
* The frontend date is also in ISO 8601 when $html5=true.
|
||||
*
|
||||
* @param string $date
|
||||
* @return string The formatted date, or null if not a valid date
|
||||
*/
|
||||
public function localisedToISO8601($date)
|
||||
protected function frontendToInternal($date)
|
||||
{
|
||||
if (!$date) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getFormatter();
|
||||
$toFormatter = $this->getISO8601Formatter();
|
||||
$fromFormatter = $this->getFrontendFormatter();
|
||||
$toFormatter = $this->getInternalFormatter();
|
||||
$timestamp = $fromFormatter->parse($date);
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
@ -500,20 +536,21 @@ class DateField extends TextField
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ISO 8601 localised date into the format specified by the
|
||||
* current date format.
|
||||
* Convert the internal date representation (ISO 8601) to a format used by the frontend,
|
||||
* as defined by {@link $dateFormat}. With $html5=true, the frontend date will also be
|
||||
* in ISO 8601.
|
||||
*
|
||||
* @param string $date
|
||||
* @return string The formatted date, or null if not a valid date
|
||||
*/
|
||||
public function iso8601ToLocalised($date)
|
||||
protected function internalToFrontend($date)
|
||||
{
|
||||
$date = $this->tidyISO8601($date);
|
||||
$date = $this->tidyInternal($date);
|
||||
if (!$date) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getISO8601Formatter();
|
||||
$toFormatter = $this->getFormatter();
|
||||
$fromFormatter = $this->getInternalFormatter();
|
||||
$toFormatter = $this->getFrontendFormatter();
|
||||
$timestamp = $fromFormatter->parse($date);
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
@ -522,18 +559,19 @@ class DateField extends TextField
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidy up iso8601-ish date, or approximation
|
||||
* Tidy up the internal date representation (ISO 8601),
|
||||
* and fall back to strtotime() if there's parsing errors.
|
||||
*
|
||||
* @param string $date Date in iso8601 or approximate form
|
||||
* @return string iso8601 date, or null if not valid
|
||||
* @param string $date Date in ISO 8601 or approximate form
|
||||
* @return string ISO 8601 date, or null if not valid
|
||||
*/
|
||||
public function tidyISO8601($date)
|
||||
protected function tidyInternal($date)
|
||||
{
|
||||
if (!$date) {
|
||||
return null;
|
||||
}
|
||||
// Re-run through formatter to tidy up (e.g. remove time component)
|
||||
$formatter = $this->getISO8601Formatter();
|
||||
$formatter = $this->getInternalFormatter();
|
||||
$timestamp = $formatter->parse($date);
|
||||
if ($timestamp === false) {
|
||||
// Fallback to strtotime
|
||||
|
@ -5,156 +5,314 @@ namespace SilverStripe\Forms;
|
||||
use IntlDateFormatter;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
|
||||
/**
|
||||
* A composite field for date and time entry,
|
||||
* based on {@link DateField} and {@link TimeField}.
|
||||
* Usually saves into a single {@link DBDateTime} database column.
|
||||
* If you want to save into {@link Date} or {@link Time} columns,
|
||||
* please instanciate the fields separately.
|
||||
*
|
||||
* This field does not implement the <input type="datetime-local"> HTML5 field,
|
||||
* but can use date and time HTML5 inputs separately (through {@link DateField->setHTML5()}
|
||||
* and {@link TimeField->setHTML5()}.
|
||||
*
|
||||
* # Configuration
|
||||
*
|
||||
* Individual options are configured either on the DatetimeField, or on individual
|
||||
* sub-fields accessed via getDateField() or getTimeField()
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* $field = new DatetimeField('Name', 'Label');
|
||||
* $field->getDateField()->setTitle('Select Date');
|
||||
* </code>
|
||||
*
|
||||
* - setLocale(): Sets a custom locale for date / time formatting.
|
||||
* - setTimezone(): Set a different timezone for viewing. {@link dataValue()} will still save
|
||||
* the time in PHP's default timezone (date_default_timezone_get()), its only a view setting.
|
||||
* Note that the sub-fields ({@link getDateField()} and {@link getTimeField()})
|
||||
* are not timezone aware, and will have their values set in local time, rather than server time.
|
||||
* - setDateTimeOrder(): An sprintf() template to determine in which order the date and time values will
|
||||
* be combined. This is necessary as those separate formats are set in their invididual fields.
|
||||
* Form field used for editing date time strings.
|
||||
* In the default HTML5 mode, the field expects form submissions
|
||||
* in normalised ISO 8601 format, for example 2017-04-26T23:59:59 (with a "T" separator).
|
||||
* Data is passed on via {@link dataValue()} with whitespace separators.
|
||||
* The {@link $value} property is always in ISO 8601 format, in the server timezone.
|
||||
*/
|
||||
class DatetimeField extends FormField
|
||||
class DatetimeField extends TextField
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DateField
|
||||
* @var bool
|
||||
*/
|
||||
protected $dateField = null;
|
||||
protected $html5 = true;
|
||||
|
||||
/**
|
||||
* @var TimeField
|
||||
*/
|
||||
protected $timeField = null;
|
||||
|
||||
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
|
||||
|
||||
/**
|
||||
* Date time order
|
||||
* Override locale. If empty will default to current locale
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dateTimeOrder = '{date} {time}';
|
||||
protected $locale = null;
|
||||
|
||||
public function __construct($name, $title = null, $value = "")
|
||||
/**
|
||||
* Min date time
|
||||
*
|
||||
* @var string ISO 8601 date time in server timezone
|
||||
*/
|
||||
protected $minDatetime = null;
|
||||
|
||||
/**
|
||||
* Max date time
|
||||
*
|
||||
* @var string ISO 860 date time in server timezone
|
||||
*/
|
||||
protected $maxDatetime = null;
|
||||
|
||||
/**
|
||||
* Override date format. If empty will default to that used by the current locale.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected $datetimeFormat = null;
|
||||
|
||||
/**
|
||||
* Length of this date (full, short, etc).
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
* @var int
|
||||
*/
|
||||
protected $dateLength = null;
|
||||
|
||||
/**
|
||||
* Length of this time (full, short, etc).
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
* @var int
|
||||
*/
|
||||
protected $timeLength = null;
|
||||
|
||||
/**
|
||||
* Unparsed value, used exclusively for comparing with internal value
|
||||
* to detect invalid values.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $rawValue = null;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
|
||||
|
||||
/**
|
||||
* Custom timezone
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $timezone = null;
|
||||
|
||||
public function getAttributes()
|
||||
{
|
||||
$this->timeField = TimeField::create($name . '[time]', false);
|
||||
$this->dateField = DateField::create($name . '[date]', false);
|
||||
parent::__construct($name, $title, $value);
|
||||
$attributes = parent::getAttributes();
|
||||
|
||||
$attributes['lang'] = i18n::convert_rfc1766($this->getLocale());
|
||||
|
||||
if ($this->getHTML5()) {
|
||||
$attributes['type'] = 'datetime-local';
|
||||
$attributes['min'] = $this->internalToFrontend($this->getMinDatetime());
|
||||
$attributes['max'] = $this->internalToFrontend($this->getMaxDatetime());
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function setForm($form)
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSchemaDataDefaults()
|
||||
{
|
||||
parent::setForm($form);
|
||||
$this->dateField->setForm($form);
|
||||
$this->timeField->setForm($form);
|
||||
return $this;
|
||||
$defaults = parent::getSchemaDataDefaults();
|
||||
return array_merge($defaults, [
|
||||
'lang' => i18n::convert_rfc1766($this->getLocale()),
|
||||
'data' => array_merge($defaults['data'], [
|
||||
'html5' => $this->getHTML5(),
|
||||
'min' => $this->internalToFrontend($this->getMinDatetime()),
|
||||
'max' => $this->internalToFrontend($this->getMaxDatetime())
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function Type()
|
||||
{
|
||||
parent::setName($name);
|
||||
$this->dateField->setName($name . '[date]');
|
||||
$this->timeField->setName($name . '[time]');
|
||||
return 'text datetime';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHTML5()
|
||||
{
|
||||
return $this->html5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $bool
|
||||
* @return $this
|
||||
*/
|
||||
public function setHTML5($bool)
|
||||
{
|
||||
$this->html5 = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value from a submitted form array
|
||||
* Assign value posted from form submission, based on {@link $datetimeFormat}.
|
||||
* When $html5=true, this needs to be normalised ISO format (with "T" separator).
|
||||
*
|
||||
* @param array $value Expected submission value is either an empty value,
|
||||
* or an array with the necessary components keyed against 'date' and 'time', each value
|
||||
* localised according to each's localisation setting.
|
||||
* @param mixed $value
|
||||
* @param mixed $data
|
||||
* @return $this
|
||||
*/
|
||||
public function setSubmittedValue($value, $data = null)
|
||||
{
|
||||
// Empty value
|
||||
if (empty($value)) {
|
||||
// Save raw value for later validation
|
||||
$this->rawValue = $value;
|
||||
|
||||
// Null case
|
||||
if (!$value) {
|
||||
$this->value = null;
|
||||
$this->dateField->setValue(null);
|
||||
$this->timeField->setValue(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Validate value is submitted in array format
|
||||
if (!is_array($value)) {
|
||||
throw new InvalidArgumentException("Value is not submitted array");
|
||||
}
|
||||
// Parse from submitted value
|
||||
$this->value = $this->frontendToInternal($value);
|
||||
|
||||
// Save each field, and convert from array to iso8601 string
|
||||
$this->dateField->setSubmittedValue($value['date'], $value);
|
||||
$this->timeField->setSubmittedValue($value['time'], $value);
|
||||
|
||||
// Combine date components back into iso8601 string for the root value
|
||||
$this->value = $this->dataValue();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatter for converting to the target timezone, if timezone is set
|
||||
* Can return null if no timezone set
|
||||
* Convert frontend date to the internal representation (ISO 8601).
|
||||
* The frontend date is also in ISO 8601 when $html5=true.
|
||||
* Assumes the value is in the defined {@link $timezone} (if one is set),
|
||||
* and adjusts for server timezone.
|
||||
*
|
||||
* @return IntlDateFormatter|null
|
||||
* @param string $datetime
|
||||
* @return string The formatted date, or null if not a valid date
|
||||
*/
|
||||
protected function getTimezoneFormatter()
|
||||
public function frontendToInternal($datetime)
|
||||
{
|
||||
$timezone = $this->getTimezone();
|
||||
if (!$timezone) {
|
||||
if (!$datetime) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getFrontendFormatter();
|
||||
$toFormatter = $this->getInternalFormatter();
|
||||
|
||||
// Build new formatter with the altered timezone
|
||||
$formatter = clone $this->getISO8601Formatter();
|
||||
$formatter->setTimeZone($timezone);
|
||||
// Try to parse time with seconds
|
||||
$timestamp = $fromFormatter->parse($datetime);
|
||||
|
||||
// Try to parse time without seconds, since that's a valid HTML5 submission format
|
||||
// See https://html.spec.whatwg.org/multipage/infrastructure.html#times
|
||||
if ($timestamp === false && $this->getHTML5()) {
|
||||
$fromFormatter->setPattern(str_replace(':ss', '', $fromFormatter->getPattern()));
|
||||
$timestamp = $fromFormatter->parse($datetime);
|
||||
}
|
||||
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
}
|
||||
return $toFormatter->format($timestamp) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date formatter with the standard locale / date format
|
||||
*
|
||||
* @throws \LogicException
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getFrontendFormatter()
|
||||
{
|
||||
if ($this->getHTML5() && $this->datetimeFormat && $this->datetimeFormat !== DBDatetime::ISO_DATETIME_NORMALISED) {
|
||||
throw new \LogicException(
|
||||
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setDatetimeFormat()'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getHTML5() && $this->dateLength) {
|
||||
throw new \LogicException(
|
||||
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setDateLength()'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getHTML5() && $this->locale) {
|
||||
throw new \LogicException(
|
||||
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setLocale()'
|
||||
);
|
||||
}
|
||||
|
||||
$formatter = IntlDateFormatter::create(
|
||||
$this->getLocale(),
|
||||
$this->getDateLength(),
|
||||
$this->getTimeLength(),
|
||||
$this->getTimezone()
|
||||
);
|
||||
|
||||
if ($this->getHTML5()) {
|
||||
// Browsers expect ISO 8601 dates, localisation is handled on the client.
|
||||
// Add 'T' date and time separator to create W3C compliant format
|
||||
$formatter->setPattern(DBDatetime::ISO_DATETIME_NORMALISED);
|
||||
} elseif ($this->datetimeFormat) {
|
||||
// Don't invoke getDatetimeFormat() directly to avoid infinite loop
|
||||
$ok = $formatter->setPattern($this->datetimeFormat);
|
||||
if (!$ok) {
|
||||
throw new InvalidArgumentException("Invalid date format {$this->datetimeFormat}");
|
||||
}
|
||||
}
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date format in CLDR standard format
|
||||
*
|
||||
* This can be set explicitly. If not, this will be generated from the current locale
|
||||
* with the current date length.
|
||||
*
|
||||
* @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table
|
||||
*/
|
||||
public function getDatetimeFormat()
|
||||
{
|
||||
if ($this->datetimeFormat) {
|
||||
return $this->datetimeFormat;
|
||||
}
|
||||
|
||||
// Get from locale
|
||||
return $this->getFrontendFormatter()->getPattern();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set date format in CLDR standard format.
|
||||
* Only applicable with {@link setHTML5(false)}.
|
||||
*
|
||||
* @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table
|
||||
* @param string $format
|
||||
* @return $this
|
||||
*/
|
||||
public function setDatetimeFormat($format)
|
||||
{
|
||||
$this->datetimeFormat = $format;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a date formatter for the ISO 8601 format
|
||||
*
|
||||
* @param String $timezone Optional timezone identifier (defaults to server timezone)
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getISO8601Formatter()
|
||||
protected function getInternalFormatter($timezone = null)
|
||||
{
|
||||
if (!$timezone) {
|
||||
$timezone = date_default_timezone_get(); // Default to server timezone
|
||||
}
|
||||
|
||||
$formatter = IntlDateFormatter::create(
|
||||
i18n::config()->uninherited('default_locale'),
|
||||
IntlDateFormatter::MEDIUM,
|
||||
IntlDateFormatter::MEDIUM,
|
||||
date_default_timezone_get() // Default to server timezone
|
||||
$timezone
|
||||
);
|
||||
$formatter->setLenient(false);
|
||||
// CLDR iso8601 date.
|
||||
// Note we omit timezone from this format, and we assume server TZ always.
|
||||
$formatter->setPattern('y-MM-dd HH:mm:ss');
|
||||
|
||||
// Note we omit timezone from this format, and we always assume server TZ
|
||||
$formatter->setPattern(DBDatetime::ISO_DATETIME);
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign value from iso8601 string
|
||||
* Assign value based on {@link $datetimeFormat}, which might be localised.
|
||||
* The value needs to be in the server timezone.
|
||||
*
|
||||
* When $html5=true, assign value from ISO 8601 normalised string (with a "T" separator).
|
||||
* Falls back to an ISO 8601 string (with a whitespace separator).
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $data
|
||||
@ -162,170 +320,181 @@ class DatetimeField extends FormField
|
||||
*/
|
||||
public function setValue($value, $data = null)
|
||||
{
|
||||
// Save raw value for later validation
|
||||
$this->rawValue = $value;
|
||||
|
||||
// Empty value
|
||||
if (empty($value)) {
|
||||
$this->value = null;
|
||||
$this->dateField->setValue(null);
|
||||
$this->timeField->setValue(null);
|
||||
return $this;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
throw new InvalidArgumentException("Use setSubmittedValue to assign by array");
|
||||
};
|
||||
|
||||
// Validate iso 8601 date
|
||||
// If invalid, assign for later validation failure
|
||||
$isoFormatter = $this->getISO8601Formatter();
|
||||
$timestamp = $isoFormatter->parse($value);
|
||||
$internalFormatter = $this->getInternalFormatter();
|
||||
$timestamp = $internalFormatter->parse($value);
|
||||
|
||||
// Retry without "T" separator
|
||||
if (!$timestamp) {
|
||||
$fallbackFormatter = $this->getInternalFormatter();
|
||||
$fallbackFormatter->setPattern(DBDatetime::ISO_DATETIME);
|
||||
$timestamp = $fallbackFormatter->parse($value);
|
||||
}
|
||||
|
||||
if ($timestamp === false) {
|
||||
$this->dateField->setSubmittedValue($value);
|
||||
$this->timeField->setValue(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Cleanup date
|
||||
$value = $isoFormatter->format($timestamp);
|
||||
$value = $internalFormatter->format($timestamp);
|
||||
|
||||
// Save value
|
||||
$this->value = $value;
|
||||
|
||||
// Shift iso date into timezone before assignment to subfields
|
||||
$timezoneFormatter = $this->getTimezoneFormatter();
|
||||
if ($timezoneFormatter) {
|
||||
$value = $timezoneFormatter->format($timestamp);
|
||||
}
|
||||
|
||||
// Set date / time components, which are unaware of their timezone
|
||||
list($date, $time) = explode(' ', $value);
|
||||
$this->dateField->setValue($date, $data);
|
||||
$this->timeField->setValue($time, $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* localised time value
|
||||
* Returns the frontend representation of the field value,
|
||||
* according to the defined {@link dateFormat}.
|
||||
* With $html5=true, this will be in ISO 8601 format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function Value()
|
||||
{
|
||||
$date = $this->dateField->Value();
|
||||
$time = $this->timeField->Value();
|
||||
return $this->joinDateTime($date, $time);
|
||||
return $this->internalToFrontend($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param string $time
|
||||
* @return string
|
||||
*/
|
||||
protected function joinDateTime($date, $time)
|
||||
{
|
||||
$format = $this->getDateTimeOrder();
|
||||
return strtr($format, [
|
||||
'{date}' => $date,
|
||||
'{time}' => $time
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ISO8601 formatted string in the local server timezone
|
||||
* Convert the internal date representation (ISO 8601) to a format used by the frontend,
|
||||
* as defined by {@link $dateFormat}. With $html5=true, the frontend date will also be
|
||||
* in ISO 8601.
|
||||
*
|
||||
* @return string|null
|
||||
* @param string $datetime
|
||||
* @return string The formatted date and time, or null if not a valid date and time
|
||||
*/
|
||||
public function dataValue()
|
||||
public function internalToFrontend($datetime)
|
||||
{
|
||||
// No date means no value (even if time is specified)
|
||||
$dateDataValue = $this->getDateField()->dataValue();
|
||||
if (empty($dateDataValue)) {
|
||||
$datetime = $this->tidyInternal($datetime);
|
||||
if (!$datetime) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getInternalFormatter();
|
||||
$toFormatter = $this->getFrontendFormatter();
|
||||
$timestamp = $fromFormatter->parse($datetime);
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build iso8601 timestamp from combined date and time
|
||||
$timeDataValue = $this->getTimeField()->dataValue() ?: '00:00:00';
|
||||
$value = $dateDataValue . ' ' . $timeDataValue;
|
||||
return $toFormatter->format($timestamp) ?: null;
|
||||
}
|
||||
|
||||
// If necessary, convert timezone
|
||||
$timezoneFormatter = $this->getTimezoneFormatter();
|
||||
if ($timezoneFormatter) {
|
||||
$timestamp = $timezoneFormatter->parse($value);
|
||||
$isoFormatter = $this->getISO8601Formatter();
|
||||
$value = $isoFormatter->format($timestamp);
|
||||
/**
|
||||
* Tidy up the internal date representation (ISO 8601),
|
||||
* and fall back to strtotime() if there's parsing errors.
|
||||
*
|
||||
* @param string $date Date in ISO 8601 or approximate form
|
||||
* @return string ISO 8601 date, or null if not valid
|
||||
*/
|
||||
public function tidyInternal($datetime)
|
||||
{
|
||||
if (!$datetime) {
|
||||
return null;
|
||||
}
|
||||
// Re-run through formatter to tidy up (e.g. remove time component)
|
||||
$formatter = $this->getInternalFormatter();
|
||||
$timestamp = $formatter->parse($datetime);
|
||||
if ($timestamp === false) {
|
||||
// Fallback to strtotime
|
||||
$timestamp = strtotime($datetime, DBDatetime::now()->getTimestamp());
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $formatter->format($timestamp);
|
||||
}
|
||||
|
||||
return $value;
|
||||
/**
|
||||
* Get length of the date format to use. One of:
|
||||
*
|
||||
* - IntlDateFormatter::SHORT
|
||||
* - IntlDateFormatter::MEDIUM
|
||||
* - IntlDateFormatter::LONG
|
||||
* - IntlDateFormatter::FULL
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
* @return int
|
||||
*/
|
||||
public function getDateLength()
|
||||
{
|
||||
if ($this->dateLength) {
|
||||
return $this->dateLength;
|
||||
}
|
||||
return IntlDateFormatter::MEDIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get length of the date format to use.
|
||||
* Only applicable with {@link setHTML5(false)}.
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
*
|
||||
* @param int $length
|
||||
* @return $this
|
||||
*/
|
||||
public function setDateLength($length)
|
||||
{
|
||||
$this->dateLength = $length;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get length of the date format to use. One of:
|
||||
*
|
||||
* - IntlDateFormatter::SHORT
|
||||
* - IntlDateFormatter::MEDIUM
|
||||
* - IntlDateFormatter::LONG
|
||||
* - IntlDateFormatter::FULL
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
* @return int
|
||||
*/
|
||||
public function getTimeLength()
|
||||
{
|
||||
if ($this->timeLength) {
|
||||
return $this->timeLength;
|
||||
}
|
||||
return IntlDateFormatter::MEDIUM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get length of the date format to use.
|
||||
* Only applicable with {@link setHTML5(false)}.
|
||||
*
|
||||
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
|
||||
*
|
||||
* @param int $length
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimeLength($length)
|
||||
{
|
||||
$this->timeLength = $length;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDisabled($bool)
|
||||
{
|
||||
parent::setDisabled($bool);
|
||||
$this->dateField->setDisabled($bool);
|
||||
$this->timeField->setDisabled($bool);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReadonly($bool)
|
||||
{
|
||||
parent::setReadonly($bool);
|
||||
$this->dateField->setReadonly($bool);
|
||||
$this->timeField->setReadonly($bool);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateField
|
||||
*/
|
||||
public function getDateField()
|
||||
{
|
||||
return $this->dateField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormField $field
|
||||
*/
|
||||
public function setDateField($field)
|
||||
{
|
||||
$expected = $this->getName() . '[date]';
|
||||
if ($field->getName() != $expected) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Wrong name format for date field: "%s" (expected "%s")',
|
||||
$field->getName(),
|
||||
$expected
|
||||
));
|
||||
}
|
||||
|
||||
$field->setForm($this->getForm());
|
||||
$field->setValue($this->dateField->dataValue());
|
||||
$this->dateField = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimeField
|
||||
*/
|
||||
public function getTimeField()
|
||||
{
|
||||
return $this->timeField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormField $field
|
||||
*/
|
||||
public function setTimeField($field)
|
||||
{
|
||||
$expected = $this->getName() . '[time]';
|
||||
if ($field->getName() != $expected) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Wrong name format for time field: "%s" (expected "%s")',
|
||||
$field->getName(),
|
||||
$expected
|
||||
));
|
||||
}
|
||||
|
||||
$field->setForm($this->getForm());
|
||||
$field->setValue($this->timeField->dataValue());
|
||||
$this->timeField = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default locale for this field. If omitted will default to the current locale.
|
||||
*
|
||||
@ -334,8 +503,7 @@ class DatetimeField extends FormField
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->dateField->setLocale($locale);
|
||||
$this->timeField->setLocale($locale);
|
||||
$this->locale = $locale;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -346,16 +514,120 @@ class DatetimeField extends FormField
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->dateField->getLocale();
|
||||
return $this->locale ?: i18n::get_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Date in ISO 8601 format, in server timezone.
|
||||
*/
|
||||
public function getMinDatetime()
|
||||
{
|
||||
return $this->minDatetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $minDatetime A string in ISO 8601 format, in server timezone.
|
||||
* @return $this
|
||||
*/
|
||||
public function setMinDatetime($minDatetime)
|
||||
{
|
||||
$this->minDatetime = $this->tidyInternal($minDatetime);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Date in ISO 8601 format, in server timezone.
|
||||
*/
|
||||
public function getMaxDatetime()
|
||||
{
|
||||
return $this->maxDatetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $maxDatetime A string in ISO 8601 format, in server timezone.
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxDatetime($maxDatetime)
|
||||
{
|
||||
$this->maxDatetime = $this->tidyInternal($maxDatetime);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$dateValid = $this->dateField->validate($validator);
|
||||
$timeValid = $this->timeField->validate($validator);
|
||||
// Don't validate empty fields
|
||||
if (empty($this->rawValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate if both subfields are valid
|
||||
return $dateValid && $timeValid;
|
||||
// We submitted a value, but it couldn't be parsed
|
||||
if (empty($this->value)) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DatetimeField.VALIDDATETIMEFORMAT',
|
||||
"Please enter a valid date and time format ({format})",
|
||||
['format' => $this->getDatetimeFormat()]
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check min date (in server timezone)
|
||||
$min = $this->getMinDatetime();
|
||||
if ($min) {
|
||||
$oops = strtotime($this->value) < strtotime($min);
|
||||
if ($oops) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DatetimeField.VALIDDATETIMEMINDATE',
|
||||
"Your date has to be newer or matching the minimum allowed date and time ({datetime})",
|
||||
[
|
||||
'datetime' => sprintf(
|
||||
'<time datetime="%s">%s</time>',
|
||||
$min,
|
||||
$this->internalToFrontend($min)
|
||||
)
|
||||
]
|
||||
),
|
||||
ValidationResult::TYPE_ERROR,
|
||||
ValidationResult::CAST_HTML
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check max date (in server timezone)
|
||||
$max = $this->getMaxDatetime();
|
||||
if ($max) {
|
||||
$oops = strtotime($this->value) > strtotime($max);
|
||||
if ($oops) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DatetimeField.VALIDDATEMAXDATETIME',
|
||||
"Your date has to be older or matching the maximum allowed date and time ({datetime})",
|
||||
[
|
||||
'datetime' => sprintf(
|
||||
'<time datetime="%s">%s</time>',
|
||||
$max,
|
||||
$this->internalToFrontend($max)
|
||||
)
|
||||
]
|
||||
),
|
||||
ValidationResult::TYPE_ERROR,
|
||||
ValidationResult::CAST_HTML
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function performReadonlyTransformation()
|
||||
@ -365,12 +637,6 @@ class DatetimeField extends FormField
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->dateField = clone $this->dateField;
|
||||
$this->timeField = clone $this->timeField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@ -379,13 +645,6 @@ class DatetimeField extends FormField
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom timezone
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $timezone = null;
|
||||
|
||||
/**
|
||||
* @param string $timezone
|
||||
* @return $this
|
||||
@ -395,29 +654,9 @@ class DatetimeField extends FormField
|
||||
if ($this->value && $timezone !== $this->timezone) {
|
||||
throw new \BadMethodCallException("Can't change timezone after setting a value");
|
||||
}
|
||||
// Note: DateField has no timezone option, and TimeField::setTimezone
|
||||
// should be ignored
|
||||
|
||||
$this->timezone = $timezone;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDateTimeOrder()
|
||||
{
|
||||
return $this->dateTimeOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set date time order format string. Use {date} and {time} as placeholders.
|
||||
*
|
||||
* @param string $dateTimeOrder
|
||||
* @return $this
|
||||
*/
|
||||
public function setDateTimeOrder($dateTimeOrder)
|
||||
{
|
||||
$this->dateTimeOrder = $dateTimeOrder;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class FormField extends RequestHandler
|
||||
const SCHEMA_DATA_TYPE_DATE = 'Date';
|
||||
|
||||
/** @see $schemaDataType */
|
||||
const SCHEMA_DATA_TYPE_DATETIME = 'DateTime';
|
||||
const SCHEMA_DATA_TYPE_DATETIME = 'Datetime';
|
||||
|
||||
/** @see $schemaDataType */
|
||||
const SCHEMA_DATA_TYPE_TIME = 'Time';
|
||||
|
@ -103,7 +103,7 @@ class TimeField extends TextField
|
||||
}
|
||||
|
||||
// Get from locale
|
||||
return $this->getFormatter()->getPattern();
|
||||
return $this->getFrontendFormatter()->getPattern();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +159,7 @@ class TimeField extends TextField
|
||||
*
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getFormatter()
|
||||
protected function getFrontendFormatter()
|
||||
{
|
||||
if ($this->getHTML5() && $this->timeFormat && $this->timeFormat !== DBTime::ISO_TIME) {
|
||||
throw new \LogicException(
|
||||
@ -204,7 +204,7 @@ class TimeField extends TextField
|
||||
*
|
||||
* @return IntlDateFormatter
|
||||
*/
|
||||
protected function getISO8601Formatter()
|
||||
protected function getInternalFormatter()
|
||||
{
|
||||
$formatter = IntlDateFormatter::create(
|
||||
i18n::config()->uninherited('default_locale'),
|
||||
@ -231,6 +231,17 @@ class TimeField extends TextField
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function getSchemaDataDefaults()
|
||||
{
|
||||
$defaults = parent::getSchemaDataDefaults();
|
||||
return array_merge($defaults, [
|
||||
'lang' => i18n::convert_rfc1766($this->getLocale()),
|
||||
'data' => array_merge($defaults['data'], [
|
||||
'html5' => $this->getHTML5(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
public function Type()
|
||||
{
|
||||
return 'time text';
|
||||
@ -249,7 +260,7 @@ class TimeField extends TextField
|
||||
$this->rawValue = $value;
|
||||
|
||||
// Parse from submitted value
|
||||
$this->value = $this->localisedToISO8601($value);
|
||||
$this->value = $this->frontendToInternal($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -272,13 +283,13 @@ class TimeField extends TextField
|
||||
}
|
||||
|
||||
// Re-run through formatter to tidy up (e.g. remove date component)
|
||||
$this->value = $this->tidyISO8601($value);
|
||||
$this->value = $this->tidyInternal($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function Value()
|
||||
{
|
||||
$localised = $this->iso8601ToLocalised($this->value);
|
||||
$localised = $this->internalToFrontend($this->value);
|
||||
if ($localised) {
|
||||
return $localised;
|
||||
}
|
||||
@ -294,7 +305,7 @@ class TimeField extends TextField
|
||||
*/
|
||||
public function getMidnight()
|
||||
{
|
||||
$formatter = $this->getFormatter();
|
||||
$formatter = $this->getFrontendFormatter();
|
||||
$timestamp = $this->withTimezone($this->getTimezone(), function () {
|
||||
return strtotime('midnight');
|
||||
});
|
||||
@ -364,24 +375,25 @@ class TimeField extends TextField
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert time localised in the current locale to ISO 8601 time
|
||||
* Convert frontend time to the internal representation (ISO 8601).
|
||||
* The frontend time is also in ISO 8601 when $html5=true.
|
||||
*
|
||||
* @param string $time
|
||||
* @return string The formatted time, or null if not a valid time
|
||||
*/
|
||||
public function localisedToISO8601($time)
|
||||
protected function frontendToInternal($time)
|
||||
{
|
||||
if (!$time) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getFormatter();
|
||||
$toFormatter = $this->getISO8601Formatter();
|
||||
$fromFormatter = $this->getFrontendFormatter();
|
||||
$toFormatter = $this->getInternalFormatter();
|
||||
$timestamp = $fromFormatter->parse($time);
|
||||
|
||||
// Try to parse time without seconds, since that's a valid HTML5 submission format
|
||||
// See https://html.spec.whatwg.org/multipage/infrastructure.html#times
|
||||
if ($timestamp === false && $this->getHTML5()) {
|
||||
$fromFormatter->setPattern('HH:mm');
|
||||
$fromFormatter->setPattern(str_replace(':ss', '', DBTime::ISO_TIME));
|
||||
$timestamp = $fromFormatter->parse($time);
|
||||
}
|
||||
|
||||
@ -394,19 +406,21 @@ class TimeField extends TextField
|
||||
}
|
||||
|
||||
/**
|
||||
* Format iso time to localised form
|
||||
* Convert the internal time representation (ISO 8601) to a format used by the frontend,
|
||||
* as defined by {@link $timeFormat}. With $html5=true, the frontend time will also be
|
||||
* in ISO 8601.
|
||||
*
|
||||
* @param string $time
|
||||
* @return string
|
||||
*/
|
||||
public function iso8601ToLocalised($time)
|
||||
protected function internalToFrontend($time)
|
||||
{
|
||||
$time = $this->tidyISO8601($time);
|
||||
$time = $this->tidyInternal($time);
|
||||
if (!$time) {
|
||||
return null;
|
||||
}
|
||||
$fromFormatter = $this->getISO8601Formatter();
|
||||
$toFormatter = $this->getFormatter();
|
||||
$fromFormatter = $this->getInternalFormatter();
|
||||
$toFormatter = $this->getFrontendFormatter();
|
||||
$timestamp = $fromFormatter->parse($time);
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
@ -417,18 +431,19 @@ class TimeField extends TextField
|
||||
|
||||
|
||||
/**
|
||||
* Tidy up iso8601-ish time, or approximation
|
||||
* Tidy up the internal time representation (ISO 8601),
|
||||
* and fall back to strtotime() if there's parsing errors.
|
||||
*
|
||||
* @param string $time Time in iso8601 or approximate form
|
||||
* @return string iso8601 time, or null if not valid
|
||||
* @param string $time Time in ISO 8601 or approximate form
|
||||
* @return string ISO 8601 time, or null if not valid
|
||||
*/
|
||||
public function tidyISO8601($time)
|
||||
protected function tidyInternal($time)
|
||||
{
|
||||
if (!$time) {
|
||||
return null;
|
||||
}
|
||||
// Re-run through formatter to tidy up (e.g. remove date component)
|
||||
$formatter = $this->getISO8601Formatter();
|
||||
$formatter = $this->getInternalFormatter();
|
||||
$timestamp = $formatter->parse($time);
|
||||
if ($timestamp === false) {
|
||||
// Fallback to strtotime
|
||||
|
@ -35,10 +35,17 @@ use InvalidArgumentException;
|
||||
class DBDatetime extends DBDate implements TemplateGlobalProvider
|
||||
{
|
||||
/**
|
||||
* Standard ISO format string for date and time in CLDR standard format
|
||||
* Standard ISO format string for date and time in CLDR standard format,
|
||||
* with a whitespace separating date and time (common database representation, e.g. in MySQL).
|
||||
*/
|
||||
const ISO_DATETIME = 'y-MM-dd HH:mm:ss';
|
||||
|
||||
/**
|
||||
* Standard ISO format string for date and time in CLDR standard format,
|
||||
* with a "T" separator between date and time (W3C standard, e.g. for HTML5 datetime-local fields).
|
||||
*/
|
||||
const ISO_DATETIME_NORMALISED = 'y-MM-dd\'T\'HH:mm:ss';
|
||||
|
||||
/**
|
||||
* Returns the standard localised date
|
||||
*
|
||||
@ -132,32 +139,18 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
|
||||
public function scaffoldFormField($title = null, $params = null)
|
||||
{
|
||||
$field = DatetimeField::create($this->name, $title);
|
||||
$dateFormat = $field->getDateField()->getDateFormat();
|
||||
$timeFormat = $field->getTimeField()->getTimeFormat();
|
||||
$dateTimeFormat = $field->getDatetimeFormat();
|
||||
|
||||
// Set date formatting hints and example
|
||||
$date = static::now()->Format($dateFormat);
|
||||
$date = static::now()->Format($dateTimeFormat);
|
||||
$field
|
||||
->getDateField()
|
||||
->setDescription(_t(
|
||||
'FormField.EXAMPLE',
|
||||
'e.g. {format}',
|
||||
'Example format',
|
||||
[ 'format' => $date ]
|
||||
))
|
||||
->setAttribute('placeholder', $dateFormat);
|
||||
|
||||
// Set time formatting hints and example
|
||||
$time = static::now()->Format($timeFormat);
|
||||
$field
|
||||
->getTimeField()
|
||||
->setDescription(_t(
|
||||
'FormField.EXAMPLE',
|
||||
'e.g. {format}',
|
||||
'Example format',
|
||||
[ 'format' => $time ]
|
||||
))
|
||||
->setAttribute('placeholder', $timeFormat);
|
||||
->setAttribute('placeholder', $dateTimeFormat);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<div id="$ID" class="form__fieldgroup<% if $extraClass %> $extraClass<% end_if %>">
|
||||
$DateField.SmallFieldHolder
|
||||
$TimeField.SmallFieldHolder
|
||||
<% if $TimeZone %>
|
||||
$TimezoneField.Field
|
||||
<% end_if %>
|
||||
</div>
|
@ -94,15 +94,22 @@ class DateFieldTest extends SapphireTest
|
||||
$this->assertEquals('2003-03-29', $f->dataValue());
|
||||
}
|
||||
|
||||
public function testTidyISO8601()
|
||||
public function testSetValue()
|
||||
{
|
||||
$f = new DateField('Date', 'Date');
|
||||
$this->assertEquals(null, $f->tidyISO8601('notadate'));
|
||||
$this->assertEquals('2011-01-31', $f->tidyISO8601('-1 day'));
|
||||
$this->assertEquals(null, $f->tidyISO8601('29/03/2003'));
|
||||
$f = (new DateField('Date', 'Date'))->setValue('notadate');
|
||||
$this->assertNull($f->Value(), 'Invalid input ignored');
|
||||
|
||||
$f = (new DateField('Date', 'Date'))->setValue('-1 day');
|
||||
$this->assertEquals($f->Value(), '2011-01-31', 'Relative dates accepted');
|
||||
|
||||
$f = (new DateField('Date', 'Date'))->setValue('2011-01-31');
|
||||
$this->assertEquals($f->Value(), '2011-01-31', 'ISO format accepted');
|
||||
|
||||
$f = (new DateField('Date', 'Date'))->setValue('2011-01-31 23:59:59');
|
||||
$this->assertEquals($f->Value(), '2011-01-31', 'ISO format with time accepted');
|
||||
}
|
||||
|
||||
public function testSetValueWithDateString()
|
||||
public function testSetValueWithLocalisedDateString()
|
||||
{
|
||||
$f = new DateField('Date', 'Date');
|
||||
$f->setHTML5(false);
|
||||
|
@ -13,6 +13,7 @@ use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
class DatetimeFieldTest extends SapphireTest
|
||||
{
|
||||
@ -37,10 +38,7 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$form = $this->getMockForm();
|
||||
$form->Fields()->push($dateTimeField);
|
||||
|
||||
$dateTimeField->setSubmittedValue([
|
||||
'date' => '2003-03-29',
|
||||
'time' => '23:59:38'
|
||||
]);
|
||||
$dateTimeField->setSubmittedValue('2003-03-29T23:59:38');
|
||||
$validator = new RequiredFields();
|
||||
$this->assertTrue($dateTimeField->validate($validator));
|
||||
$m = new Model();
|
||||
@ -51,12 +49,7 @@ class DatetimeFieldTest extends SapphireTest
|
||||
public function testFormSaveIntoLocalised()
|
||||
{
|
||||
$dateTimeField = new DatetimeField('MyDatetime');
|
||||
|
||||
$dateTimeField->getDateField()
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
$dateTimeField->getTimeField()
|
||||
$dateTimeField
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
@ -64,10 +57,7 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$form->Fields()->push($dateTimeField);
|
||||
|
||||
// en_NZ standard format
|
||||
$dateTimeField->setSubmittedValue([
|
||||
'date' => '29/03/2003',
|
||||
'time' => '11:59:38 pm'
|
||||
]);
|
||||
$dateTimeField->setSubmittedValue('29/03/2003 11:59:38 pm');
|
||||
$validator = new RequiredFields();
|
||||
$this->assertTrue($dateTimeField->validate($validator));
|
||||
$m = new Model();
|
||||
@ -84,18 +74,28 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$this->assertEquals('2003-03-29 23:59:38', $f->dataValue(), 'From date/time string');
|
||||
}
|
||||
|
||||
public function testDataValueWithTimezone()
|
||||
{
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setTimezone('Pacific/Auckland');
|
||||
$f->setSubmittedValue('2003-01-30T23:59:38'); // frontend timezone (Auckland)
|
||||
$this->assertEquals('2003-01-30 11:59:38', $f->dataValue()); // server timezone (Berlin)
|
||||
}
|
||||
|
||||
public function testConstructorWithoutArgs()
|
||||
{
|
||||
$f = new DatetimeField('Datetime');
|
||||
$this->assertEquals($f->dataValue(), null);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @expectedException InvalidArgumentException
|
||||
// */
|
||||
// public function testConstructorWithLocalizedDateString() {
|
||||
// $f = new DatetimeField('Datetime', 'Datetime', '29/03/2003 23:59:38');
|
||||
// }
|
||||
public function testConstructorWithLocalizedDateSetsNullValue()
|
||||
{
|
||||
$f = new DatetimeField('Datetime', 'Datetime', '29/03/2003 23:59:38');
|
||||
$this->assertNull($f->Value());
|
||||
}
|
||||
|
||||
public function testConstructorWithIsoDate()
|
||||
{
|
||||
@ -104,49 +104,45 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$this->assertEquals($f->dataValue(), '2003-03-29 23:59:38');
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @expectedException InvalidArgumentException
|
||||
// */
|
||||
// public function testSetValueWithDateString() {
|
||||
// $f = new DatetimeField('Datetime', 'Datetime');
|
||||
// $f->setValue('29/03/2003');
|
||||
// }
|
||||
|
||||
public function testSetValueWithDateTimeString()
|
||||
{
|
||||
$f = new DatetimeField('Datetime', 'Datetime');
|
||||
$f->setValue('2003-03-29 23:59:38');
|
||||
$this->assertEquals($f->dataValue(), '2003-03-29 23:59:38');
|
||||
$this->assertEquals($f->dataValue(), '2003-03-29 23:59:38', 'Accepts ISO');
|
||||
|
||||
$f = new DatetimeField('Datetime', 'Datetime');
|
||||
$f->setValue('2003-03-29T23:59:38');
|
||||
$this->assertNull($f->dataValue(), 'Rejects normalised ISO');
|
||||
}
|
||||
|
||||
public function testSetValueWithArray()
|
||||
public function testSubmittedValue()
|
||||
{
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
$datetimeField->setSubmittedValue([
|
||||
'date' => '2003-03-29',
|
||||
'time' => '23:00:00'
|
||||
]);
|
||||
$datetimeField->setSubmittedValue('2003-03-29 23:00:00');
|
||||
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00');
|
||||
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
$datetimeField->setSubmittedValue('2003-03-29T23:00:00');
|
||||
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00', 'Normalised ISO');
|
||||
}
|
||||
|
||||
public function testSetValueWithArrayLocalised()
|
||||
public function testSetValueWithLocalised()
|
||||
{
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
|
||||
$datetimeField->getDateField()
|
||||
$datetimeField
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
$datetimeField->getTimeField()
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
// Values can only be localized (= non-ISO) in array notation
|
||||
$datetimeField->setSubmittedValue([
|
||||
'date' => '29/03/2003',
|
||||
'time' => '11:00:00 pm'
|
||||
]);
|
||||
$datetimeField->setSubmittedValue('29/03/2003 11:00:00 pm');
|
||||
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00');
|
||||
|
||||
// Some localisation packages exclude the ',' in default medium format
|
||||
$this->assertRegExp(
|
||||
'#29/03/2003(,)? 11:00:00 (PM|pm)#',
|
||||
$datetimeField->Value(),
|
||||
'User value is formatted, and in user timezone'
|
||||
);
|
||||
}
|
||||
|
||||
public function testValidate()
|
||||
@ -154,124 +150,244 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 23:59:38');
|
||||
$this->assertTrue($f->validate(new RequiredFields()));
|
||||
|
||||
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 00:00:00');
|
||||
$this->assertTrue($f->validate(new RequiredFields()));
|
||||
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29T23:59:38');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Normalised ISO');
|
||||
|
||||
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Leaving out time');
|
||||
|
||||
$f = (new DatetimeField('Datetime', 'Datetime'))
|
||||
->setSubmittedValue('2003-03-29T00:00');
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Leaving out seconds (like many browsers)');
|
||||
|
||||
$f = new DatetimeField('Datetime', 'Datetime', 'wrong');
|
||||
$this->assertFalse($f->validate(new RequiredFields()));
|
||||
}
|
||||
|
||||
public function testTimezoneSetLocalised()
|
||||
public function testSetMinDate()
|
||||
{
|
||||
$f = (new DatetimeField('Datetime'))->setMinDatetime('2009-03-31T23:00:00');
|
||||
$this->assertEquals($f->getMinDatetime(), '2009-03-31 23:00:00', 'Retains ISO');
|
||||
|
||||
$f = (new DatetimeField('Datetime'))->setMinDatetime('2009-03-31 23:00:00');
|
||||
$this->assertEquals($f->getMinDatetime(), '2009-03-31 23:00:00', 'Converts normalised ISO to ISO');
|
||||
|
||||
$f = (new DatetimeField('Datetime'))->setMinDatetime('invalid');
|
||||
$this->assertNull($f->getMinDatetime(), 'Ignores invalid values');
|
||||
}
|
||||
|
||||
public function testSetMaxDate()
|
||||
{
|
||||
$f = (new DatetimeField('Datetime'))->setMaxDatetime('2009-03-31T23:00:00');
|
||||
$this->assertEquals($f->getMaxDatetime(), '2009-03-31 23:00:00', 'Retains ISO');
|
||||
|
||||
$f = (new DatetimeField('Datetime'))->setMaxDatetime('2009-03-31 23:00:00');
|
||||
$this->assertEquals($f->getMaxDatetime(), '2009-03-31 23:00:00', 'Converts normalised ISO to ISO');
|
||||
|
||||
$f = (new DatetimeField('Datetime'))->setMaxDatetime('invalid');
|
||||
$this->assertNull($f->getMaxDatetime(), 'Ignores invalid values');
|
||||
}
|
||||
|
||||
public function testValidateMinDate()
|
||||
{
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setMinDatetime('2009-03-31 23:00:00');
|
||||
$dateField->setValue('2009-03-31 23:00:01');
|
||||
$this->assertTrue($dateField->validate(new RequiredFields()), 'Time above min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setMinDatetime('2009-03-31 23:00:00');
|
||||
$dateField->setValue('2009-03-31 22:00:00');
|
||||
$this->assertFalse($dateField->validate(new RequiredFields()), 'Time below min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setMinDatetime('2009-03-31 23:00:00');
|
||||
$dateField->setValue('2009-03-31 23:00:00');
|
||||
$this->assertTrue($dateField->validate(new RequiredFields()), 'Date and time matching min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setMinDatetime('2009-03-31 23:00:00');
|
||||
$dateField->setValue('2008-03-31 23:00:00');
|
||||
$this->assertFalse($dateField->validate(new RequiredFields()), 'Date below min datetime');
|
||||
}
|
||||
|
||||
public function testValidateMinDateWithSubmittedValueAndTimezone()
|
||||
{
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setTimezone('Pacific/Auckland');
|
||||
$dateField->setMinDatetime('2009-01-30 23:00:00'); // server timezone (Berlin)
|
||||
$dateField->setSubmittedValue('2009-01-31T11:00:01'); // frontend timezone (Auckland)
|
||||
$this->assertTrue($dateField->validate(new RequiredFields()), 'Time above min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setTimezone('Pacific/Auckland');
|
||||
$dateField->setMinDatetime('2009-01-30 23:00:00');
|
||||
$dateField->setSubmittedValue('2009-01-31T10:00:00');
|
||||
$this->assertFalse($dateField->validate(new RequiredFields()), 'Time below min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setTimezone('Pacific/Auckland');
|
||||
$dateField->setMinDatetime('2009-01-30 23:00:00');
|
||||
$dateField->setSubmittedValue('2009-01-31T11:00:00');
|
||||
$this->assertTrue($dateField->validate(new RequiredFields()), 'Date and time matching min datetime');
|
||||
|
||||
$dateField = new DatetimeField('Datetime');
|
||||
$dateField->setTimezone('Pacific/Auckland');
|
||||
$dateField->setMinDatetime('2009-01-30 23:00:00');
|
||||
$dateField->setSubmittedValue('2008-01-31T11:00:00');
|
||||
$this->assertFalse($dateField->validate(new RequiredFields()), 'Date below min datetime');
|
||||
}
|
||||
|
||||
public function testValidateMinDateStrtotime()
|
||||
{
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMinDatetime('-7 days');
|
||||
$f->setValue(strftime('%Y-%m-%d %T', strtotime('-8 days', DBDatetime::now()->getTimestamp())));
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Date below min datetime, with strtotime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMinDatetime('-7 days');
|
||||
$f->setValue(strftime('%Y-%m-%d %T', strtotime('-7 days', DBDatetime::now()->getTimestamp())));
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Date matching min datetime, with strtotime');
|
||||
}
|
||||
|
||||
public function testValidateMaxDateStrtotime()
|
||||
{
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('7 days');
|
||||
$f->setValue(strftime('%Y-%m-%d %T', strtotime('8 days', DBDatetime::now()->getTimestamp())));
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Date above max date, with strtotime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('7 days');
|
||||
$f->setValue(strftime('%Y-%m-%d %T', strtotime('7 days', DBDatetime::now()->getTimestamp())));
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Date matching max date, with strtotime');
|
||||
}
|
||||
|
||||
public function testValidateMaxDate()
|
||||
{
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('2009-03-31 23:00:00');
|
||||
$f->setValue('2009-03-31 22:00:00');
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Time below max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('2009-03-31 23:00:00');
|
||||
$f->setValue('2010-03-31 23:00:01');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Time above max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('2009-03-31 23:00:00');
|
||||
$f->setValue('2009-03-31 23:00:00');
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Date and time matching max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setMaxDatetime('2009-03-31 23:00:00');
|
||||
$f->setValue('2010-03-31 23:00:00');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Date above max datetime');
|
||||
}
|
||||
|
||||
public function testValidateMaxDateWithSubmittedValueAndTimezone()
|
||||
{
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setTimezone('Pacific/Auckland');
|
||||
$f->setMaxDatetime('2009-01-31 23:00:00'); // server timezone (Berlin)
|
||||
$f->setSubmittedValue('2009-01-31T10:00:00'); // frontend timezone (Auckland)
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Time below max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setTimezone('Pacific/Auckland');
|
||||
$f->setMaxDatetime('2009-01-31 23:00:00');
|
||||
$f->setSubmittedValue('2010-01-31T11:00:01');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Time above max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setTimezone('Pacific/Auckland');
|
||||
$f->setMaxDatetime('2009-01-31 23:00:00');
|
||||
$f->setSubmittedValue('2009-01-31T11:00:00');
|
||||
$this->assertTrue($f->validate(new RequiredFields()), 'Date and time matching max datetime');
|
||||
|
||||
$f = new DatetimeField('Datetime');
|
||||
$f->setTimezone('Pacific/Auckland');
|
||||
$f->setMaxDatetime('2009-01-31 23:00:00');
|
||||
$f->setSubmittedValue('2010-01-31T11:00:00');
|
||||
$this->assertFalse($f->validate(new RequiredFields()), 'Date above max datetime');
|
||||
}
|
||||
|
||||
public function testTimezoneSetValueLocalised()
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
|
||||
$datetimeField->getDateField()
|
||||
$datetimeField
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
$datetimeField->getTimeField()
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
->setDatetimeFormat('dd/MM/y HH:mm:ss');
|
||||
|
||||
$datetimeField->setTimezone('Pacific/Auckland');
|
||||
$datetimeField->setValue('2003-12-24 23:59:59');
|
||||
$this->assertEquals(
|
||||
'25/12/2003 11:59:59 AM',
|
||||
'25/12/2003 11:59:59',
|
||||
$datetimeField->Value(),
|
||||
'User value is formatted, and in user timezone'
|
||||
);
|
||||
$this->assertEquals('25/12/2003', $datetimeField->getDateField()->Value());
|
||||
$this->assertEquals('11:59:59 AM', $datetimeField->getTimeField()->Value());
|
||||
|
||||
$this->assertEquals(
|
||||
'2003-12-24 23:59:59',
|
||||
$datetimeField->dataValue(),
|
||||
'Data value is unformatted, and in server timezone'
|
||||
'Data value is in ISO format, and in server timezone'
|
||||
);
|
||||
}
|
||||
|
||||
public function testTimezoneFromConfigLocalised()
|
||||
public function testTimezoneSetValueWithHtml5()
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
|
||||
$datetimeField->setTimezone('Pacific/Auckland');
|
||||
$datetimeField->setValue('2003-12-24 23:59:59');
|
||||
$this->assertEquals(
|
||||
'2003-12-25T11:59:59',
|
||||
$datetimeField->Value(),
|
||||
'User value is in normalised ISO format and in user timezone'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'2003-12-24 23:59:59',
|
||||
$datetimeField->dataValue(),
|
||||
'Data value is in ISO format, and in server timezone'
|
||||
);
|
||||
}
|
||||
|
||||
public function testTimezoneSetSubmittedValueLocalised()
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere summer, but Berlin and Moscow only 2h.
|
||||
$datetimeField = new DatetimeField('Datetime', 'Datetime');
|
||||
|
||||
$datetimeField->getDateField()
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
$datetimeField->getTimeField()
|
||||
$datetimeField
|
||||
->setHTML5(false)
|
||||
->setLocale('en_NZ');
|
||||
|
||||
$datetimeField->setTimezone('Europe/Moscow');
|
||||
$datetimeField->setSubmittedValue([
|
||||
// pass in default format, at user time (Moscow)
|
||||
'date' => '24/06/2003',
|
||||
'time' => '11:59:59 pm',
|
||||
]);
|
||||
// pass in default format, at user time (Moscow)
|
||||
$datetimeField->setSubmittedValue('24/06/2003 11:59:59 pm');
|
||||
$this->assertTrue($datetimeField->validate(new RequiredFields()));
|
||||
$this->assertEquals('2003-06-24 21:59:59', $datetimeField->dataValue(), 'Data value matches server timezone');
|
||||
}
|
||||
|
||||
public function testSetDateField()
|
||||
{
|
||||
$form = $this->getMockForm();
|
||||
$field = new DatetimeField('Datetime', 'Datetime');
|
||||
$field->setForm($form);
|
||||
$field->setSubmittedValue([
|
||||
'date' => '2003-06-24',
|
||||
'time' => '23:59:59',
|
||||
]);
|
||||
$dateField = new DateField('Datetime[date]');
|
||||
$field->setDateField($dateField);
|
||||
|
||||
$this->assertEquals(
|
||||
$dateField->getForm(),
|
||||
$form,
|
||||
'Sets form on new field'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'2003-06-24',
|
||||
$dateField->dataValue(),
|
||||
'Sets existing value on new field'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetTimeField()
|
||||
{
|
||||
$form = $this->getMockForm();
|
||||
$field = new DatetimeField('Datetime', 'Datetime');
|
||||
$field->setForm($form);
|
||||
$field->setSubmittedValue([
|
||||
'date' => '2003-06-24',
|
||||
'time' => '23:59:59',
|
||||
]);
|
||||
$timeField = new TimeField('Datetime[time]');
|
||||
$field->setTimeField($timeField);
|
||||
|
||||
$this->assertEquals(
|
||||
$timeField->getForm(),
|
||||
$form,
|
||||
'Sets form on new field'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'23:59:59',
|
||||
$timeField->dataValue(),
|
||||
'Sets existing value on new field'
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetName()
|
||||
{
|
||||
$field = new DatetimeField('Datetime');
|
||||
|
||||
$this->assertEquals('Datetime', $field->getName());
|
||||
$this->assertEquals('Datetime[date]', $field->getDateField()->getName());
|
||||
$this->assertEquals('Datetime[time]', $field->getTimeField()->getName());
|
||||
}
|
||||
|
||||
public function testSetName()
|
||||
@ -279,8 +395,54 @@ class DatetimeFieldTest extends SapphireTest
|
||||
$field = new DatetimeField('Datetime', 'Datetime');
|
||||
$field->setName('CustomDatetime');
|
||||
$this->assertEquals('CustomDatetime', $field->getName());
|
||||
$this->assertEquals('CustomDatetime[date]', $field->getDateField()->getName());
|
||||
$this->assertEquals('CustomDatetime[time]', $field->getTimeField()->getName());
|
||||
}
|
||||
|
||||
public function testSchemaDataDefaultsIncludesMinMax()
|
||||
{
|
||||
$field = new DatetimeField('Datetime');
|
||||
$field->setMinDatetime('2009-03-31 23:00:00');
|
||||
$field->setMaxDatetime('2010-03-31 23:00:00');
|
||||
$defaults = $field->getSchemaDataDefaults();
|
||||
$this->assertEquals($defaults['data']['min'], '2009-03-31T23:00:00');
|
||||
$this->assertEquals($defaults['data']['max'], '2010-03-31T23:00:00');
|
||||
}
|
||||
|
||||
public function testSchemaDataDefaultsAdjustsMinMaxToTimezone()
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere summer, but Berlin and Moscow only 2h.
|
||||
|
||||
$field = new DatetimeField('Datetime');
|
||||
$field->setTimezone('Pacific/Auckland');
|
||||
$field->setMinDatetime('2009-01-31 11:00:00'); // server timezone
|
||||
$field->setMaxDatetime('2010-01-31 11:00:00'); // server timezone
|
||||
$defaults = $field->getSchemaDataDefaults();
|
||||
$this->assertEquals($defaults['data']['min'], '2009-01-31T23:00:00'); // frontend timezone
|
||||
$this->assertEquals($defaults['data']['max'], '2010-01-31T23:00:00'); // frontend timezone
|
||||
}
|
||||
|
||||
public function testAttributesIncludesMinMax()
|
||||
{
|
||||
$field = new DatetimeField('Datetime');
|
||||
$field->setMinDatetime('2009-03-31 23:00:00');
|
||||
$field->setMaxDatetime('2010-03-31 23:00:00');
|
||||
$attrs = $field->getAttributes();
|
||||
$this->assertEquals($attrs['min'], '2009-03-31T23:00:00');
|
||||
$this->assertEquals($attrs['max'], '2010-03-31T23:00:00');
|
||||
}
|
||||
|
||||
public function testAttributesAdjustsMinMaxToTimezone()
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
// Berlin and Auckland have 12h time difference in northern hemisphere summer, but Berlin and Moscow only 2h.
|
||||
|
||||
$field = new DatetimeField('Datetime');
|
||||
$field->setTimezone('Pacific/Auckland');
|
||||
$field->setMinDatetime('2009-01-31 11:00:00'); // server timezone
|
||||
$field->setMaxDatetime('2010-01-31 11:00:00'); // server timezone
|
||||
$attrs = $field->getAttributes();
|
||||
$this->assertEquals($attrs['min'], '2009-01-31T23:00:00'); // frontend timezone
|
||||
$this->assertEquals($attrs['max'], '2010-01-31T23:00:00'); // frontend timezone
|
||||
}
|
||||
|
||||
protected function getMockForm()
|
||||
|
@ -462,6 +462,7 @@ class FormSchemaTest extends SapphireTest
|
||||
'extraClass' => 'date text',
|
||||
'description' => null,
|
||||
'rightTitle' => null,
|
||||
'lang' => 'en-US',
|
||||
'leftTitle' => null,
|
||||
'readOnly' => false,
|
||||
'disabled' => false,
|
||||
@ -472,8 +473,11 @@ class FormSchemaTest extends SapphireTest
|
||||
],
|
||||
'attributes' =>
|
||||
[],
|
||||
'data' =>
|
||||
[],
|
||||
'data' => [
|
||||
'html5' => true,
|
||||
'min' => null,
|
||||
'max' => null
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Number',
|
||||
|
Loading…
x
Reference in New Issue
Block a user