diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md
index 40d761fb0..8870e5fea 100644
--- a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md
+++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md
@@ -40,19 +40,19 @@ A custom date format for a [api:DateField] can be provided through `setDateForma
DateField::create('MyDate')->setDateFormat('dd-MM-yyyy');
-The formats are based on [CLDR format](http://userguide.icu-project.org/formatparse/datetime).
+The formats are based on [ICU format](http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details).
## Min and Max Dates
Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or
-strtotime()).
+`strtotime()`).
:::php
DateField::create('MyDate')
->setMinDate('-7 days')
- ->setMaxDate'2012-12-31')
+ ->setMaxDate('2012-12-31')
## Separate Day / Month / Year Fields
@@ -66,33 +66,21 @@ HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
Any custom date format settings will be ignored.
-## Calendar Picker
+## Date Picker and HTML5 support
-The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
+The field can be used as a [HTML5 input date type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date)
+(with `type=date`) by calling `setHTML5(true)`.
:::php
DateField::create('MyDate')
- ->setShowCalendar(true);
+ ->setHTML5(true);
-The jQuery date picker will support most custom locale formats (if left as default).
-If setting an explicit date format via setDateFormat() then the below table of supported
-characters should be used.
+In browsers [supporting HTML5 date inputs](caniuse.com/#feat=input-datetime),
+this will cause a localised date picker to appear for users.
+In this mode, the field will be forced to present and save ISO 8601 date formats (`y-MM-dd`),
+since the browser takes care of converting to/from a localised presentation.
-It is recommended to use numeric format, as `MMM` or `MMMM` month names may not always pass validation.
-
-Constant | xxxxx
--------- | -----
-d | numeric day of the month (without leading zero)
-dd | numeric day of the month (with leading zero)
-EEE | dayname, abbreviated
-EEEE | dayname
-M | numeric month of the year (without leading zero)
-MM | numeric month of the year (with leading zero)
-MMM | monthname, abbreviated
-MMMM | monthname
-y | year (4 digits)
-yy | year (2 digits)
-yyyy | year (4 digits)
+Browsers without support receive an `` based polyfill.
## Formatting Hints
diff --git a/docs/en/02_Developer_Guides/13_i18n/index.md b/docs/en/02_Developer_Guides/13_i18n/index.md
index 46023817f..f5363cab9 100644
--- a/docs/en/02_Developer_Guides/13_i18n/index.md
+++ b/docs/en/02_Developer_Guides/13_i18n/index.md
@@ -71,18 +71,24 @@ and default alignment of paragraphs and tables to browsers.
### Date and time formats
-Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need
-to write your own logic for any frontend output.
+Formats can be set globally in the i18n class.
+You can use these settings for your own view logic.
:::php
Config::inst()->update('i18n', 'date_format', 'dd.MM.YYYY');
Config::inst()->update('i18n', 'time_format', 'HH:mm');
-Most localization routines in SilverStripe use the [Zend_Date API](http://framework.zend.com/manual/1.12/en/zend.date.overview.html).
-This means all formats are defined in
-[ISO date format](http://framework.zend.com/manual/1.12/en/zend.date.constants.html),
+Localization in SilverStripe uses PHP's [intl extension](http://php.net/intl).
+Formats for it's [IntlDateFormatter](http://php.net/manual/en/class.intldateformatter.php)
+are defined in [ICU format](http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details),
not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
+These settings are not used for CMS presentation.
+Users can choose their own locale, which determines the date format
+that gets presented to them. Currently this is a mix of PHP defaults (for readonly `DateField` and `TimeField`),
+browser defaults (for `DateField` on browsers supporting HTML5), and [Moment.JS](http://momentjs.com/)
+client-side logic (for `DateField` polyfills and other readonly dates and times).
+
### Language Names
SilverStripe comes with a built-in list of common languages, listed by locale and region.
diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md
index 5db6936df..bb03f9099 100644
--- a/docs/en/04_Changelogs/4.0.0.md
+++ b/docs/en/04_Changelogs/4.0.0.md
@@ -398,6 +398,18 @@ In templates this can also be invoked as below:
<%t MyObject.PLURALS 'An item|{count} items' count=$Count %>
+#### Removed Member.DateFormat and Member.TimeFormat database settings
+
+We're using [native HTML5 date and time pickers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date)
+in `DateField` and `TimeField` now ([discussion](https://github.com/silverstripe/silverstripe-framework/issues/6626)),
+where the browser localises the output based on the browser/system preferences.
+In this context it no longer makes sense to give users control over their own
+date and time formats in their CMS profile.
+
+`Member->getDateFormat()` and `Member->getTimeFormat()` still exist, and default to
+the [IntlDateFormatter defaults](http://php.net/manual/en/class.intldateformatter.php) for the selected locale.
+
+
#### New asset storage mechanism
File system has been abstracted into an abstract interface. By default, the out of the box filesystem
@@ -1495,19 +1507,27 @@ New `DatetimeField` methods replace `getConfig()` / `setConfig()`:
New `DateField` methods replace `getConfig()` / `setConfig()`:
-* `getShowCalendar()` / `setShowCalendar()`
-* `getDateFormat()` / `setShowCalendar()`
+* `getDateFormat()` / `setDateFormat()`
* `getMinDate()` / `setMinDate()`
* `getMaxDate()` / `setMaxDate()`
-* `getPlaceholders()` / `setPlaceholders()`
-* `getClientLocale` / `setClientLocale`
* `getLocale()` / `setLocale()`
-* option `dmyfields` is now superceded with an `SeparatedDateField` class
+
+The `DateField` has changed behavior:
+
+* `DateField` no longer provides a jQuery UI date picker,
+ and uses [HTML5 date pickers](https://www.wufoo.com/html5/types/4-date.html) instead.
+ Use `setUseHTML()` to activate this mode (instead of `setConfig('showcalendar', true)`).
+* `DateField` provides an optional polyfill for
+ [browsers without HTML5 date picker support](http://caniuse.com/#feat=input-datetime)
+* The `dmyfields` option is now superceded with an `SeparatedDateField` class.
+* `getPlaceholders()` / `setPlaceholders()` moved to a new `SeparatedDateField` class
+* `getClientLocale` / `setClientLocale` have been removed (handled by `DateField->locale` and browser settings)
New `TimeField` methods replace `getConfig()` / `setConfig()`
* `getTimeFormat()` / `setTimeFormat()`
* `getLocale()` / `setLocale()`
+* `getClientConfig()` has been removed (in favour of `setHTML5()`)
#### Template and Form Removed API
diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php
index 4df37bb3f..6f8eef379 100644
--- a/src/Forms/DateField.php
+++ b/src/Forms/DateField.php
@@ -115,25 +115,27 @@ class DateField extends TextField
protected $rawValue = null;
/**
- * Check if calendar should be shown on the frontend
+ * Use HTML5-based input fields (and force ISO 8601 date formats).
*
+ * @var bool
+ */
+ protected $html5 = false;
+
+ /**
* @return bool
*/
- public function getShowCalendar()
+ public function getHTML5()
{
- return $this->showCalendar;
+ return $this->html5;
}
/**
- * Set if calendar should be shown on the frontend.
- * @internal WARNING: Experimental and volatile API.
- *
- * @param bool $show
+ * @param boolean $bool
* @return $this
*/
- public function setShowCalendar($show)
+ public function setHTML5($bool)
{
- $this->showCalendar = $show;
+ $this->html5 = $bool;
return $this;
}
@@ -209,6 +211,7 @@ class DateField extends TextField
/**
* Get date formatter with the standard locale / date format
*
+ * @throws \LogicException
* @return IntlDateFormatter
*/
protected function getFormatter()
@@ -219,8 +222,20 @@ class DateField extends TextField
IntlDateFormatter::NONE
);
- // Don't invoke getDateFormat() directly to avoid infinite loop
- if ($this->dateFormat) {
+ $isoFormat = 'y-MM-dd';
+
+ if ($this->dateFormat && $this->getHTML5() && $this->dateFormat !== $isoFormat) {
+ throw new \LogicException(sprintf(
+ 'Can\'t use a custom dateFormat value with $html5=true (needs to be %s)',
+ $isoFormat
+ ));
+ }
+
+ if ($this->getHTML5()) {
+ // Browsers expect ISO 8601 dates, localisation is handled on the client
+ $formatter->setPattern($isoFormat);
+ } elseif ($this->dateFormat) {
+ // Don't invoke getDateFormat() directly to avoid infinite loop
$ok = $formatter->setPattern($this->dateFormat);
if (!$ok) {
throw new InvalidArgumentException("Invalid date format {$this->dateFormat}");
@@ -236,59 +251,38 @@ class DateField extends TextField
*/
protected function getISO8601Formatter()
{
+ $locale = i18n::config()->uninherited('default_locale');
$formatter = IntlDateFormatter::create(
i18n::config()->uninherited('default_locale'),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::NONE
);
$formatter->setLenient(false);
- // CLDR iso8601 date.
+ // CLDR ISO 8601 date.
$formatter->setPattern('y-MM-dd');
return $formatter;
}
public function FieldHolder($properties = array())
{
- return $this->renderWithClientView(function () use ($properties) {
- return parent::FieldHolder($properties);
- });
- }
-
- public function SmallFieldHolder($properties = array())
- {
- return $this->renderWithClientView(function () use ($properties) {
- return parent::SmallFieldHolder($properties);
- });
- }
-
- /**
- * Generate field with client view enabled
- *
- * @param callable $callback
- * @return string
- */
- protected function renderWithClientView($callback)
- {
- $clientView = null;
- if ($this->getShowCalendar()) {
- $clientView = $this->getClientView();
- $clientView->onBeforeRender();
+ if ($this->getHTML5()) {
+ // Browsers expect ISO 8601 dates, localisation is handled on the client
+ $this->setDateFormat('y-MM-dd');
}
- $html = $callback();
- if ($clientView) {
- $html = $clientView->onAfterRender($html);
- }
- return $html;
+
+ return parent::FieldHolder($properties);
}
public function getAttributes()
{
$attributes = parent::getAttributes();
- // Merge with client config
- $config = $this->getClientConfig();
- foreach ($config as $key => $value) {
- $attributes["data-{$key}"] = $value;
+ $attributes['lang'] = i18n::convert_rfc1766($this->getLocale());
+
+ if ($this->getHTML5()) {
+ $attributes['type'] = 'date';
+ $attributes['min'] = $this->getMinDate();
+ $attributes['max'] = $this->getMaxDate();
}
return $attributes;
@@ -438,29 +432,6 @@ class DateField extends TextField
return $this;
}
- /**
- * Get locale code for client-side. Will default to getLocale() if omitted.
- *
- * @return string
- */
- public function getClientLocale()
- {
- if ($this->clientLocale) {
- return $this->clientLocale;
- }
- return $this->getLocale();
- }
-
- /**
- * @param string $clientLocale
- * @return DateField
- */
- public function setClientLocale($clientLocale)
- {
- $this->clientLocale = $clientLocale;
- return $this;
- }
-
public function getSchemaValidation()
{
$rules = parent::getSchemaValidation();
@@ -504,35 +475,6 @@ class DateField extends TextField
return $this;
}
- /**
- * Get client data properties for this field
- *
- * @return array
- */
- public function getClientConfig()
- {
- $view = $this->getClientView();
- $config = [
- 'showcalendar' => $this->getShowCalendar() ? 'true' : null,
- 'date-format' => $view->getDateFormat(), // https://api.jqueryui.com/datepicker/#option-dateFormat
- 'locale' => $view->getLocale(),
- ];
-
- // Format min/maxDate in format expected by jquery datepicker
- $min = $this->getMinDate();
- if ($min) {
- // https://api.jqueryui.com/datepicker/#option-minDate
- $config['min-date'] = $this->iso8601ToLocalised($min);
- }
- $max = $this->getMaxDate();
- if ($max) {
- // https://api.jqueryui.com/datepicker/#option-maxDate
- $config['max-date'] = $this->iso8601ToLocalised($max);
- }
-
- return $config;
- }
-
/**
* Convert date localised in the current locale to ISO 8601 date
*
@@ -599,11 +541,4 @@ class DateField extends TextField
return $formatter->format($timestamp);
}
- /**
- * @return DateField_View_JQuery
- */
- protected function getClientView()
- {
- return DateField_View_JQuery::create($this);
- }
}
diff --git a/src/Forms/DateField_View_JQuery.php b/src/Forms/DateField_View_JQuery.php
deleted file mode 100644
index b00539b80..000000000
--- a/src/Forms/DateField_View_JQuery.php
+++ /dev/null
@@ -1,197 +0,0 @@
- 'en-GB',
- 'en_US' => 'en',
- 'en_NZ' => 'en-GB',
- 'fr_CH' => 'fr',
- 'pt_BR' => 'pt-BR',
- 'sr_SR' => 'sr-SR',
- 'zh_CN' => 'zh-CN',
- 'zh_HK' => 'zh-HK',
- 'zh_TW' => 'zh-TW',
- );
-
- /**
- * @param DateField $field
- */
- public function __construct($field)
- {
- $this->field = $field;
-
- // Health check
- if (!$this->localePath('en')) {
- throw new InvalidArgumentException("Missing jquery config");
- }
- }
-
- /**
- * @return DateField
- */
- public function getField()
- {
- return $this->field;
- }
-
- /**
- * Get path to localisation file for a given locale, if it exists
- *
- * @param string $lang
- * @return string Relative path to file, or null if it isn't available
- */
- protected function localePath($lang)
- {
- $path = ADMIN_THIRDPARTY_DIR . "/jquery-ui/datepicker/i18n/jquery.ui.datepicker-{$lang}.js";
- if (file_exists(BASE_PATH . '/' . $path)) {
- return $path;
- }
- return null;
- }
-
- public function onBeforeRender()
- {
- }
-
- /**
- * @param String $html
- * @return string
- */
- public function onAfterRender($html)
- {
- if ($this->getField()->getShowCalendar()) {
- // Load config for this locale if available
- $locale = $this->getLocale();
- $localeFile = $this->localePath($locale);
- if ($localeFile) {
- Requirements::javascript($localeFile);
- }
- }
-
- return $html;
- }
-
- /**
- * Determines which language to use for jQuery UI, which
- * can be different from the value set in i18n.
- *
- * @return string
- */
- public function getLocale()
- {
- $locale = $this->getField()->getClientLocale();
-
- // Check standard mappings
- $map = Config::inst()->get(__CLASS__, 'locale_map');
- if (array_key_exists($locale, $map)) {
- return $map[$locale];
- }
-
- // Fall back to default lang (meaning "en_US" turns into "en")
- return i18n::getData()->langFromLocale($locale);
- }
-
- /**
- * Convert iso to jquery UI date format.
- * Needs to be consistent with Zend formatting, otherwise validation will fail.
- * Removes all time settings like hour/minute/second from the format.
- * See http://docs.jquery.com/UI/Datepicker/formatDate
- * From http://userguide.icu-project.org/formatparse/datetime
- *
- * @param string $format
- * @return string
- */
- public static function convert_iso_to_jquery_format($format)
- {
- $convert = array(
- '/([^d])d([^d])/' => '$1d$2',
- '/^d([^d])/' => 'd$1',
- '/([^d])d$/' => '$1d',
- '/dd/' => 'dd',
- '/SS/' => '',
- '/eee/' => 'd',
- '/e/' => 'N',
- '/D/' => '',
- '/EEEE/' => 'DD',
- '/EEE/' => 'D',
- '/w/' => '',
- // make single "M" lowercase
- '/([^M])M([^M])/' => '$1m$2',
- // make single "M" at start of line lowercase
- '/^M([^M])/' => 'm$1',
- // make single "M" at end of line lowercase
- '/([^M])M$/' => '$1m',
- // match exactly three capital Ms not preceeded or followed by an M
- '/(? 'M',
- // match exactly two capital Ms not preceeded or followed by an M
- '/(? 'mm',
- // match four capital Ms (maximum allowed)
- '/MMMM/' => 'MM',
- '/l/' => '',
- '/YYYY/' => 'yy',
- '/yyyy/' => 'yy',
- // See http://open.silverstripe.org/ticket/7669
- '/y{1,3}/' => 'yy',
- '/a/' => '',
- '/B/' => '',
- '/hh/' => '',
- '/h/' => '',
- '/([^H])H([^H])/' => '',
- '/^H([^H])/' => '',
- '/([^H])H$/' => '',
- '/HH/' => '',
- // '/mm/' => '',
- '/ss/' => '',
- '/zzzz/' => '',
- '/I/' => '',
- '/ZZZZ/' => '',
- '/Z/' => '',
- '/z/' => '',
- '/X/' => '',
- '/r/' => '',
- '/U/' => '',
- );
- $patterns = array_keys($convert);
- $replacements = array_values($convert);
-
- return preg_replace($patterns, $replacements, $format);
- }
-
- /**
- * Get client date format
- *
- * @return string
- */
- public function getDateFormat()
- {
- return static::convert_iso_to_jquery_format($this->getField()->getDateFormat());
- }
-}
diff --git a/src/Forms/DatetimeField.php b/src/Forms/DatetimeField.php
index 4190e2d3e..b47933faa 100644
--- a/src/Forms/DatetimeField.php
+++ b/src/Forms/DatetimeField.php
@@ -13,6 +13,10 @@ use SilverStripe\i18n\i18n;
* If you want to save into {@link Date} or {@link Time} columns,
* please instanciate the fields separately.
*
+ * This field does not implement the 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
diff --git a/src/Forms/TimeField.php b/src/Forms/TimeField.php
index ee5427510..4f63004b0 100644
--- a/src/Forms/TimeField.php
+++ b/src/Forms/TimeField.php
@@ -57,6 +57,31 @@ class TimeField extends TextField
*/
protected $timezone = null;
+ /**
+ * Use HTML5-based input fields (and force ISO 8601 date formats).
+ *
+ * @var bool
+ */
+ protected $html5 = false;
+
+ /**
+ * @return bool
+ */
+ public function getHTML5()
+ {
+ return $this->html5;
+ }
+
+ /**
+ * @param boolean $bool
+ * @return $this
+ */
+ public function setHTML5($bool)
+ {
+ $this->html5 = $bool;
+ return $this;
+ }
+
/**
* Get time format in CLDR standard format
*
@@ -140,8 +165,20 @@ class TimeField extends TextField
$this->getTimezone()
);
- // Don't invoke getDateFormat() directly to avoid infinite loop
- if ($this->timeFormat) {
+ $isoFormat = 'HH:mm:ss';
+
+ if ($this->timeFormat && $this->getHTML5() && $this->timeFormat !== $isoFormat) {
+ throw new \LogicException(sprintf(
+ 'Can\'t use a custom timeFormat value with $html5=true (needs to be %s)',
+ $isoFormat
+ ));
+ }
+
+ if ($this->getHTML5()) {
+ // Browsers expect ISO 8601 times, localisation is handled on the client
+ $formatter->setPattern($isoFormat);
+ // Don't invoke getTimeFormat() directly to avoid infinite loop
+ } elseif ($this->timeFormat) {
$ok = $formatter->setPattern($this->timeFormat);
if (!$ok) {
throw new InvalidArgumentException("Invalid time format {$this->timeFormat}");
@@ -164,35 +201,21 @@ class TimeField extends TextField
date_default_timezone_get() // Default to server timezone
);
$formatter->setLenient(false);
- // CLDR iso8601 time
+ // ISO 8601 time
// Note we omit timezone from this format, and we assume server TZ always.
$formatter->setPattern('HH:mm:ss');
return $formatter;
}
- public function getAttribute($name)
+ public function getAttributes()
{
$attributes = parent::getAttributes();
- // Merge with client config
- $config = $this->getClientConfig();
- foreach ($config as $key => $value) {
- $attributes["data-{$key}"] = $value;
+ if ($this->getHTML5()) {
+ $attributes['type'] = 'time';
}
- return $attributes;
- }
- /**
- * Get client config options for this field
- *
- * @return array
- */
- public function getClientConfig()
- {
- return [
- // @todo - Support javascript time picker
- 'timeformat' => $this->getTimeFormat(),
- ];
+ return $attributes;
}
public function Type()
@@ -337,9 +360,19 @@ class TimeField extends TextField
$fromFormatter = $this->getFormatter();
$toFormatter = $this->getISO8601Formatter();
$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->setHTML5(true)) {
+ $fromFormatter->setPattern('HH:mm');
+ $timestamp = $fromFormatter->parse($time);
+ }
+
+ // If timestamp still can't be detected, we've got an invalid time
if ($timestamp === false) {
return null;
}
+
return $toFormatter->format($timestamp);
}
diff --git a/src/ORM/FieldType/DBDate.php b/src/ORM/FieldType/DBDate.php
index 1e1fd26e8..4eeb6a0a7 100644
--- a/src/ORM/FieldType/DBDate.php
+++ b/src/ORM/FieldType/DBDate.php
@@ -512,17 +512,7 @@ class DBDate extends DBField
public function scaffoldFormField($title = null, $params = null)
{
$field = DateField::create($this->name, $title);
- $format = $field->getDateFormat();
-
- // Show formatting hints for better usability
- $now = DBDatetime::now()->Format($format);
- $field->setDescription(_t(
- 'FormField.EXAMPLE',
- 'e.g. {format}',
- 'Example format',
- [ 'format' => $now ]
- ));
- $field->setAttribute('placeholder', $format);
+ $field->setHTML5(true);
return $field;
}
diff --git a/src/ORM/FieldType/DBTime.php b/src/ORM/FieldType/DBTime.php
index d6eac7f06..629d4da1a 100644
--- a/src/ORM/FieldType/DBTime.php
+++ b/src/ORM/FieldType/DBTime.php
@@ -142,17 +142,8 @@ class DBTime extends DBField
public function scaffoldFormField($title = null, $params = null)
{
$field = TimeField::create($this->name, $title);
- $format = $field->getTimeFormat();
+ $field->setHTML5(true);
- // Show formatting hints for better usability
- $now = DBDatetime::now()->Format($format);
- $field->setDescription(_t(
- 'FormField.Example',
- 'e.g. {format}',
- 'Example format',
- [ 'format' => $now ]
- ));
- $field->setAttribute('placeholder', $format);
return $field;
}
diff --git a/src/Security/Member.php b/src/Security/Member.php
index e7090a706..1009e4a24 100644
--- a/src/Security/Member.php
+++ b/src/Security/Member.php
@@ -19,7 +19,6 @@ use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
use SilverStripe\Forms\ListboxField;
-use SilverStripe\Forms\MemberDatetimeOptionsetField;
use SilverStripe\i18n\i18n;
use SilverStripe\MSSQL\MSSQLDatabase;
use SilverStripe\ORM\ArrayList;
@@ -81,9 +80,6 @@ class Member extends DataObject implements TemplateGlobalProvider
'Locale' => 'Varchar(6)',
// handled in registerFailedLogin(), only used if $lock_out_after_incorrect_logins is set
'FailedLoginCount' => 'Int',
- // In ISO format
- 'DateFormat' => 'Varchar(30)',
- 'TimeFormat' => 'Varchar(30)',
);
private static $belongs_many_many = array(
@@ -1315,19 +1311,16 @@ class Member extends DataObject implements TemplateGlobalProvider
}
/**
- * Override the default getter for DateFormat so the
- * default format for the user's locale is used
- * if the user has not defined their own.
+ * Return the date format based on the user's chosen locale,
+ * falling back to the default format defined by the {@link i18n.get_locale()} setting.
*
* @return string ISO date format
*/
public function getDateFormat()
{
- $format = $this->getField('DateFormat');
- if ($format) {
- return $format;
- }
- return $this->getDefaultDateFormat();
+ $format = $this->getDefaultDateFormat();
+ $this->extend('updateDateFormat', $format);
+ return $format;
}
/**
@@ -1343,19 +1336,17 @@ class Member extends DataObject implements TemplateGlobalProvider
}
/**
- * Override the default getter for TimeFormat so the
- * default format for the user's locale is used
- * if the user has not defined their own.
+ * Return the time format based on the user's chosen locale,
+ * falling back to the default format defined by the {@link i18n.get_locale()} setting.
*
* @return string ISO date format
*/
public function getTimeFormat()
{
- $timeFormat = $this->getField('TimeFormat');
- if ($timeFormat) {
- return $timeFormat;
- }
- return $this->getDefaultTimeFormat();
+ $format = $this->getDefaultTimeFormat();
+ $this->extend('updateTimeFormat', $format);
+
+ return $format;
}
//---------------------------------------------------------------------//
@@ -1592,61 +1583,11 @@ class Member extends DataObject implements TemplateGlobalProvider
if ($permissionsTab) {
$permissionsTab->addExtraClass('readonly');
}
-
- // Date format selecter
- $mainFields->push(
- $dateFormatField = new MemberDatetimeOptionsetField(
- 'DateFormat',
- $this->fieldLabel('DateFormat'),
- $this->getDateFormats()
- )
- );
- $formatClass = get_class($dateFormatField);
- $dateFormatField->setValue($this->DateFormat);
- $dateTemplate = SSViewer::get_templates_by_class($formatClass, '_description_date', $formatClass);
- $dateFormatField->setDescriptionTemplate($dateTemplate);
-
- // Time format selector
- $mainFields->push(
- $timeFormatField = new MemberDatetimeOptionsetField(
- 'TimeFormat',
- $this->fieldLabel('TimeFormat'),
- $this->getTimeFormats()
- )
- );
- $timeFormatField->setValue($this->TimeFormat);
- $timeTemplate = SSViewer::get_templates_by_class($formatClass, '_description_time', $formatClass);
- $timeFormatField->setDescriptionTemplate($timeTemplate);
});
return parent::getCMSFields();
}
- /**
- * Get list of date formats with example values
- *
- * @return array
- */
- protected function getDateFormats()
- {
- $defaultDateFormat = $this->getDefaultDateFormat();
- $formats = [
- 'MMM d, y' => null,
- 'yyyy/MM/dd' => null,
- 'MM/dd/y' => null,
- 'dd/MM/y' => null,
- ];
- unset($formats[$defaultDateFormat]);
- $formats[$defaultDateFormat] = null;
- // Fill in each format with example
- foreach (array_keys($formats) as $format) {
- $formats[$format] = DBDatetime::now()->Format($format);
- }
- // Mark default format
- $formats[$defaultDateFormat] .= sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
- return $formats;
- }
-
/**
* @return string
*/
@@ -1674,30 +1615,6 @@ class Member extends DataObject implements TemplateGlobalProvider
return $defaultTimeFormat;
}
-
- /**
- * Get list of date formats with example values
- *
- * @return array
- */
- protected function getTimeFormats()
- {
- $defaultTimeFormat = $this->getDefaultTimeFormat();
- $formats = [
- 'h:mm a' => null,
- 'H:mm' => null,
- ];
- unset($formats[$defaultTimeFormat]);
- $formats[$defaultTimeFormat] = null;
- // Fill in each format with example
- foreach (array_keys($formats) as $format) {
- $formats[$format] = DBDatetime::now()->Format($format);
- }
- // Mark default format
- $formats[$defaultTimeFormat] .= sprintf(' (%s)', _t('Member.DefaultDateTime', 'default'));
- return $formats;
- }
-
/**
* @param bool $includerelations Indicate if the labels returned include relation fields
* @return array
@@ -1714,8 +1631,6 @@ class Member extends DataObject implements TemplateGlobalProvider
$labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', 'Password expiry date');
$labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', 'Security related date');
$labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
- $labels['DateFormat'] = _t('Member.DATEFORMAT', 'Date format');
- $labels['TimeFormat'] = _t('Member.TIMEFORMAT', 'Time format');
if ($includerelations) {
$labels['Groups'] = _t(
'Member.belongs_many_many_Groups',
diff --git a/src/i18n/i18n.php b/src/i18n/i18n.php
index ee7c5fcea..732eb3922 100644
--- a/src/i18n/i18n.php
+++ b/src/i18n/i18n.php
@@ -85,12 +85,20 @@ class i18n implements TemplateGlobalProvider
private static $default_locale = 'en_US';
/**
+ * System-wide date format. Will be overruled for CMS UI display
+ * by the format defaults inferred from the browser as well as
+ * any user-specific locale preferences.
+ *
* @config
* @var string
*/
private static $date_format = 'yyyy-MM-dd';
/**
+ * System-wide time format. Will be overruled for CMS UI display
+ * by the format defaults inferred from the browser as well as
+ * any user-specific locale preferences.
+ *
* @config
* @var string
*/
diff --git a/tests/php/Forms/DateFieldTest.php b/tests/php/Forms/DateFieldTest.php
index fbfe18e89..29eba129b 100644
--- a/tests/php/Forms/DateFieldTest.php
+++ b/tests/php/Forms/DateFieldTest.php
@@ -259,4 +259,16 @@ class DateFieldTest extends SapphireTest
"Even if input value hasn't got leading 0's in it we still get the correct data value"
);
}
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testHtml5WithCustomFormatThrowsException()
+ {
+ $dateField = new DateField('Date', 'Date');
+ $dateField->setValue('2010-03-31');
+ $dateField->setHTML5(true);
+ $dateField->setDateFormat('d/M/y');
+ $dateField->Value();
+ }
}
diff --git a/tests/php/Forms/DatefieldViewJQueryTest.php b/tests/php/Forms/DatefieldViewJQueryTest.php
deleted file mode 100644
index 83b7a931a..000000000
--- a/tests/php/Forms/DatefieldViewJQueryTest.php
+++ /dev/null
@@ -1,47 +0,0 @@
-assertEquals(
- 'M d, yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('MMM d, yyyy')
- );
-
- $this->assertEquals(
- 'd/mm/yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('d/MM/yyyy')
- );
-
- $this->assertEquals(
- 'dd.m.yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('dd.M.yyyy'),
- 'Month, no leading zero'
- );
-
- $this->assertEquals(
- 'dd.mm.yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('dd.MM.yyyy'),
- 'Month, two digit'
- );
-
- $this->assertEquals(
- 'dd.M.yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('dd.MMM.yyyy'),
- 'Abbreviated month name'
- );
-
- $this->assertEquals(
- 'dd.MM.yy',
- DateField_View_JQuery::convert_iso_to_jquery_format('dd.MMMM.yyyy'),
- 'Full month name'
- );
- }
-}
diff --git a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php b/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php
deleted file mode 100644
index 5755b4fa8..000000000
--- a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php
+++ /dev/null
@@ -1,177 +0,0 @@
-getDefaultDateFormat();
- $dateFormatMap = array(
- 'yyyy-MM-dd' => DBDatetime::now()->Format('yyyy-MM-dd'),
- 'yyyy/MM/dd' => DBDatetime::now()->Format('yyyy/MM/dd'),
- 'MM/dd/yyyy' => DBDatetime::now()->Format('MM/dd/yyyy'),
- 'dd/MM/yyyy' => DBDatetime::now()->Format('dd/MM/yyyy'),
- );
- $dateFormatMap[$defaultDateFormat] = DBDatetime::now()->Format($defaultDateFormat) . ' (default)';
- $field = new MemberDatetimeOptionsetField(
- 'DateFormat',
- 'Date format',
- $dateFormatMap
- );
- $field->setValue($member->getDateFormat());
- return $field;
- }
-
- /**
- * @param Member $member
- * @return MemberDatetimeOptionsetField
- */
- protected function createTimeFormatFieldForMember($member)
- {
- $defaultTimeFormat = $member->getDefaultTimeFormat();
- $timeFormatMap = array(
- 'h:mm a' => DBDatetime::now()->Format('h:mm a'),
- 'H:mm' => DBDatetime::now()->Format('H:mm'),
- );
- $timeFormatMap[$defaultTimeFormat] = DBDatetime::now()->Format($defaultTimeFormat) . ' (default)';
- $field = new MemberDatetimeOptionsetField(
- 'TimeFormat',
- 'Time format',
- $timeFormatMap
- );
- $field->setValue($member->getTimeFormat());
- return $field;
- }
-
- public function testDateFormatDefaultCheckedInFormField()
- {
- /** @var Member $member */
- $member = $this->objFromFixture(Member::class, 'noformatmember');
- $field = $this->createDateFormatFieldForMember($member);
- /** @skipUpgrade */
- $field->setForm(
- new Form(
- new Controller(),
- 'Form',
- new FieldList(),
- new FieldList()
- )
- ); // fake form
- // `MMM d, y` is default format for default locale (en_US)
- $parser = new CSSContentParser($field->Field());
- $xmlArr = $parser->getBySelector('#Form_Form_DateFormat_MMM_d_y');
- $this->assertEquals('checked', (string) $xmlArr[0]['checked']);
- }
-
- public function testTimeFormatDefaultCheckedInFormField()
- {
- /** @var Member $member */
- $member = $this->objFromFixture(Member::class, 'noformatmember');
- $field = $this->createTimeFormatFieldForMember($member);
- /** @skipUpgrade */
- $field->setForm(
- new Form(
- new Controller(),
- 'Form',
- new FieldList(),
- new FieldList()
- )
- ); // fake form
- // `h:mm:ss a` is the default for en_US locale
- $parser = new CSSContentParser($field->Field());
- $xmlArr = $parser->getBySelector('#Form_Form_TimeFormat_h:mm:ss_a');
- $this->assertEquals('checked', (string) $xmlArr[0]['checked']);
- }
-
- public function testDateFormatChosenIsCheckedInFormField()
- {
- /** @var Member $member */
- $member = $this->objFromFixture(Member::class, 'noformatmember');
- $member->setField('DateFormat', 'MM/dd/yyyy');
- $field = $this->createDateFormatFieldForMember($member);
- /** @skipUpgrade */
- $field->setForm(
- new Form(
- new Controller(),
- 'Form',
- new FieldList(),
- new FieldList()
- )
- ); // fake form
- $parser = new CSSContentParser($field->Field());
- $xmlArr = $parser->getBySelector('#Form_Form_DateFormat_MM_dd_yyyy');
- $this->assertEquals('checked', (string) $xmlArr[0]['checked']);
- }
-
- public function testDateFormatCustomFormatAppearsInCustomInputInField()
- {
- /** @var Member $member */
- $member = $this->objFromFixture(Member::class, 'noformatmember');
- $member->setField('DateFormat', 'dd MM yy');
- $field = $this->createDateFormatFieldForMember($member);
- /** @skipUpgrade */
- $field->setForm(
- new Form(
- new Controller(),
- 'Form',
- new FieldList(),
- new FieldList()
- )
- ); // fake form
- $parser = new CSSContentParser($field->Field());
- $xmlInputArr = $parser->getBySelector('.valcustom input');
- $this->assertEquals('checked', (string) $xmlInputArr[0]['checked']);
- $this->assertEquals('dd MM yy', (string) $xmlInputArr[1]['value']);
- }
-
- public function testDateFormValid()
- {
- $field = new MemberDatetimeOptionsetField('DateFormat', 'DateFormat');
- $validator = new RequiredFields();
- $this->assertTrue($field->validate($validator));
- $field->setSubmittedValue([
- 'Options' => '__custom__',
- 'Custom' => 'dd MM yyyy'
- ]);
- $this->assertTrue($field->validate($validator));
- $field->setSubmittedValue([
- 'Options' => '__custom__',
- 'Custom' => 'sdfdsfdfd1244'
- ]);
- // @todo - Be less forgiving of invalid CLDR date format strings
- $this->assertTrue($field->validate($validator));
- }
-
- public function testDescriptionTemplate()
- {
- $field = new MemberDatetimeOptionsetField('DateFormat', 'DateFormat');
-
- $this->assertEmpty($field->getDescription());
-
- $field->setDescription('Test description');
- $this->assertEquals('Test description', $field->getDescription());
-
- $field->setDescriptionTemplate(get_class($field).'_description_time');
- $this->assertNotEmpty($field->getDescription());
- $this->assertNotEquals('Test description', $field->getDescription());
- }
-}
diff --git a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml b/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml
deleted file mode 100644
index 655d400df..000000000
--- a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-SilverStripe\Security\Member:
- noformatmember:
- Email: noformat@test.com
- delocalemember:
- Email: delocalemember@test.com
- Locale: de_DE
diff --git a/tests/php/Forms/TimeFieldTest.php b/tests/php/Forms/TimeFieldTest.php
index 58d2e31dc..e2cf1e337 100644
--- a/tests/php/Forms/TimeFieldTest.php
+++ b/tests/php/Forms/TimeFieldTest.php
@@ -40,6 +40,17 @@ class TimeFieldTest extends SapphireTest
$this->assertFalse($f->validate(new RequiredFields()));
}
+ public function testValidateLenientWithHtml5()
+ {
+ $f = new TimeField('Time', 'Time', '23:59:59');
+ $f->setHTML5(true);
+ $this->assertTrue($f->validate(new RequiredFields()));
+
+ $f = new TimeField('Time', 'Time', '23:59'); // leave out seconds
+ $f->setHTML5(true);
+ $this->assertTrue($f->validate(new RequiredFields()));
+ }
+
public function testSetLocale()
{
// should get en_NZ by default through setUp()
@@ -123,4 +134,24 @@ class TimeFieldTest extends SapphireTest
$f->setValue('03:59:00');
$this->assertEquals($f->dataValue(), '03:59:00');
}
+
+ public function testLenientSubmissionParseWithoutSecondsOnHtml5()
+ {
+ $f = new TimeField('Time', 'Time');
+ $f->setHTML5(true);
+ $f->setSubmittedValue('23:59');
+ $this->assertEquals($f->Value(), '23:59:00');
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testHtml5WithCustomFormatThrowsException()
+ {
+ $f = new TimeField('Time', 'Time');
+ $f->setValue('15:59:00');
+ $f->setHTML5(true);
+ $f->setTimeFormat('mm:HH');
+ $f->Value();
+ }
}