silverstripe-framework/src/Forms/DatetimeField.php

424 lines
12 KiB
PHP
Raw Normal View History

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
namespace SilverStripe\Forms;
use IntlDateFormatter;
use InvalidArgumentException;
use SilverStripe\i18n\i18n;
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
/**
* A composite field for date and time entry,
* based on {@link DateField} and {@link TimeField}.
* Usually saves into a single {@link DBDateTime} database column.
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
* If you want to save into {@link Date} or {@link Time} columns,
* please instanciate the fields separately.
2014-08-15 08:53:05 +02:00
*
* 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()}.
*
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
* # Configuration
2014-08-15 08:53:05 +02:00
*
* Individual options are configured either on the DatetimeField, or on individual
* sub-fields accessed via getDateField() or getTimeField()
2014-08-15 08:53:05 +02:00
*
* Example:
* <code>
* $field = new DatetimeField('Name', 'Label');
2017-04-04 00:56:03 +02:00
* $field->getDateField()->setTitle('Select Date');
* </code>
2014-08-15 08:53:05 +02:00
*
2017-04-04 00:56:03 +02:00
* - 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.
2017-04-04 00:56:03 +02:00
* - 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.
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
*/
2016-11-29 00:31:16 +01:00
class DatetimeField extends FormField
{
/**
* @var DateField
*/
protected $dateField = null;
/**
* @var TimeField
*/
protected $timeField = null;
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
/**
* Date time order
*
* @var string
2016-11-29 00:31:16 +01:00
*/
protected $dateTimeOrder = '{date} {time}';
2016-11-29 00:31:16 +01:00
public function __construct($name, $title = null, $value = "")
{
$this->timeField = TimeField::create($name . '[time]', false);
$this->dateField = DateField::create($name . '[date]', false);
parent::__construct($name, $title, $value);
}
public function setForm($form)
{
parent::setForm($form);
$this->dateField->setForm($form);
$this->timeField->setForm($form);
return $this;
}
public function setName($name)
{
parent::setName($name);
$this->dateField->setName($name . '[date]');
$this->timeField->setName($name . '[time]');
return $this;
}
/**
* Sets value from a submitted form array
*
* @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 $data
* @return $this
2016-11-29 00:31:16 +01:00
*/
public function setSubmittedValue($value, $data = null)
2016-11-29 00:31:16 +01:00
{
// Empty value
if (empty($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");
}
// Save each field, and convert from array to iso8601 string
$this->dateField->setSubmittedValue($value['date'], $value);
$this->timeField->setSubmittedValue($value['time'], $value);
2016-11-29 00:31:16 +01:00
// Combine date components back into iso8601 string for the root value
$this->value = $this->dataValue();
return $this;
2016-11-29 00:31:16 +01:00
}
/**
* Get formatter for converting to the target timezone, if timezone is set
* Can return null if no timezone set
*
* @return IntlDateFormatter|null
2016-11-29 00:31:16 +01:00
*/
protected function getTimezoneFormatter()
2016-11-29 00:31:16 +01:00
{
$timezone = $this->getTimezone();
if (!$timezone) {
return null;
}
// Build new formatter with the altered timezone
$formatter = clone $this->getISO8601Formatter();
$formatter->setTimeZone($timezone);
return $formatter;
2016-11-29 00:31:16 +01:00
}
/**
* Get a date formatter for the ISO 8601 format
2016-11-29 00:31:16 +01:00
*
* @return IntlDateFormatter
*/
protected function getISO8601Formatter()
{
$formatter = IntlDateFormatter::create(
2017-02-22 04:14:53 +01:00
i18n::config()->uninherited('default_locale'),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::MEDIUM,
date_default_timezone_get() // Default to server 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');
return $formatter;
}
/**
* Assign value from iso8601 string
2016-11-29 00:31:16 +01:00
*
* @param mixed $value
* @param mixed $data
2016-11-29 00:31:16 +01:00
* @return $this
*/
public function setValue($value, $data = null)
2016-11-29 00:31:16 +01:00
{
// Empty value
if (empty($value)) {
2016-11-29 00:31:16 +01:00
$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);
if ($timestamp === false) {
$this->dateField->setSubmittedValue($value);
$this->timeField->setValue(null);
return $this;
2016-11-29 00:31:16 +01:00
}
// Cleanup date
$value = $isoFormatter->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);
2016-11-29 00:31:16 +01:00
return $this;
}
/**
* localised time value
*
* @return string
*/
2016-11-29 00:31:16 +01:00
public function Value()
{
$date = $this->dateField->Value();
$time = $this->timeField->Value();
return $this->joinDateTime($date, $time);
}
/**
* @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
*
* @return string|null
*/
public function dataValue()
{
// No date means no value (even if time is specified)
$dateDataValue = $this->getDateField()->dataValue();
if (empty($dateDataValue)) {
return null;
}
// Build iso8601 timestamp from combined date and time
$timeDataValue = $this->getTimeField()->dataValue() ?: '00:00:00';
$value = $dateDataValue . ' ' . $timeDataValue;
// If necessary, convert timezone
$timezoneFormatter = $this->getTimezoneFormatter();
if ($timezoneFormatter) {
$timestamp = $timezoneFormatter->parse($value);
$isoFormatter = $this->getISO8601Formatter();
$value = $isoFormatter->format($timestamp);
2016-11-29 00:31:16 +01:00
}
return $value;
2016-11-29 00:31:16 +01:00
}
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());
2016-11-29 00:31:16 +01:00
$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());
2016-11-29 00:31:16 +01:00
$this->timeField = $field;
}
/**
* Set default locale for this field. If omitted will default to the current locale.
2016-11-29 00:31:16 +01:00
*
* @param string $locale
* @return $this
2016-11-29 00:31:16 +01:00
*/
public function setLocale($locale)
{
$this->dateField->setLocale($locale);
$this->timeField->setLocale($locale);
return $this;
}
/**
* Get locale for this field
2016-11-29 00:31:16 +01:00
*
* @return string
2016-11-29 00:31:16 +01:00
*/
public function getLocale()
2016-11-29 00:31:16 +01:00
{
return $this->dateField->getLocale();
2016-11-29 00:31:16 +01:00
}
public function validate($validator)
{
$dateValid = $this->dateField->validate($validator);
$timeValid = $this->timeField->validate($validator);
// Validate if both subfields are valid
return $dateValid && $timeValid;
2016-11-29 00:31:16 +01:00
}
public function performReadonlyTransformation()
{
$field = clone $this;
$field->setReadonly(true);
return $field;
}
public function __clone()
{
$this->dateField = clone $this->dateField;
$this->timeField = clone $this->timeField;
}
/**
* @return string
*/
public function getTimezone()
{
return $this->timezone;
}
/**
* Custom timezone
*
* @var string
*/
protected $timezone = null;
/**
* @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");
}
// 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;
2016-11-29 00:31:16 +01: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
}