FEATURE New DatetimeField class (form field wrapper composed of DateField andTimeField)
FEATURE New DateField and TimeField form classes with more consistent API and easier localization
API CHANGE Date/time parsing in DateField, TimeField and DatetimeField defaults to i18n::get_locale() ('en_US') instead of using en_NZ/en_GB specific parsing. Use i18n::set_locale('en_NZ') in mysite/_config.php to revert to old behaviour.
API CHANGE constructor parameter in TimeField needs to be in ISO date notation (not PHP's date())
API CHANGE TimeField, DateField and related subclasses use Zend_Date for date parsing, meaning they're stricer than the previously used strtotime()
API CHANGE Removed DMYCalendarDateField and CalendarDateField, use DateField with setConfig('showcalendar')
API CHANGE Removed CompositeDateField, DMYDateField, use DateField with setConfig('dmyfields')
API CHANGE Removed DropdownTimeField, use TimeField with setConfig('showdropdown')
API CHANGE Removed PopupDateTimeField, use DatetimeField
API CHANGE Changed 'date', 'month' and 'year' HTML field names to lowercase in DMYDateField
API CHANGE Removed support for ambiguous date formats in DateField, e.g. '06/03/03'. Use DateField->setConfig('dateformat', '<format>') to revert to this behaviour.
API CHANGE Removed flag from DateField, CalendarDateField etc., use DateField->setConfig('min') and DateField->setConfig('max')
ENHANCEMENT Using Zend_Date for DateField and TimeField, with more robust date handling, starting localization support. Set globally via i18n::set_locale(), or for a field instance through setLocale(). Note: Javascript validation is not localized yet. (from r99360)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@102859 467b73ca-7a2a-4603-9d3b-597d59a354a9
2010-04-14 06:38:40 +02:00
|
|
|
<?php
|
2015-08-30 07:02:55 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Forms;
|
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
use IntlDateFormatter;
|
2016-08-19 00:51:35 +02:00
|
|
|
use InvalidArgumentException;
|
2017-01-26 05:20:08 +01:00
|
|
|
use SilverStripe\i18n\i18n;
|
2018-06-13 03:31:04 +02:00
|
|
|
use SilverStripe\ORM\FieldType\DBDate;
|
2017-04-12 07:11:30 +02:00
|
|
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
2017-04-27 11:44:52 +02:00
|
|
|
use SilverStripe\ORM\ValidationResult;
|
2016-08-19 00:51:35 +02:00
|
|
|
|
FEATURE New DatetimeField class (form field wrapper composed of DateField andTimeField)
FEATURE New DateField and TimeField form classes with more consistent API and easier localization
API CHANGE Date/time parsing in DateField, TimeField and DatetimeField defaults to i18n::get_locale() ('en_US') instead of using en_NZ/en_GB specific parsing. Use i18n::set_locale('en_NZ') in mysite/_config.php to revert to old behaviour.
API CHANGE constructor parameter in TimeField needs to be in ISO date notation (not PHP's date())
API CHANGE TimeField, DateField and related subclasses use Zend_Date for date parsing, meaning they're stricer than the previously used strtotime()
API CHANGE Removed DMYCalendarDateField and CalendarDateField, use DateField with setConfig('showcalendar')
API CHANGE Removed CompositeDateField, DMYDateField, use DateField with setConfig('dmyfields')
API CHANGE Removed DropdownTimeField, use TimeField with setConfig('showdropdown')
API CHANGE Removed PopupDateTimeField, use DatetimeField
API CHANGE Changed 'date', 'month' and 'year' HTML field names to lowercase in DMYDateField
API CHANGE Removed support for ambiguous date formats in DateField, e.g. '06/03/03'. Use DateField->setConfig('dateformat', '<format>') to revert to this behaviour.
API CHANGE Removed flag from DateField, CalendarDateField etc., use DateField->setConfig('min') and DateField->setConfig('max')
ENHANCEMENT Using Zend_Date for DateField and TimeField, with more robust date handling, starting localization support. Set globally via i18n::set_locale(), or for a field instance through setLocale(). Note: Javascript validation is not localized yet. (from r99360)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@102859 467b73ca-7a2a-4603-9d3b-597d59a354a9
2010-04-14 06:38:40 +02:00
|
|
|
/**
|
2017-04-26 03:49:59 +02:00
|
|
|
* Form field used for editing date time strings.
|
2017-04-27 00:32:22 +02:00
|
|
|
* 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).
|
2017-04-26 03:49:59 +02:00
|
|
|
* Data is passed on via {@link dataValue()} with whitespace separators.
|
2017-04-27 04:59:11 +02:00
|
|
|
* The {@link $value} property is always in ISO 8601 format, in the server timezone.
|
FEATURE New DatetimeField class (form field wrapper composed of DateField andTimeField)
FEATURE New DateField and TimeField form classes with more consistent API and easier localization
API CHANGE Date/time parsing in DateField, TimeField and DatetimeField defaults to i18n::get_locale() ('en_US') instead of using en_NZ/en_GB specific parsing. Use i18n::set_locale('en_NZ') in mysite/_config.php to revert to old behaviour.
API CHANGE constructor parameter in TimeField needs to be in ISO date notation (not PHP's date())
API CHANGE TimeField, DateField and related subclasses use Zend_Date for date parsing, meaning they're stricer than the previously used strtotime()
API CHANGE Removed DMYCalendarDateField and CalendarDateField, use DateField with setConfig('showcalendar')
API CHANGE Removed CompositeDateField, DMYDateField, use DateField with setConfig('dmyfields')
API CHANGE Removed DropdownTimeField, use TimeField with setConfig('showdropdown')
API CHANGE Removed PopupDateTimeField, use DatetimeField
API CHANGE Changed 'date', 'month' and 'year' HTML field names to lowercase in DMYDateField
API CHANGE Removed support for ambiguous date formats in DateField, e.g. '06/03/03'. Use DateField->setConfig('dateformat', '<format>') to revert to this behaviour.
API CHANGE Removed flag from DateField, CalendarDateField etc., use DateField->setConfig('min') and DateField->setConfig('max')
ENHANCEMENT Using Zend_Date for DateField and TimeField, with more robust date handling, starting localization support. Set globally via i18n::set_locale(), or for a field instance through setLocale(). Note: Javascript validation is not localized yet. (from r99360)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@102859 467b73ca-7a2a-4603-9d3b-597d59a354a9
2010-04-14 06:38:40 +02:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
class DatetimeField extends TextField
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
2017-04-12 07:11:30 +02:00
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
protected $html5 = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Override locale. If empty will default to current locale
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $locale = null;
|
|
|
|
|
2017-05-08 07:21:51 +02:00
|
|
|
protected $inputType = 'datetime-local';
|
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
/**
|
|
|
|
* Min date time
|
|
|
|
*
|
2017-04-27 04:59:11 +02:00
|
|
|
* @var string ISO 8601 date time in server timezone
|
2017-04-12 07:11:30 +02:00
|
|
|
*/
|
|
|
|
protected $minDatetime = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Max date time
|
|
|
|
*
|
2017-04-27 04:59:11 +02:00
|
|
|
* @var string ISO 860 date time in server timezone
|
2017-04-12 07:11:30 +02:00
|
|
|
*/
|
|
|
|
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
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
protected $timeLength = null;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
2017-04-12 07:11:30 +02:00
|
|
|
* Unparsed value, used exclusively for comparing with internal value
|
|
|
|
* to detect invalid values.
|
|
|
|
*
|
|
|
|
* @var mixed
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
protected $rawValue = null;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-26 03:49:59 +02:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
|
|
|
|
|
2017-04-26 03:49:59 +02:00
|
|
|
/**
|
|
|
|
* Custom timezone
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $timezone = null;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-20 00:54:41 +02:00
|
|
|
public function getAttributes()
|
|
|
|
{
|
|
|
|
$attributes = parent::getAttributes();
|
|
|
|
|
|
|
|
$attributes['lang'] = i18n::convert_rfc1766($this->getLocale());
|
|
|
|
|
|
|
|
if ($this->getHTML5()) {
|
2017-04-27 04:59:11 +02:00
|
|
|
$attributes['min'] = $this->internalToFrontend($this->getMinDatetime());
|
|
|
|
$attributes['max'] = $this->internalToFrontend($this->getMaxDatetime());
|
2017-05-08 07:21:51 +02:00
|
|
|
} else {
|
|
|
|
$attributes['type'] = 'text';
|
2017-04-20 00:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $attributes;
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:56:23 +02:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2017-04-12 12:39:23 +02:00
|
|
|
public function getSchemaDataDefaults()
|
|
|
|
{
|
|
|
|
$defaults = parent::getSchemaDataDefaults();
|
|
|
|
return array_merge($defaults, [
|
2017-04-27 01:47:04 +02:00
|
|
|
'lang' => i18n::convert_rfc1766($this->getLocale()),
|
|
|
|
'data' => array_merge($defaults['data'], [
|
|
|
|
'html5' => $this->getHTML5(),
|
2017-04-27 04:59:11 +02:00
|
|
|
'min' => $this->internalToFrontend($this->getMinDatetime()),
|
|
|
|
'max' => $this->internalToFrontend($this->getMaxDatetime())
|
2017-04-27 01:47:04 +02:00
|
|
|
])
|
2017-04-12 12:39:23 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:56:23 +02:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function Type()
|
|
|
|
{
|
|
|
|
return 'text datetime';
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:56:23 +02:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function getHTML5()
|
|
|
|
{
|
|
|
|
return $this->html5;
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:56:23 +02:00
|
|
|
/**
|
|
|
|
* @param $bool
|
|
|
|
* @return $this
|
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function setHTML5($bool)
|
|
|
|
{
|
|
|
|
$this->html5 = $bool;
|
2016-11-29 00:31:16 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 00:32:22 +02:00
|
|
|
* Assign value posted from form submission, based on {@link $datetimeFormat}.
|
|
|
|
* When $html5=true, this needs to be normalised ISO format (with "T" separator).
|
2017-01-26 05:20:08 +01:00
|
|
|
*
|
2017-04-12 07:11:30 +02:00
|
|
|
* @param mixed $value
|
2017-01-26 05:20:08 +01:00
|
|
|
* @param mixed $data
|
|
|
|
* @return $this
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-01-26 05:20:08 +01:00
|
|
|
public function setSubmittedValue($value, $data = null)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
// Save raw value for later validation
|
|
|
|
$this->rawValue = $value;
|
|
|
|
|
|
|
|
// Null case
|
|
|
|
if (!$value) {
|
2017-01-26 05:20:08 +01:00
|
|
|
$this->value = null;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
// Parse from submitted value
|
2017-04-27 01:53:43 +02:00
|
|
|
$this->value = $this->frontendToInternal($value);
|
2017-04-20 05:08:44 +02:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 01:53:43 +02:00
|
|
|
* Convert frontend date to the internal representation (ISO 8601).
|
|
|
|
* The frontend date is also in ISO 8601 when $html5=true.
|
2017-04-27 04:59:11 +02:00
|
|
|
* Assumes the value is in the defined {@link $timezone} (if one is set),
|
|
|
|
* and adjusts for server timezone.
|
2017-04-12 07:11:30 +02:00
|
|
|
*
|
2017-04-26 03:49:59 +02:00
|
|
|
* @param string $datetime
|
2017-04-12 07:11:30 +02:00
|
|
|
* @return string The formatted date, or null if not a valid date
|
|
|
|
*/
|
2017-04-27 01:53:43 +02:00
|
|
|
public function frontendToInternal($datetime)
|
2017-04-12 07:11:30 +02:00
|
|
|
{
|
|
|
|
if (!$datetime) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-04-27 01:53:43 +02:00
|
|
|
$fromFormatter = $this->getFrontendFormatter();
|
|
|
|
$toFormatter = $this->getInternalFormatter();
|
2017-04-26 03:49:59 +02:00
|
|
|
|
|
|
|
// Try to parse time with seconds
|
2017-04-12 07:11:30 +02:00
|
|
|
$timestamp = $fromFormatter->parse($datetime);
|
2017-04-26 03:49:29 +02:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
if ($timestamp === false) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return $toFormatter->format($timestamp) ?: null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get date formatter with the standard locale / date format
|
|
|
|
*
|
|
|
|
* @throws \LogicException
|
|
|
|
* @return IntlDateFormatter
|
|
|
|
*/
|
2017-04-27 01:53:43 +02:00
|
|
|
protected function getFrontendFormatter()
|
2017-04-12 07:11:30 +02:00
|
|
|
{
|
2017-04-26 03:49:59 +02:00
|
|
|
if ($this->getHTML5() && $this->datetimeFormat && $this->datetimeFormat !== DBDatetime::ISO_DATETIME_NORMALISED) {
|
2017-04-12 07:11:30 +02:00
|
|
|
throw new \LogicException(
|
2017-04-20 00:54:41 +02:00
|
|
|
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setDatetimeFormat()'
|
2017-04-12 07:11:30 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->getHTML5() && $this->dateLength) {
|
|
|
|
throw new \LogicException(
|
|
|
|
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setDateLength()'
|
|
|
|
);
|
2017-01-26 05:20:08 +01:00
|
|
|
}
|
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
if ($this->getHTML5() && $this->locale) {
|
|
|
|
throw new \LogicException(
|
|
|
|
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setLocale()'
|
|
|
|
);
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
$formatter = IntlDateFormatter::create(
|
|
|
|
$this->getLocale(),
|
|
|
|
$this->getDateLength(),
|
|
|
|
$this->getTimeLength(),
|
|
|
|
$this->getTimezone()
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($this->getHTML5()) {
|
2017-04-26 03:49:59 +02:00
|
|
|
// 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);
|
2017-04-12 07:11:30 +02:00
|
|
|
} elseif ($this->datetimeFormat) {
|
2017-04-20 05:08:44 +02:00
|
|
|
// Don't invoke getDatetimeFormat() directly to avoid infinite loop
|
2017-04-12 07:11:30 +02:00
|
|
|
$ok = $formatter->setPattern($this->datetimeFormat);
|
|
|
|
if (!$ok) {
|
|
|
|
throw new InvalidArgumentException("Invalid date format {$this->datetimeFormat}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $formatter;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2017-04-20 00:54:41 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2017-04-20 05:08:44 +02:00
|
|
|
public function getDatetimeFormat()
|
2017-04-20 00:54:41 +02:00
|
|
|
{
|
|
|
|
if ($this->datetimeFormat) {
|
|
|
|
return $this->datetimeFormat;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get from locale
|
2017-04-27 01:53:43 +02:00
|
|
|
return $this->getFrontendFormatter()->getPattern();
|
2017-04-20 00:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
2017-04-27 04:59:11 +02:00
|
|
|
* Get a date formatter for the ISO 8601 format
|
2017-01-26 05:20:08 +01:00
|
|
|
*
|
2017-04-27 04:59:11 +02:00
|
|
|
* @param String $timezone Optional timezone identifier (defaults to server timezone)
|
|
|
|
* @return IntlDateFormatter
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-27 04:59:11 +02:00
|
|
|
protected function getInternalFormatter($timezone = null)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-01-26 05:20:08 +01:00
|
|
|
if (!$timezone) {
|
2017-04-27 04:59:11 +02:00
|
|
|
$timezone = date_default_timezone_get(); // Default to server timezone
|
2017-01-26 05:20:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$formatter = IntlDateFormatter::create(
|
2018-06-13 03:31:04 +02:00
|
|
|
DBDate::ISO_LOCALE,
|
2017-01-26 05:20:08 +01:00
|
|
|
IntlDateFormatter::MEDIUM,
|
|
|
|
IntlDateFormatter::MEDIUM,
|
2017-04-27 04:59:11 +02:00
|
|
|
$timezone
|
2017-01-26 05:20:08 +01:00
|
|
|
);
|
|
|
|
$formatter->setLenient(false);
|
2017-04-26 03:49:59 +02:00
|
|
|
|
|
|
|
// Note we omit timezone from this format, and we always assume server TZ
|
2017-04-27 04:59:11 +02:00
|
|
|
$formatter->setPattern(DBDatetime::ISO_DATETIME);
|
2017-04-26 03:49:59 +02:00
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
return $formatter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 01:53:43 +02:00
|
|
|
* Assign value based on {@link $datetimeFormat}, which might be localised.
|
2017-04-27 04:59:11 +02:00
|
|
|
* The value needs to be in the server timezone.
|
2017-04-27 00:32:22 +02:00
|
|
|
*
|
|
|
|
* 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).
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
2017-01-26 05:20:08 +01:00
|
|
|
* @param mixed $value
|
|
|
|
* @param mixed $data
|
2016-11-29 00:31:16 +01:00
|
|
|
* @return $this
|
|
|
|
*/
|
2017-01-26 05:20:08 +01:00
|
|
|
public function setValue($value, $data = null)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 12:39:23 +02:00
|
|
|
// Save raw value for later validation
|
|
|
|
$this->rawValue = $value;
|
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
// Empty value
|
|
|
|
if (empty($value)) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$this->value = null;
|
2017-01-26 05:20:08 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate iso 8601 date
|
|
|
|
// If invalid, assign for later validation failure
|
2017-04-27 01:53:43 +02:00
|
|
|
$internalFormatter = $this->getInternalFormatter();
|
|
|
|
$timestamp = $internalFormatter->parse($value);
|
2017-04-26 03:49:59 +02:00
|
|
|
|
2018-06-13 03:31:04 +02:00
|
|
|
// Retry with "T" separator
|
2017-04-26 03:49:59 +02:00
|
|
|
if (!$timestamp) {
|
2017-04-27 01:53:43 +02:00
|
|
|
$fallbackFormatter = $this->getInternalFormatter();
|
2018-06-13 03:31:04 +02:00
|
|
|
$fallbackFormatter->setPattern(DBDatetime::ISO_DATETIME_NORMALISED);
|
2017-04-27 01:53:43 +02:00
|
|
|
$timestamp = $fallbackFormatter->parse($value);
|
2017-04-26 03:49:59 +02:00
|
|
|
}
|
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
if ($timestamp === false) {
|
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
// Cleanup date
|
2017-04-27 01:53:43 +02:00
|
|
|
$value = $internalFormatter->format($timestamp);
|
2017-01-26 05:20:08 +01:00
|
|
|
|
|
|
|
// Save value
|
|
|
|
$this->value = $value;
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-04-27 01:53:43 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function Value()
|
|
|
|
{
|
2017-04-27 01:53:43 +02:00
|
|
|
return $this->internalToFrontend($this->value);
|
2017-01-26 05:20:08 +01:00
|
|
|
}
|
|
|
|
|
2017-04-26 03:49:59 +02:00
|
|
|
/**
|
2017-04-27 01:53:43 +02:00
|
|
|
* 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.
|
2017-04-26 03:49:59 +02:00
|
|
|
*
|
|
|
|
* @param string $datetime
|
|
|
|
* @return string The formatted date and time, or null if not a valid date and time
|
2017-01-26 05:20:08 +01:00
|
|
|
*/
|
2017-04-27 01:53:43 +02:00
|
|
|
public function internalToFrontend($datetime)
|
2017-01-26 05:20:08 +01:00
|
|
|
{
|
2017-04-27 01:53:43 +02:00
|
|
|
$datetime = $this->tidyInternal($datetime);
|
2017-04-12 07:11:30 +02:00
|
|
|
if (!$datetime) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-04-27 01:53:43 +02:00
|
|
|
$fromFormatter = $this->getInternalFormatter();
|
|
|
|
$toFormatter = $this->getFrontendFormatter();
|
2017-04-12 07:11:30 +02:00
|
|
|
$timestamp = $fromFormatter->parse($datetime);
|
|
|
|
if ($timestamp === false) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-04-26 03:49:59 +02:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
return $toFormatter->format($timestamp) ?: null;
|
2017-01-26 05:20:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 01:53:43 +02:00
|
|
|
* Tidy up the internal date representation (ISO 8601),
|
|
|
|
* and fall back to strtotime() if there's parsing errors.
|
2017-01-26 05:20:08 +01:00
|
|
|
*
|
2017-05-08 13:34:39 +02:00
|
|
|
* @param string $datetime Date in ISO 8601 or approximate form
|
2017-04-27 01:53:43 +02:00
|
|
|
* @return string ISO 8601 date, or null if not valid
|
2017-01-26 05:20:08 +01:00
|
|
|
*/
|
2017-04-27 01:53:43 +02:00
|
|
|
public function tidyInternal($datetime)
|
2017-01-26 05:20:08 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
if (!$datetime) {
|
2017-01-26 05:20:08 +01:00
|
|
|
return null;
|
|
|
|
}
|
2017-04-12 07:11:30 +02:00
|
|
|
// Re-run through formatter to tidy up (e.g. remove time component)
|
2017-04-27 01:53:43 +02:00
|
|
|
$formatter = $this->getInternalFormatter();
|
2017-04-12 07:11:30 +02:00
|
|
|
$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);
|
|
|
|
}
|
2017-01-26 05:20:08 +01:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2017-01-26 05:20:08 +01:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2017-04-12 07:11:30 +02:00
|
|
|
return IntlDateFormatter::MEDIUM;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function setDisabled($bool)
|
|
|
|
{
|
|
|
|
parent::setDisabled($bool);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setReadonly($bool)
|
|
|
|
{
|
|
|
|
parent::setReadonly($bool);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-12 07:11:30 +02:00
|
|
|
* Set default locale for this field. If omitted will default to the current locale.
|
|
|
|
*
|
|
|
|
* @param string $locale
|
|
|
|
* @return $this
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function setLocale($locale)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
$this->locale = $locale;
|
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-12 07:11:30 +02:00
|
|
|
* Get locale for this field
|
|
|
|
*
|
|
|
|
* @return string
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function getLocale()
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this->locale ?: i18n::get_locale();
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 04:59:11 +02:00
|
|
|
* @return string Date in ISO 8601 format, in server timezone.
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function getMinDatetime()
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this->minDatetime;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 04:59:11 +02:00
|
|
|
* @param string $minDatetime A string in ISO 8601 format, in server timezone.
|
2017-04-12 07:11:30 +02:00
|
|
|
* @return $this
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function setMinDatetime($minDatetime)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-27 01:53:43 +02:00
|
|
|
$this->minDatetime = $this->tidyInternal($minDatetime);
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 04:59:11 +02:00
|
|
|
* @return string Date in ISO 8601 format, in server timezone.
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function getMaxDatetime()
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this->maxDatetime;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-27 04:59:11 +02:00
|
|
|
* @param string $maxDatetime A string in ISO 8601 format, in server timezone.
|
2017-04-12 07:11:30 +02:00
|
|
|
* @return $this
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2017-04-12 07:11:30 +02:00
|
|
|
public function setMaxDatetime($maxDatetime)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2017-04-27 01:53:43 +02:00
|
|
|
$this->maxDatetime = $this->tidyInternal($maxDatetime);
|
2017-04-12 07:11:30 +02:00
|
|
|
return $this;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
/**
|
|
|
|
* @param Validator $validator
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function validate($validator)
|
|
|
|
{
|
2017-04-12 07:11:30 +02:00
|
|
|
// Don't validate empty fields
|
|
|
|
if (empty($this->rawValue)) {
|
|
|
|
return true;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-04-12 07:11:30 +02:00
|
|
|
// We submitted a value, but it couldn't be parsed
|
|
|
|
if (empty($this->value)) {
|
|
|
|
$validator->validationError(
|
|
|
|
$this->name,
|
|
|
|
_t(
|
2018-01-16 19:39:30 +01:00
|
|
|
__CLASS__ . '.VALIDDATETIMEFORMAT',
|
2017-04-26 03:48:48 +02:00
|
|
|
"Please enter a valid date and time format ({format})",
|
2017-04-20 05:08:44 +02:00
|
|
|
['format' => $this->getDatetimeFormat()]
|
2017-04-12 07:11:30 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-27 04:59:11 +02:00
|
|
|
// Check min date (in server timezone)
|
2017-04-12 07:11:30 +02:00
|
|
|
$min = $this->getMinDatetime();
|
|
|
|
if ($min) {
|
|
|
|
$oops = strtotime($this->value) < strtotime($min);
|
|
|
|
if ($oops) {
|
|
|
|
$validator->validationError(
|
|
|
|
$this->name,
|
|
|
|
_t(
|
2018-01-16 19:39:30 +01:00
|
|
|
__CLASS__ . '.VALIDDATETIMEMINDATE',
|
2017-04-26 03:48:48 +02:00
|
|
|
"Your date has to be newer or matching the minimum allowed date and time ({datetime})",
|
2017-04-27 11:44:52 +02:00
|
|
|
[
|
|
|
|
'datetime' => sprintf(
|
|
|
|
'<time datetime="%s">%s</time>',
|
|
|
|
$min,
|
|
|
|
$this->internalToFrontend($min)
|
|
|
|
)
|
|
|
|
]
|
|
|
|
),
|
|
|
|
ValidationResult::TYPE_ERROR,
|
|
|
|
ValidationResult::CAST_HTML
|
2017-04-12 07:11:30 +02:00
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-27 04:59:11 +02:00
|
|
|
// Check max date (in server timezone)
|
2017-04-12 07:11:30 +02:00
|
|
|
$max = $this->getMaxDatetime();
|
|
|
|
if ($max) {
|
|
|
|
$oops = strtotime($this->value) > strtotime($max);
|
|
|
|
if ($oops) {
|
|
|
|
$validator->validationError(
|
|
|
|
$this->name,
|
|
|
|
_t(
|
2018-01-16 19:39:30 +01:00
|
|
|
__CLASS__ . '.VALIDDATEMAXDATETIME',
|
2017-04-26 03:48:48 +02:00
|
|
|
"Your date has to be older or matching the maximum allowed date and time ({datetime})",
|
2017-04-27 11:44:52 +02:00
|
|
|
[
|
|
|
|
'datetime' => sprintf(
|
|
|
|
'<time datetime="%s">%s</time>',
|
|
|
|
$max,
|
|
|
|
$this->internalToFrontend($max)
|
|
|
|
)
|
|
|
|
]
|
|
|
|
),
|
|
|
|
ValidationResult::TYPE_ERROR,
|
|
|
|
ValidationResult::CAST_HTML
|
2017-04-12 07:11:30 +02:00
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function performReadonlyTransformation()
|
|
|
|
{
|
|
|
|
$field = clone $this;
|
|
|
|
$field->setReadonly(true);
|
|
|
|
return $field;
|
|
|
|
}
|
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getTimezone()
|
|
|
|
{
|
|
|
|
return $this->timezone;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $timezone
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setTimezone($timezone)
|
|
|
|
{
|
|
|
|
if ($this->value && $timezone !== $this->timezone) {
|
|
|
|
throw new \BadMethodCallException("Can't change timezone after setting a value");
|
|
|
|
}
|
2017-04-27 04:59:11 +02:00
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
$this->timezone = $timezone;
|
2017-04-27 04:59:11 +02:00
|
|
|
|
2017-01-26 05:20:08 +01:00
|
|
|
return $this;
|
|
|
|
}
|
FEATURE New DatetimeField class (form field wrapper composed of DateField andTimeField)
FEATURE New DateField and TimeField form classes with more consistent API and easier localization
API CHANGE Date/time parsing in DateField, TimeField and DatetimeField defaults to i18n::get_locale() ('en_US') instead of using en_NZ/en_GB specific parsing. Use i18n::set_locale('en_NZ') in mysite/_config.php to revert to old behaviour.
API CHANGE constructor parameter in TimeField needs to be in ISO date notation (not PHP's date())
API CHANGE TimeField, DateField and related subclasses use Zend_Date for date parsing, meaning they're stricer than the previously used strtotime()
API CHANGE Removed DMYCalendarDateField and CalendarDateField, use DateField with setConfig('showcalendar')
API CHANGE Removed CompositeDateField, DMYDateField, use DateField with setConfig('dmyfields')
API CHANGE Removed DropdownTimeField, use TimeField with setConfig('showdropdown')
API CHANGE Removed PopupDateTimeField, use DatetimeField
API CHANGE Changed 'date', 'month' and 'year' HTML field names to lowercase in DMYDateField
API CHANGE Removed support for ambiguous date formats in DateField, e.g. '06/03/03'. Use DateField->setConfig('dateformat', '<format>') to revert to this behaviour.
API CHANGE Removed flag from DateField, CalendarDateField etc., use DateField->setConfig('min') and DateField->setConfig('max')
ENHANCEMENT Using Zend_Date for DateField and TimeField, with more robust date handling, starting localization support. Set globally via i18n::set_locale(), or for a field instance through setLocale(). Note: Javascript validation is not localized yet. (from r99360)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@102859 467b73ca-7a2a-4603-9d3b-597d59a354a9
2010-04-14 06:38:40 +02:00
|
|
|
}
|