Merge pull request #6766 from open-sausages/pulls/4.0/6626-remove-jquery-datepicker

HTML5 Date Fields
This commit is contained in:
Damian Mooyman 2017-04-04 09:13:04 +12:00 committed by GitHub
commit 32578e07d6
23 changed files with 403 additions and 1279 deletions

View File

@ -4,7 +4,11 @@ summary: How to format and use the DateField class.
# DateField
This `FormField` subclass lets you display an editable date, in a single text input field.
It also provides a calendar date picker.
It implements the [HTML5 input date type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date)
(with `type=date`). In supported browsers, this will cause a localised date picker to appear for users.
HTML5 date fields present and save ISO 8601 date formats (`y-MM-dd`),
since the browser takes care of converting to/from a localised presentation.
Browsers without support receive an `<input type=text>` based polyfill.
The following example will add a simple DateField to your Page, allowing you to enter a date manually.
@ -34,65 +38,28 @@ The following example will add a simple DateField to your Page, allowing you to
## Custom Date Format
A custom date format for a [api:DateField] can be provided through `setDateFormat`.
This is only necessary if you want to opt-out of the built-in browser localisation via `type=date`.
:::php
// will display a date in the following format: 31-06-2012
DateField::create('MyDate')->setDateFormat('dd-MM-yyyy');
// will display a date in the following format: 31/06/2012
DateField::create('MyDate')
->setHTML5(false)
->setDateFormat('dd/MM/yyyy');
<div class="info" markdown="1">
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).
</div>
## 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')
## Separate Day / Month / Year Fields
To display separate input fields for day, month and year separately you can use the `SeparatedDateField` subclass`.
HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
:::php
SeparatedDateField::create('MyDate');
<div class="alert" markdown="1">
Any custom date format settings will be ignored.
</div>
## Calendar Picker
The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
:::php
DateField::create('MyDate')
->setShowCalendar(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.
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)
->setMaxDate('2012-12-31')
## Formatting Hints

View File

@ -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.
@ -126,32 +132,17 @@ Please refer to [W3C: Introduction to IDN and IRI](http://www.w3.org/Internation
### i18n in Form Fields
Date- and time related form fields support i18n ([api:DateField], [api:TimeField], [api:DatetimeField]).
Date and time related form fields are automatically localised ([api:DateField], [api:TimeField], [api:DatetimeField]).
Since they use HTML5 `type=date` and `type=time` fields by default, these fields will present dates
in a localised format chosen by the browser and operating system.
:::php
i18n::set_locale('ca_AD');
$field = new DateField(); // will automatically set date format defaults for 'ca_AD'
$field->setLocale('de_DE'); // will not update the date formats
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // sets typical 'de_DE' date format, shows as "23. Juni 1982"
Defaults can be applied globally for all field instances through the `DateField.default_config`
and `TimeField.default_config` [configuration arrays](/developer_guides/configuration).
If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used.
**Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
fields created through [api:DataObject::getCMSFields()] will get their i18n settings from a specific member
The [api:DateField] API can be enhanced by JavaScript, and comes with
[jQuery UI datepicker](http://jqueryui.com/demos/datepicker/) capabilities built-in.
The field tries to translate the date formats and locales into a format compatible with jQuery UI
(see [api:DateField_View_JQuery::$locale_map_] and [api:DateField_View_JQuery::convert_iso_to_jquery_format()]).
Fields can be forced to use a certain locale and date/time format by calling `setHTML5(false)`,
followed by `setLocale()` or `setDateFormat()`/`setTimeFormat()`.
:::php
$field = new DateField();
$field->setLocale('de_AT'); // set Austrian/German locale
$field->setConfig('showcalendar', true);
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // will be transformed to 'dd. MM yy' for jQuery
$field->setLocale('de_AT'); // set Austrian/German locale, defaulting format to dd.MM.y
$field->setDateFormat('d.M.y'); // set a more specific date format (single digit day/month)
## Translating text

View File

@ -398,6 +398,19 @@ 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.
Consequently, we've also removed `MemberDatetimeOptionsetField`.
`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 +1508,25 @@ 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 (`showcalendar` option),
and uses [HTML5 date pickers](https://www.wufoo.com/html5/types/4-date.html) by default instead.
* `DateField` provides an optional polyfill for
[browsers without HTML5 date picker support](http://caniuse.com/#feat=input-datetime)
* The `dmyfields` option has been replced with native HTML5 behaviour (as one single `<input type=date>`).
* `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()`)
#### <a name="overview-template-removed"></a>Template and Form Removed API
@ -1550,6 +1569,8 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
* `get_source_file_comments()`
* `getOption`
* `setOption`
* Removed `MemberDatetimeOptionsetField` (no replacement)
* Removed `DateField_View_JQuery` (replaced with native HTML5 support in `DateField`)
### <a name="overview-i18n"></a>i18n API

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Forms;
use IntlDateFormatter;
use SilverStripe\i18n\i18n;
use InvalidArgumentException;
use SilverStripe\ORM\FieldType\DBDate;
use SilverStripe\ORM\FieldType\DBDatetime;
/**
@ -85,13 +86,6 @@ class DateField extends TextField
*/
protected $dateLength = null;
/**
* Set whether to show placeholders
*
* @var bool
*/
protected $placeholders = true;
/**
* Override locale for client side.
*
@ -122,25 +116,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 = true;
/**
* @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;
}
@ -164,12 +160,8 @@ class DateField extends TextField
}
/**
* Get length of the date format to use. One of:
*
* - IntlDateFormatter::SHORT
* - IntlDateFormatter::MEDIUM
* - IntlDateFormatter::LONG
* - IntlDateFormatter::FULL
* 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
*
@ -192,6 +184,11 @@ class DateField extends TextField
*/
public function getDateFormat()
{
if ($this->getHTML5()) {
// Browsers expect ISO 8601 dates, localisation is handled on the client
$this->setDateFormat(DBDate::ISO_DATE);
}
if ($this->dateFormat) {
return $this->dateFormat;
}
@ -202,6 +199,7 @@ class DateField extends TextField
/**
* 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
@ -216,18 +214,40 @@ class DateField extends TextField
/**
* Get date formatter with the standard locale / date format
*
* @throws \LogicException
* @return IntlDateFormatter
*/
protected function getFormatter()
{
if ($this->getHTML5() && $this->dateFormat && $this->dateFormat !== DBDate::ISO_DATE) {
throw new \LogicException(
'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setDateFormat()'
);
}
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(),
IntlDateFormatter::NONE
);
if ($this->getHTML5()) {
// Browsers expect ISO 8601 dates, localisation is handled on the client
$formatter->setPattern(DBDate::ISO_DATE);
} elseif ($this->dateFormat) {
// Don't invoke getDateFormat() directly to avoid infinite loop
if ($this->dateFormat) {
$ok = $formatter->setPattern($this->dateFormat);
if (!$ok) {
throw new InvalidArgumentException("Invalid date format {$this->dateFormat}");
@ -243,59 +263,28 @@ 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.
$formatter->setPattern('y-MM-dd');
// CLDR ISO 8601 date.
$formatter->setPattern(DBDate::ISO_DATE);
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();
}
$html = $callback();
if ($clientView) {
$html = $clientView->onAfterRender($html);
}
return $html;
}
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;
@ -434,7 +423,9 @@ class DateField extends TextField
}
/**
* Caution: Will not update the 'dateformat' config value.
* Determines the presented/processed format based on locale defaults,
* instead of explicitly setting {@link setDateFormat()}.
* Only applicable with {@link setHTML5(false)}.
*
* @param string $locale
* @return $this
@ -445,29 +436,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();
@ -475,28 +443,6 @@ class DateField extends TextField
return $rules;
}
/**
* If placeholders are shown
*
* @return bool
*/
public function getPlaceholders()
{
return $this->placeholders;
}
/**
* Set if placeholders are shown
*
* @param bool $placeholders
* @return $this
*/
public function setPlaceholders($placeholders)
{
$this->placeholders = $placeholders;
return $this;
}
/**
* @return string
*/
@ -533,35 +479,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
*
@ -627,12 +544,4 @@ class DateField extends TextField
}
return $formatter->format($timestamp);
}
/**
* @return DateField_View_JQuery
*/
protected function getClientView()
{
return DateField_View_JQuery::create($this);
}
}

