diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php index 5b2ea0474..a9c72e033 100644 --- a/src/Forms/DateField.php +++ b/src/Forms/DateField.php @@ -173,9 +173,9 @@ class DateField extends TextField */ public function getDateFormat() { + // Browsers expect ISO 8601 dates, localisation is handled on the client if ($this->getHTML5()) { - // Browsers expect ISO 8601 dates, localisation is handled on the client - $this->setDateFormat(DBDate::ISO_DATE); + return DBDate::ISO_DATE; } if ($this->dateFormat) { @@ -220,9 +220,7 @@ class DateField extends TextField ); } - - - if ($this->getHTML5() && $this->locale) { + if ($this->getHTML5() && $this->locale && $this->locale !== DBDate::ISO_LOCALE) { throw new \LogicException( 'Please opt-out of HTML5 processing of ISO 8601 dates via setHTML5(false) if using setLocale()' ); @@ -254,9 +252,8 @@ class DateField extends TextField */ protected function getInternalFormatter() { - $locale = i18n::config()->uninherited('default_locale'); $formatter = IntlDateFormatter::create( - i18n::config()->uninherited('default_locale'), + DBDate::ISO_LOCALE, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE ); @@ -448,6 +445,10 @@ class DateField extends TextField */ public function getLocale() { + // Use iso locale for html5 + if ($this->getHTML5()) { + return DBDate::ISO_LOCALE; + } return $this->locale ?: i18n::get_locale(); } diff --git a/src/Forms/DatetimeField.php b/src/Forms/DatetimeField.php index 16c94db8b..03c4bf6c0 100644 --- a/src/Forms/DatetimeField.php +++ b/src/Forms/DatetimeField.php @@ -5,6 +5,7 @@ namespace SilverStripe\Forms; use IntlDateFormatter; use InvalidArgumentException; use SilverStripe\i18n\i18n; +use SilverStripe\ORM\FieldType\DBDate; use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\ValidationResult; @@ -297,7 +298,7 @@ class DatetimeField extends TextField } $formatter = IntlDateFormatter::create( - i18n::config()->uninherited('default_locale'), + DBDate::ISO_LOCALE, IntlDateFormatter::MEDIUM, IntlDateFormatter::MEDIUM, $timezone @@ -337,10 +338,10 @@ class DatetimeField extends TextField $internalFormatter = $this->getInternalFormatter(); $timestamp = $internalFormatter->parse($value); - // Retry without "T" separator + // Retry with "T" separator if (!$timestamp) { $fallbackFormatter = $this->getInternalFormatter(); - $fallbackFormatter->setPattern(DBDatetime::ISO_DATETIME); + $fallbackFormatter->setPattern(DBDatetime::ISO_DATETIME_NORMALISED); $timestamp = $fallbackFormatter->parse($value); } diff --git a/src/ORM/FieldType/DBDate.php b/src/ORM/FieldType/DBDate.php index 98cf2f06f..9b8489b70 100644 --- a/src/ORM/FieldType/DBDate.php +++ b/src/ORM/FieldType/DBDate.php @@ -34,6 +34,12 @@ class DBDate extends DBField */ const ISO_DATE = 'y-MM-dd'; + /** + * Fixed locale to use for ISO date formatting. This is necessary to prevent + * locale-specific numeric localisation breaking internal date strings. + */ + const ISO_LOCALE = 'en_NZ'; + public function setValue($value, $record = null, $markChanged = true) { $value = $this->parseDate($value); @@ -77,8 +83,7 @@ class DBDate extends DBField } // Format as iso8601 - $formatter = $this->getFormatter(); - $formatter->setPattern($this->getISOFormat()); + $formatter = $this->getInternalFormatter(); return $formatter->format($source); } @@ -203,7 +208,43 @@ class DBDate extends DBField */ public function getFormatter($dateLength = IntlDateFormatter::MEDIUM, $timeLength = IntlDateFormatter::NONE) { - return new IntlDateFormatter(i18n::get_locale(), $dateLength, $timeLength); + return $this->getCustomFormatter(null, $dateLength, $timeLength); + } + + /** + * Return formatter in a given locale. Useful if localising in a format other than the current locale. + * + * @param string|null $locale The current locale, or null to use default + * @param string|null $pattern Custom pattern to use for this, if required + * @param int $dateLength + * @param int $timeLength + * @return IntlDateFormatter + */ + public function getCustomFormatter( + $locale = null, + $pattern = null, + $dateLength = IntlDateFormatter::MEDIUM, + $timeLength = IntlDateFormatter::NONE + ) { + $locale = $locale ?: i18n::get_locale(); + $formatter = IntlDateFormatter::create($locale, $dateLength, $timeLength); + if ($pattern) { + $formatter->setPattern($pattern); + } + return $formatter; + } + + /** + * Formatter used internally + * + * @internal + * @return IntlDateFormatter + */ + protected function getInternalFormatter() + { + $formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDate::ISO_DATE); + $formatter->setLenient(false); + return $formatter; } /** @@ -271,7 +312,8 @@ class DBDate extends DBField // Get user format $format = $member->getDateFormat(); - return $this->Format($format); + $formatter = $this->getCustomFormatter($format, $member->getLocale()); + return $formatter->format($this->getTimestamp()); } /** @@ -319,7 +361,9 @@ class DBDate extends DBField */ public function Rfc2822() { - return $this->Format('y-MM-dd HH:mm:ss'); + $formatter = $this->getInternalFormatter(); + $formatter->setPattern('y-MM-dd HH:mm:ss'); + return $formatter->format($this->getTimestamp()); } /** @@ -420,7 +464,7 @@ class DBDate extends DBField ); case "minutes": - $span = round($ago/60); + $span = round($ago / 60); return _t( __CLASS__ . '.MINUTES_SHORT_PLURALS', '{count} min|{count} mins', @@ -428,7 +472,7 @@ class DBDate extends DBField ); case "hours": - $span = round($ago/3600); + $span = round($ago / 3600); return _t( __CLASS__ . '.HOURS_SHORT_PLURALS', '{count} hour|{count} hours', @@ -436,7 +480,7 @@ class DBDate extends DBField ); case "days": - $span = round($ago/86400); + $span = round($ago / 86400); return _t( __CLASS__ . '.DAYS_SHORT_PLURALS', '{count} day|{count} days', @@ -444,7 +488,7 @@ class DBDate extends DBField ); case "months": - $span = round($ago/86400/30); + $span = round($ago / 86400 / 30); return _t( __CLASS__ . '.MONTHS_SHORT_PLURALS', '{count} month|{count} months', @@ -452,7 +496,7 @@ class DBDate extends DBField ); case "years": - $span = round($ago/86400/365); + $span = round($ago / 86400 / 365); return _t( __CLASS__ . '.YEARS_SHORT_PLURALS', '{count} year|{count} years', @@ -466,8 +510,8 @@ class DBDate extends DBField public function requireField() { - $parts=array('datatype'=>'date', 'arrayValue'=>$this->arrayValue); - $values=array('type'=>'date', 'parts'=>$parts); + $parts = array('datatype' => 'date', 'arrayValue' => $this->arrayValue); + $values = array('type' => 'date', 'parts' => $parts); DB::require_field($this->tableName, $this->name, $values); } diff --git a/src/ORM/FieldType/DBDatetime.php b/src/ORM/FieldType/DBDatetime.php index 78a5cff7f..7a058bf6f 100644 --- a/src/ORM/FieldType/DBDatetime.php +++ b/src/ORM/FieldType/DBDatetime.php @@ -228,6 +228,38 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider return new IntlDateFormatter(i18n::get_locale(), $dateLength, $timeLength); } + + /** + * Return formatter in a given locale. Useful if localising in a format other than the current locale. + * + * @param string|null $locale The current locale, or null to use default + * @param string|null $pattern Custom pattern to use for this, if required + * @param int $dateLength + * @param int $timeLength + * @return IntlDateFormatter + */ + public function getCustomFormatter( + $locale = null, + $pattern = null, + $dateLength = IntlDateFormatter::MEDIUM, + $timeLength = IntlDateFormatter::MEDIUM + ) { + return parent::getCustomFormatter($locale, $pattern, $dateLength, $timeLength); + } + + /** + * Formatter used internally + * + * @internal + * @return IntlDateFormatter + */ + protected function getInternalFormatter() + { + $formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDatetime::ISO_DATETIME); + $formatter->setLenient(false); + return $formatter; + } + /** * Get standard ISO date format string *