View File

@ -1,197 +0,0 @@
<?php
namespace SilverStripe\Forms;
use InvalidArgumentException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\i18n\i18n;
use SilverStripe\View\Requirements;
/**
* Preliminary API to separate optional view properties
* like calendar popups from the actual datefield logic.
*
* Caution: This API is highly volatile, and might change without prior deprecation.
*/
class DateField_View_JQuery
{
use Injectable;
use Configurable;
/**
* @var DateField
*/
protected $field;
/**
* @var array Maps values from {@link i18n::$all_locales} to
* localizations existing in jQuery UI.
*/
private static $locale_map = array(
'en_GB' => '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)MMM(?!M)/' => 'M',
// match exactly two capital Ms not preceeded or followed by an M
'/(?<!M)MM(?!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());
}
}

View File

@ -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 <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

View File

@ -1,177 +0,0 @@
<?php
namespace SilverStripe\Forms;
use SilverStripe\i18n\i18n;
/**
* Date field with separate inputs for d/m/y
*/
class SeparatedDateField extends DateField
{
/**
* @var string
*/
protected $separator = '/';
public function Field($properties = array())
{
// Three separate fields for day, month and year
$valArr = $this->iso8601ToArray($this->dataValue());
$fieldDay = NumericField::create($this->name . '[day]', false, $valArr ? $valArr['day'] : null)
->addExtraClass('day')
->setHTML5(true)
->setMaxLength(2);
$fieldMonth = NumericField::create($this->name . '[month]', false, $valArr ? $valArr['month'] : null)
->addExtraClass('month')
->setHTML5(true)
->setMaxLength(2);
$fieldYear = NumericField::create($this->name . '[year]', false, $valArr ? $valArr['year'] : null)
->addExtraClass('year')
->setHTML5(true)
->setMaxLength(4);
// Set placeholders
if ($this->getPlaceholders()) {
$fieldDay->setAttribute('placeholder', _t(__CLASS__ . '.DAY', 'Day'));
$fieldMonth->setAttribute('placeholder', _t(__CLASS__ . '.MONTH', 'Month'));
$fieldYear->setAttribute('placeholder', _t(__CLASS__ . '.YEAR', 'Year'));
}
$format = $this->getDateFormat();
$validFormat = (
stripos($format, 'd') !== false
&& stripos($format, 'm') !== false
&& stripos($format, 'y') !== false
);
if (!$validFormat) {
throw new \InvalidArgumentException(
'Invalid date format for field ordering: ' . $format
. '. Requires "d", "m", and "y" values to determine order'
);
}
$fields = array();
$fields[stripos($format, 'd')] = $fieldDay->Field();
$fields[stripos($format, 'm')] = $fieldMonth->Field();
$fields[stripos($format, 'y')] = $fieldYear->Field();
ksort($fields);
// Join all fields
$sep = '&nbsp;<span class="separator">' . $this->getSeparator() . '</span>&nbsp;';
return implode($sep, $fields);
}
/**
* @param $string
* @return self
*/
public function setSeparator($separator)
{
$this->separator = $separator;
return $this;
}
/**
* @return string
*/
public function getSeparator()
{
return $this->separator;
}
/**
* Convert array to timestamp
*
* @param array $value
* @return string
*/
public function arrayToISO8601($value)
{
if ($this->isEmptyArray($value)) {
return null;
}
// ensure all keys are specified
if (!isset($value['month']) || !isset($value['day']) || !isset($value['year'])) {
return null;
}
// Ensure valid range
if (!checkdate($value['month'], $value['day'], $value['year'])) {
return null;
}
// Note: Set formatter to strict for array input
$formatter = $this->getISO8601Formatter();
$timestamp = mktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
if ($timestamp === false) {
return null;
}
return $formatter->format($timestamp);
}
/**
* Convert iso 8601 date to array (day / month / year)
*
* @param string $date
* @return array|null Array form, or null if not valid
*/
public function iso8601ToArray($date)
{
if (!$date) {
return null;
}
$formatter = $this->getISO8601Formatter();
$timestamp = $formatter->parse($date);
if ($timestamp === false) {
return null;
}
// Format time manually into an array
return [
'day' => date('j', $timestamp),
'month' => date('n', $timestamp),
'year' => date('Y', $timestamp),
];
}
/**
* Assign value posted from form submission
*
* @param mixed $value
* @param mixed $data
* @return $this
*/
public function setSubmittedValue($value, $data = null)
{
// Filter out empty arrays
if ($this->isEmptyArray($value)) {
$value = null;
}
$this->rawValue = $value;
// Null case
if (!$value || !is_array($value)) {
$this->value = null;
return $this;
}
// Parse
$this->value = $this->arrayToISO8601($value);
return $this;
}
/**
* Check if this array is empty
*
* @param $value
* @return bool
*/
public function isEmptyArray($value)
{
return is_array($value) && !array_filter($value);
}
}

View File

@ -6,6 +6,7 @@ use IntlDateFormatter;
use InvalidArgumentException;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBTime;
/**
* Form field to display editable time values in an <input type="text"> field.
@ -57,6 +58,31 @@ class TimeField extends TextField
*/
protected $timezone = null;
/**
* Use HTML5-based input fields (and force ISO 8601 time formats).
*
* @var bool
*/
protected $html5 = true;
/**
* @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
*
@ -67,6 +93,11 @@ class TimeField extends TextField
*/
public function getTimeFormat()
{
if ($this->getHTML5()) {
// Browsers expect ISO 8601 times, localisation is handled on the client
$this->setTimeFormat(DBTime::ISO_TIME);
}
if ($this->timeFormat) {
return $this->timeFormat;
}
@ -77,6 +108,7 @@ class TimeField extends TextField
/**
* Set time 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
@ -108,12 +140,8 @@ class TimeField extends TextField
}
/**
* Get length of the time format to use. One of:
*
* - IntlDateFormatter::SHORT E.g. '6:31 PM'
* - IntlDateFormatter::MEDIUM E.g. '6:30:48 PM'
* - IntlDateFormatter::LONG E.g. '6:32:09 PM NZDT'
* - IntlDateFormatter::FULL E.g. '6:32:24 PM New Zealand Daylight Time'
* Get length of the time format to use.
* Only applicable with {@link setHTML5(false)}.
*
* @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants
*
@ -133,6 +161,24 @@ class TimeField extends TextField
*/
protected function getFormatter()
{
if ($this->getHTML5() && $this->timeFormat && $this->timeFormat !== DBTime::ISO_TIME) {
throw new \LogicException(
'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setTimeFormat()'
);
}
if ($this->getHTML5() && $this->timeLength) {
throw new \LogicException(
'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setTimeLength()'
);
}
if ($this->getHTML5() && $this->locale) {
throw new \LogicException(
'Please opt-out of HTML5 processing of ISO 8601 times via setHTML5(false) if using setLocale()'
);
}
$formatter = IntlDateFormatter::create(
$this->getLocale(),
IntlDateFormatter::NONE,
@ -140,8 +186,11 @@ class TimeField extends TextField
$this->getTimezone()
);
// Don't invoke getDateFormat() directly to avoid infinite loop
if ($this->timeFormat) {
if ($this->getHTML5()) {
// Browsers expect ISO 8601 times, localisation is handled on the client
$formatter->setPattern(DBTime::ISO_TIME);
// 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 +213,22 @@ class TimeField extends TextField
date_default_timezone_get() // Default to server timezone
);
$formatter->setLenient(false);
// CLDR iso8601 time
// Note we omit timezone from this format, and we assume server TZ always.
$formatter->setPattern('HH:mm:ss');
$formatter->setPattern(DBTime::ISO_TIME);
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;
}
return $attributes;
if ($this->getHTML5()) {
$attributes['type'] = 'time';
}
/**
* 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()
@ -302,6 +338,10 @@ class TimeField extends TextField
}
/**
* Determines the presented/processed format based on locale defaults,
* instead of explicitly setting {@link setTimeFormat()}.
* Only applicable with {@link setHTML5(false)}.
*
* @param string $locale
* @return $this
*/
@ -337,9 +377,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->getHTML5()) {
$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);
}

View File

@ -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;
}

View File

@ -141,19 +141,7 @@ class DBTime extends DBField
public function scaffoldFormField($title = null, $params = null)
{
$field = TimeField::create($this->name, $title);
$format = $field->getTimeFormat();
// 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;
return TimeField::create($this->name, $title);
}
/**

View File

@ -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,20 +1311,24 @@ 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) {
$formatter = new IntlDateFormatter(
$this->getLocale(),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::NONE
);
$format = $formatter->getPattern();
$this->extend('updateDateFormat', $format);
return $format;
}
return $this->getDefaultDateFormat();
}
/**
* Get user locale
@ -1343,19 +1343,23 @@ 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();
$formatter = new IntlDateFormatter(
$this->getLocale(),
IntlDateFormatter::NONE,
IntlDateFormatter::MEDIUM
);
$format = $formatter->getPattern();
$this->extend('updateTimeFormat', $format);
return $format;
}
//---------------------------------------------------------------------//
@ -1592,112 +1596,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
*/
public function getDefaultDateFormat()
{
$formatter = new IntlDateFormatter(
$this->getLocale(),
IntlDateFormatter::MEDIUM,
IntlDateFormatter::NONE
);
return $formatter->getPattern();
}
/**
* @return string
*/
public function getDefaultTimeFormat()
{
$formatter = new IntlDateFormatter(
$this->getLocale(),
IntlDateFormatter::NONE,
IntlDateFormatter::MEDIUM
);
$defaultTimeFormat = $formatter->getPattern();
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 +1617,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',

View File

@ -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
*/

View File

@ -2,9 +2,9 @@
namespace SilverStripe\Forms\Tests;
use IntlDateFormatter;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\SeparatedDateField;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBDatetime;
@ -105,21 +105,11 @@ class DateFieldTest extends SapphireTest
public function testSetValueWithDateString()
{
$f = new DateField('Date', 'Date');
$f->setHTML5(false);
$f->setSubmittedValue('29/03/2003');
$this->assertEquals($f->dataValue(), '2003-03-29');
}
public function testSetValueWithDateArray()
{
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => 29,
'month' => 03,
'year' => 2003
]);
$this->assertEquals($f->dataValue(), '2003-03-29');
}
public function testConstructorWithIsoDate()
{
// used by Form->loadDataFrom()
@ -145,84 +135,11 @@ class DateFieldTest extends SapphireTest
$this->assertFalse($f->validate(new RequiredFields()));
}
public function testEmptyValueValidation()
{
$validator = new RequiredFields();
$field = new SeparatedDateField('Date');
$this->assertTrue($field->validate($validator));
$field->setSubmittedValue([
'day' => '',
'month' => '',
'year' => '',
]);
$this->assertTrue($field->validate($validator));
}
public function testValidateArray()
{
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => 29,
'month' => 03,
'year' => 2003
]);
$this->assertTrue($f->validate(new RequiredFields()));
$f->setValue(null);
$this->assertTrue($f->validate(new RequiredFields()), 'NULL values are validating TRUE');
$f->setSubmittedValue(array());
$this->assertTrue($f->validate(new RequiredFields()), 'Empty array values are validating TRUE');
$f->setSubmittedValue([
'day' => null,
'month' => null,
'year' => null
]);
$this->assertTrue($f->validate(new RequiredFields()), 'Empty array values with keys are validating TRUE');
$f->setSubmittedValue([
'day' => 9999,
'month' => 9999,
'year' => 9999
]);
$this->assertFalse($f->validate(new RequiredFields()));
}
public function testValidateEmptyArrayValuesSetsNullForValueObject()
{
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => '',
'month' => '',
'year' => ''
]);
$this->assertNull($f->dataValue());
$f->setSubmittedValue([
'day' => null,
'month' => null,
'year' => null
]);
$this->assertNull($f->dataValue());
}
public function testValidateArrayValue()
{
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue(['day' => 29, 'month' => 03, 'year' => 2003]);
$this->assertTrue($f->validate(new RequiredFields()));
$f->setSubmittedValue(['month' => 03, 'year' => 2003]);
$this->assertFalse($f->validate(new RequiredFields()));
$f->setSubmittedValue(array('day' => 99, 'month' => 99, 'year' => 2003));
$this->assertFalse($f->validate(new RequiredFields()));
}
public function testFormatEnNz()
{
/* We get YYYY-MM-DD format as the data value for DD/MM/YYYY input value */
$f = new DateField('Date', 'Date');
$f->setHTML5(false);
$f->setSubmittedValue('29/03/2003');
$this->assertEquals($f->dataValue(), '2003-03-29');
}
@ -232,6 +149,7 @@ class DateFieldTest extends SapphireTest
// should get en_NZ by default through setUp()
i18n::set_locale('de_DE');
$f = new DateField('Date', 'Date', '29/03/2003');
$f->setHTML5(false);
$f->setValue('29.06.2006');
$this->assertEquals($f->dataValue(), '2006-06-29');
}
@ -242,6 +160,7 @@ class DateFieldTest extends SapphireTest
public function testMDYFormat()
{
$dateField = new DateField('Date', 'Date');
$dateField->setHTML5(false);
$dateField->setDateFormat('d/M/y');
$dateField->setSubmittedValue('31/03/2003');
$this->assertEquals(
@ -251,6 +170,7 @@ class DateFieldTest extends SapphireTest
);
$dateField2 = new DateField('Date', 'Date');
$dateField2->setHTML5(false);
$dateField2->setDateFormat('d/M/y');
$dateField2->setSubmittedValue('04/3/03');
$this->assertEquals(
@ -259,4 +179,40 @@ 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
* @expectedExceptionMessageRegExp /Please opt-out .* if using setDateFormat/
*/
public function testHtml5WithCustomFormatThrowsException()
{
$dateField = new DateField('Date', 'Date');
$dateField->setValue('2010-03-31');
$dateField->setDateFormat('d/M/y');
$dateField->Value();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessageRegExp /Please opt-out .* if using setDateLength/
*/
public function testHtml5WithCustomDateLengthThrowsException()
{
$dateField = new DateField('Date', 'Date');
$dateField->setValue('2010-03-31');
$dateField->setDateLength(IntlDateFormatter::MEDIUM);
$dateField->Value();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessageRegExp /Please opt-out .* if using setLocale/
*/
public function testHtml5WithCustomLocaleThrowsException()
{
$dateField = new DateField('Date', 'Date');
$dateField->setValue('2010-03-31');
$dateField->setLocale('de_DE');
$dateField->Value();
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace SilverStripe\Forms\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\DateField_View_JQuery;
class DateFieldViewJQueryTest extends SapphireTest
{
public function testConvert()
{
$this->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'
);
}
}

View File

@ -7,7 +7,6 @@ use SilverStripe\Control\Controller;
use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\SeparatedDateField;
use SilverStripe\Forms\Tests\DatetimeFieldTest\Model;
use SilverStripe\Forms\TimeField;
use SilverStripe\Forms\FieldList;
@ -38,6 +37,32 @@ class DatetimeFieldTest extends SapphireTest
$form = $this->getMockForm();
$form->Fields()->push($dateTimeField);
$dateTimeField->setSubmittedValue([
'date' => '2003-03-29',
'time' => '23:59:38'
]);
$validator = new RequiredFields();
$this->assertTrue($dateTimeField->validate($validator));
$m = new Model();
$form->saveInto($m);
$this->assertEquals('2003-03-29 23:59:38', $m->MyDatetime);
}
public function testFormSaveIntoLocalised()
{
$dateTimeField = new DatetimeField('MyDatetime');
$dateTimeField->getDateField()
->setHTML5(false)
->setLocale('en_NZ');
$dateTimeField->getTimeField()
->setHTML5(false)
->setLocale('en_NZ');
$form = $this->getMockForm();
$form->Fields()->push($dateTimeField);
// en_NZ standard format
$dateTimeField->setSubmittedValue([
'date' => '29/03/2003',
@ -97,6 +122,25 @@ class DatetimeFieldTest extends SapphireTest
public function testSetValueWithArray()
{
$datetimeField = new DatetimeField('Datetime', 'Datetime');
$datetimeField->setSubmittedValue([
'date' => '2003-03-29',
'time' => '23:00:00'
]);
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00');
}
public function testSetValueWithArrayLocalised()
{
$datetimeField = new DatetimeField('Datetime', 'Datetime');
$datetimeField->getDateField()
->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',
@ -105,17 +149,6 @@ class DatetimeFieldTest extends SapphireTest
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00');
}
public function testSetValueWithDmyArray()
{
$f = new DatetimeField('Datetime', 'Datetime');
$f->setDateField(new SeparatedDateField('Datetime[date]'));
$f->setSubmittedValue([
'date' => ['day' => 29, 'month' => 03, 'year' => 2003],
'time' => '11:00:00 pm'
]);
$this->assertEquals($f->dataValue(), '2003-03-29 23:00:00');
}
public function testValidate()
{
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 23:59:38');
@ -128,11 +161,20 @@ class DatetimeFieldTest extends SapphireTest
$this->assertFalse($f->validate(new RequiredFields()));
}
public function testTimezoneSet()
public function testTimezoneSetLocalised()
{
date_default_timezone_set('Europe/Berlin');
// Berlin and Auckland have 12h time difference in northern hemisphere winter
$datetimeField = new DatetimeField('Datetime', 'Datetime');
$datetimeField->getDateField()
->setHTML5(false)
->setLocale('en_NZ');
$datetimeField->getTimeField()
->setHTML5(false)
->setLocale('en_NZ');
$datetimeField->setTimezone('Pacific/Auckland');
$datetimeField->setValue('2003-12-24 23:59:59');
$this->assertEquals(
@ -149,11 +191,20 @@ class DatetimeFieldTest extends SapphireTest
);
}
public function testTimezoneFromConfig()
public function testTimezoneFromConfigLocalised()
{
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()
->setHTML5(false)
->setLocale('en_NZ');
$datetimeField->setTimezone('Europe/Moscow');
$datetimeField->setSubmittedValue([
// pass in default format, at user time (Moscow)
@ -170,7 +221,7 @@ class DatetimeFieldTest extends SapphireTest
$field = new DatetimeField('Datetime', 'Datetime');
$field->setForm($form);
$field->setSubmittedValue([
'date' => '24/06/2003',
'date' => '2003-06-24',
'time' => '23:59:59',
]);
$dateField = new DateField('Datetime[date]');
@ -195,8 +246,8 @@ class DatetimeFieldTest extends SapphireTest
$field = new DatetimeField('Datetime', 'Datetime');
$field->setForm($form);
$field->setSubmittedValue([
'date' => '24/06/2003',
'time' => '11:59:59 pm',
'date' => '2003-06-24',
'time' => '23:59:59',
]);
$timeField = new TimeField('Datetime[time]');
$field->setTimeField($timeField);

View File

@ -1,177 +0,0 @@
<?php
namespace SilverStripe\Forms\Tests;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\CSSContentParser;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Controller;
use SilverStripe\Forms\MemberDatetimeOptionsetField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;
class MemberDatetimeOptionsetFieldTest extends SapphireTest
{
protected static $fixture_file = 'MemberDatetimeOptionsetFieldTest.yml';
/**
* @param Member $member
* @return MemberDatetimeOptionsetField
*/
protected function createDateFormatFieldForMember($member)
{
$defaultDateFormat = $member->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());
}
}

View File

@ -1,6 +0,0 @@
SilverStripe\Security\Member:
noformatmember:
Email: noformat@test.com
delocalemember:
Email: delocalemember@test.com
Locale: de_DE

View File

@ -1,53 +0,0 @@
<?php
namespace SilverStripe\Forms\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\SeparatedDateField;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBDatetime;
class SeparatedDateFieldTest extends SapphireTest
{
protected function setUp()
{
parent::setUp();
i18n::set_locale('en_NZ');
DBDatetime::set_mock_now('2011-02-01 8:34:00');
}
public function testFieldOrderingBasedOnLocale()
{
$dateField = new SeparatedDateField('Date');
$dateField->setLocale('en_NZ');
$this->assertRegExp('/.*[day].*[month].*[year]/', $dateField->Field());
}
public function testFieldOrderingBasedOnDateFormat()
{
$dateField = new SeparatedDateField('Date');
$dateField->setDateFormat('y/MM/dd');
$this->assertRegExp('/.*[year].*[month].*[day]/', $dateField->Field());
}
public function testCustomSeparator()
{
$dateField = new SeparatedDateField('Date');
$dateField->setDateFormat('dd/MM/y');
$dateField->setSeparator('###');
$this->assertRegExp('/.*[day].*###.*[month].*###.*[day]/', $dateField->Field());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid date format
*/
public function testInvalidDateFormat()
{
$dateField = new SeparatedDateField('Date');
$dateField->setDateFormat('y/MM');
$dateField->Field();
}
}

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Forms\Tests;
use IntlDateFormatter;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\TimeField;
@ -40,11 +41,23 @@ 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()
$f = new TimeField('Time', 'Time');
$f->setLocale('de_DE');
$f->setHTML5(false);
$f->setLocale('fr_FR');
// TODO Find a hour format thats actually different
$f->setValue('23:59');
$this->assertEquals($f->dataValue(), '23:59:00');
@ -85,8 +98,7 @@ class TimeFieldTest extends SapphireTest
public function testOverrideWithNull()
{
$field = new TimeField('Time', 'Time');
$field->setValue('11:00pm');
$field->setValue('11:00:00');
$field->setValue('');
$this->assertEquals($field->dataValue(), '');
}
@ -94,33 +106,80 @@ class TimeFieldTest extends SapphireTest
/**
* Test that AM/PM is preserved correctly in various situations
*/
public function testPreserveAMPM()
public function testSetTimeFormat()
{
// Test with timeformat that includes hour
// Check pm
$f = new TimeField('Time', 'Time');
$f->setHTML5(false);
$f->setTimeFormat('h:mm:ss a');
$f->setValue('3:59 pm');
$this->assertEquals($f->dataValue(), '15:59:00');
// Check am
$f = new TimeField('Time', 'Time');
$f->setHTML5(false);
$f->setTimeFormat('h:mm:ss a');
$f->setValue('3:59 am');
$this->assertEquals($f->dataValue(), '03:59:00');
// Check with ISO date/time
$f = new TimeField('Time', 'Time');
$f->setHTML5(false);
$f->setTimeFormat('h:mm:ss a');
$f->setValue('15:59:00');
$this->assertEquals($f->dataValue(), '15:59:00');
// ISO am
$f = new TimeField('Time', 'Time');
$f->setHTML5(false);
$f->setTimeFormat('h:mm:ss a');
$f->setValue('03:59:00');
$this->assertEquals($f->dataValue(), '03:59:00');
}
public function testLenientSubmissionParseWithoutSecondsOnHtml5()
{
$f = new TimeField('Time', 'Time');
$f->setSubmittedValue('23:59');
$this->assertEquals($f->Value(), '23:59:00');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessageRegExp /Please opt-out .* if using setTimeFormat/
*/
public function testHtml5WithCustomFormatThrowsException()
{
$f = new TimeField('Time', 'Time');
$f->setValue('15:59:00');
$f->setTimeFormat('mm:HH');
$f->Value();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessageRegExp /Please opt-out .* if using setTimeLength/
*/
public function testHtml5WithCustomDateLengthThrowsException()
{
$f = new TimeField('Time', 'Time');
$f->setValue('15:59:00');
$f->setTimeLength(IntlDateFormatter::MEDIUM);
$f->Value();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessageRegExp /Please opt-out .* if using setLocale/
*/
public function testHtml5WithCustomLocaleThrowsException()
{
$f = new TimeField('Time', 'Time');
$f->setValue('15:59:00');
$f->setLocale('de_DE');
$f->Value();
}
}

View File

@ -324,41 +324,4 @@ class DBDateTest extends SapphireTest
DBDatetime::clear_mock_now();
}
/**
* @see testFormatFromSettings
* @return array
*/
public function dataTestFormatFromSettings()
{
return [
['2000-12-31', '31/12/2000'],
['31-12-2000', '31/12/2000'],
['2014-04-01', '01/04/2014'],
];
}
/**
* @dataProvider dataTestFormatFromSettings
* @param string $from
* @param string $to
*/
public function testFormatFromSettings($from, $to)
{
$this->suppressNotices();
$member = new Member();
$member->DateFormat = 'dd/MM/y';
$date = DBField::create_field('Date', $from);
$this->assertEquals($to, $date->FormatFromSettings($member));
}
/**
* Test that FormatFromSettings without a member defaults to Nice()
*/
public function testFormatFromSettingsEmpty()
{
$date = DBfield::create_field('Date', '2000-12-31');
$this->assertEquals('31/12/2000', $date->FormatFromSettings());
}
}

View File

@ -204,41 +204,4 @@ class DBDatetimeTest extends SapphireTest
DBDatetime::clear_mock_now();
}
public function dataTestFormatFromSettings()
{
return [
['2000-12-31 10:11:01', '31/12/2000 10:11:01'],
['2000-12-31 1:11:01', '31/12/2000 01:11:01'],
['2000-12-12 1:11:01', '12/12/2000 01:11:01'],
['2000-12-31', '31/12/2000 00:00:00'],
['2014-04-01 10:11:01', '01/04/2014 10:11:01']
];
}
/**
* @dataProvider dataTestFormatFromSettings
* @param string $from
* @param string $to
*/
public function testFormatFromSettings($from, $to)
{
$member = new Member();
$member->DateFormat = 'dd/MM/y';
$member->TimeFormat = 'HH:mm:ss';
$date = DBDatetime::create_field('Datetime', $from);
$this->assertEquals($to, $date->FormatFromSettings($member));
}
/**
* Test that FormatFromSettings without a member defaults to Nice()
*/
public function testFormatFromSettingsEmpty()
{
$date = DBDatetime::create_field('Datetime', '2000-12-31 10:11:01');
// note: Some localisation packages exclude the ',' in default medium format
$this->assertRegExp('#31/12/2000(,)? 10:11:01 AM#', $date->FormatFromSettings());
}
}

View File

@ -56,35 +56,4 @@ class DBTimeTest extends SapphireTest
$time = DBTime::create_field('Time', '17:15:55');
$this->assertEquals('5:15 PM', $time->Short());
}
public function dataTestFormatFromSettings()
{
return [
['10:11:01', '10:11:01 (AM)'],
['21:11:01', '9:11:01 (PM)'],
];
}
/**
* @dataProvider dataTestFormatFromSettings
* @param string $from
* @param string $to
*/
public function testFormatFromSettings($from, $to)
{
$member = new Member();
$member->TimeFormat = 'h:mm:ss (a)';
$date = DBTime::create_field('Time', $from);
$this->assertEquals($to, $date->FormatFromSettings($member));
}
/**
* Test that FormatFromSettings without a member defaults to Nice()
*/
public function testFormatFromSettingsEmpty()
{
$date = DBTime::create_field('Time', '10:11:01');
$this->assertEquals('10:11:01 AM', $date->FormatFromSettings());
}
}

View File

@ -395,15 +395,6 @@ class MemberTest extends FunctionalTest
$member->PasswordExpiry = date('Y-m-d', time() + 86400);
$this->assertFalse($member->isPasswordExpired());
}
public function testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat()
{
// Note: All default strings are based on locale defaults for en_US
$member = $this->objFromFixture(Member::class, 'noformatmember');
$this->assertEquals('MMM d, y', $member->DateFormat);
$this->assertEquals('h:mm:ss a', $member->TimeFormat);
}
public function testInGroups()
{
$staffmember = $this->objFromFixture(Member::class, 'staffmember');