mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT Added user timezone support to DatetimeField
This commit is contained in:
parent
0601384cda
commit
c89bdbb268
@ -18,6 +18,11 @@
|
|||||||
* $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
|
* $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
|
* - "timezone": Set a different timezone for viewing. {@link dataValue()} will still save
|
||||||
|
* the time in PHP's default timezone (date_default_timezone_get()), its only a view setting.
|
||||||
|
* Note that the sub-fields ({@link getDateField()} and {@link getTimeField()})
|
||||||
|
* are not timezone aware, and will have their values set in local time, rather than server time.
|
||||||
|
*
|
||||||
* @package sapphire
|
* @package sapphire
|
||||||
* @subpackage forms
|
* @subpackage forms
|
||||||
*/
|
*/
|
||||||
@ -37,12 +42,14 @@ class DatetimeField extends FormField {
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $config = array(
|
protected $config = array(
|
||||||
'datavalueformat' => 'YYYY-MM-dd HH:mm:ss'
|
'datavalueformat' => 'YYYY-MM-dd HH:mm:ss',
|
||||||
|
'usertimezone' => null,
|
||||||
);
|
);
|
||||||
|
|
||||||
function __construct($name, $title = null, $value = ""){
|
function __construct($name, $title = null, $value = ""){
|
||||||
$this->dateField = new DateField($name . '[date]', false);
|
$this->dateField = new DateField($name . '[date]', false);
|
||||||
$this->timeField = new TimeField($name . '[time]', false);
|
$this->timeField = new TimeField($name . '[time]', false);
|
||||||
|
$this->timezoneField = new HiddenField($this->Name() . '[timezone]');
|
||||||
|
|
||||||
parent::__construct($name, $title, $value);
|
parent::__construct($name, $title, $value);
|
||||||
}
|
}
|
||||||
@ -52,62 +59,107 @@ class DatetimeField extends FormField {
|
|||||||
|
|
||||||
$this->dateField->setForm($form);
|
$this->dateField->setForm($form);
|
||||||
$this->timeField->setForm($form);
|
$this->timeField->setForm($form);
|
||||||
|
$this->timezoneField->setForm($form);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Field() {
|
function Field() {
|
||||||
Requirements::css(SAPPHIRE_DIR . '/css/DatetimeField.css');
|
Requirements::css(SAPPHIRE_DIR . '/css/DatetimeField.css');
|
||||||
|
|
||||||
return $this->dateField->FieldHolder() . $this->timeField->FieldHolder() . '<div class="clear"><!-- --></div>';
|
$tzField = ($this->getConfig('usertimezone')) ? $this->timezoneField->FieldHolder() : '';
|
||||||
|
return $this->dateField->FieldHolder() .
|
||||||
|
$this->timeField->FieldHolder() .
|
||||||
|
$tzField .
|
||||||
|
'<div class="clear"><!-- --></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the internal value to ISO date format.
|
* Sets the internal value to ISO date format, based on either a database value in ISO date format,
|
||||||
|
* or a form submssion in the user date format. Uses the individual date and time fields
|
||||||
|
* to take care of the actual formatting and value conversion.
|
||||||
|
*
|
||||||
|
* Value setting happens *before* validation, so we have to set the value even if its not valid.
|
||||||
|
*
|
||||||
|
* Caution: Only converts user timezones when value is passed as array data (= form submission).
|
||||||
|
* Weak indication, but unfortunately the framework doesn't support a distinction between
|
||||||
|
* setting a value from the database, application logic, and user input.
|
||||||
*
|
*
|
||||||
* @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
|
* @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
|
||||||
* keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
|
* keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
|
||||||
* the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
|
* the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
|
||||||
*/
|
*/
|
||||||
function setValue($val) {
|
function setValue($val) {
|
||||||
|
// If timezones are enabled, assume user data needs to be reverted to server timezone
|
||||||
|
if($this->getConfig('usertimezone')) {
|
||||||
|
// Accept user input on timezone, but only when timezone support is enabled
|
||||||
|
$userTz = (is_array($val) && array_key_exists('timezone', $val)) ? $val['timezone'] : null;
|
||||||
|
if(!$userTz) $userTz = $this->getConfig('usertimezone'); // fall back to defined timezone
|
||||||
|
} else {
|
||||||
|
$userTz = null;
|
||||||
|
}
|
||||||
|
|
||||||
if(empty($val)) {
|
if(empty($val)) {
|
||||||
|
$this->value = null;
|
||||||
$this->dateField->setValue(null);
|
$this->dateField->setValue(null);
|
||||||
$this->timeField->setValue(null);
|
$this->timeField->setValue(null);
|
||||||
} else {
|
} else {
|
||||||
// String setting is only possible from the database, so we don't allow anything but ISO format
|
// Case 1: String setting from database, in ISO date format
|
||||||
if(is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $this->locale)) {
|
if(is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $this->locale)) {
|
||||||
// split up in date and time string values.
|
$this->value = $val;
|
||||||
$valueObj = new Zend_Date($val, $this->getConfig('datavalueformat'), $this->locale);
|
}
|
||||||
// set date either as array, or as string
|
// Case 2: Array form submission with user date format
|
||||||
|
elseif(is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) {
|
||||||
|
|
||||||
|
$dataTz = date_default_timezone_get();
|
||||||
|
// If timezones are enabled, assume user data needs to be converted to server timezone
|
||||||
|
if($userTz) date_default_timezone_set($userTz);
|
||||||
|
|
||||||
|
// Uses sub-fields to temporarily write values and delegate dealing with their normalization,
|
||||||
|
// actual sub-field value setting happens later
|
||||||
|
$this->dateField->setValue($val['date']);
|
||||||
|
$this->timeField->setValue($val['time']);
|
||||||
|
if($this->dateField->dataValue() && $this->timeField->dataValue()) {
|
||||||
|
$userValueObj = new Zend_Date(null, null, $this->locale);
|
||||||
|
$userValueObj->setDate($this->dateField->dataValue(), $this->dateField->getConfig('datavalueformat'));
|
||||||
|
$userValueObj->setTime($this->timeField->dataValue(), $this->timeField->getConfig('datavalueformat'));
|
||||||
|
if($userTz) $userValueObj->setTimezone($dataTz);
|
||||||
|
$this->value = $userValueObj->get($this->getConfig('datavalueformat'), $this->locale);
|
||||||
|
unset($userValueObj);
|
||||||
|
} else {
|
||||||
|
// Validation happens later, so set the raw string in case Zend_Date doesn't accept it
|
||||||
|
$this->value = $val['date'] . ' ' . $val['time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($userTz) date_default_timezone_set($dataTz);
|
||||||
|
}
|
||||||
|
// Case 3: Value is invalid, but set it anyway to allow validation by the fields later on
|
||||||
|
else {
|
||||||
|
$this->dateField->setValue($val);
|
||||||
|
if(is_string($val) )$this->timeField->setValue($val);
|
||||||
|
$this->value = $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// view settings (dates might differ from $this->value based on user timezone settings)
|
||||||
|
if (Zend_Date::isDate($this->value, $this->getConfig('datavalueformat'), $this->locale)) {
|
||||||
|
$valueObj = new Zend_Date($this->value, $this->getConfig('datavalueformat'), $this->locale);
|
||||||
|
if($userTz) $valueObj->setTimezone($userTz);
|
||||||
|
|
||||||
|
// Set view values in sub-fields
|
||||||
if($this->dateField->getConfig('dmyfields')) {
|
if($this->dateField->getConfig('dmyfields')) {
|
||||||
$this->dateField->setValue($valueObj->toArray());
|
$this->dateField->setValue($valueObj->toArray());
|
||||||
} else {
|
} else {
|
||||||
$this->dateField->setValue($valueObj->get($this->dateField->getConfig('dateformat'), $this->locale));
|
$this->dateField->setValue($valueObj->get($this->dateField->getConfig('dateformat'), $this->locale));
|
||||||
}
|
}
|
||||||
// set time
|
|
||||||
$this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $this->locale));
|
$this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $this->locale));
|
||||||
}
|
}
|
||||||
// Setting from form submission
|
|
||||||
elseif(is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) {
|
|
||||||
$this->dateField->setValue($val['date']);
|
|
||||||
$this->timeField->setValue($val['time']);
|
|
||||||
} else {
|
|
||||||
$this->dateField->setValue($val);
|
|
||||||
$this->timeField->setValue($val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dataValue() {
|
function Value() {
|
||||||
$valDate = $this->dateField->dataValue();
|
$valDate = $this->dateField->Value();
|
||||||
$valTime = $this->timeField->dataValue();
|
$valTime = $this->timeField->Value();
|
||||||
|
|
||||||
// Only date is actually required, time is optional
|
|
||||||
if($valDate) {
|
|
||||||
if(!$valTime) $valTime = '00:00:00';
|
if(!$valTime) $valTime = '00:00:00';
|
||||||
|
|
||||||
return $valDate . ' ' . $valTime;
|
return $valDate . ' ' . $valTime;
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,6 +194,11 @@ class DatetimeField extends FormField {
|
|||||||
*/
|
*/
|
||||||
function setConfig($name, $val) {
|
function setConfig($name, $val) {
|
||||||
$this->config[$name] = $val;
|
$this->config[$name] = $val;
|
||||||
|
|
||||||
|
if($name == 'usertimezone') {
|
||||||
|
$this->timezoneField->setValue($val);
|
||||||
|
$this->setValue($this->dataValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,10 +118,14 @@ class TimeField extends TextField {
|
|||||||
$this->value = $this->valueObj->get($this->getConfig('timeformat'));
|
$this->value = $this->valueObj->get($this->getConfig('timeformat'));
|
||||||
}
|
}
|
||||||
// Fallback: Set incorrect value so validate() can pick it up
|
// Fallback: Set incorrect value so validate() can pick it up
|
||||||
else {
|
elseif(is_string($val)) {
|
||||||
$this->value = $val;
|
$this->value = $val;
|
||||||
$this->valueObj = null;
|
$this->valueObj = null;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$this->value = null;
|
||||||
|
$this->valueObj = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,12 +44,6 @@ class DatetimeFieldTest extends SapphireTest {
|
|||||||
|
|
||||||
$f = new DatetimeField('Datetime', null, '2003-03-29 23:59:38');
|
$f = new DatetimeField('Datetime', null, '2003-03-29 23:59:38');
|
||||||
$this->assertEquals('2003-03-29 23:59:38', $f->dataValue(), 'From date/time string');
|
$this->assertEquals('2003-03-29 23:59:38', $f->dataValue(), 'From date/time string');
|
||||||
|
|
||||||
$f = new DatetimeField('Datetime', null, '2003-03-29');
|
|
||||||
$this->assertEquals('2003-03-29 00:00:00', $f->dataValue(), 'From date string (no time)');
|
|
||||||
|
|
||||||
$f = new DatetimeField('Datetime', null, array('date' => '2003-03-29', 'time' => null));
|
|
||||||
$this->assertEquals('2003-03-29 00:00:00', $f->dataValue(), 'From date array (no time)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testConstructorWithoutArgs() {
|
function testConstructorWithoutArgs() {
|
||||||
@ -108,9 +102,66 @@ class DatetimeFieldTest extends SapphireTest {
|
|||||||
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 23:59:38');
|
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 23:59:38');
|
||||||
$this->assertTrue($f->validate(new RequiredFields()));
|
$this->assertTrue($f->validate(new RequiredFields()));
|
||||||
|
|
||||||
|
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29');
|
||||||
|
$this->assertTrue($f->validate(new RequiredFields()));
|
||||||
|
|
||||||
$f = new DatetimeField('Datetime', 'Datetime', 'wrong');
|
$f = new DatetimeField('Datetime', 'Datetime', 'wrong');
|
||||||
$this->assertFalse($f->validate(new RequiredFields()));
|
$this->assertFalse($f->validate(new RequiredFields()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testTimezone() {
|
||||||
|
$oldTz = date_default_timezone_get();
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
// Berlin and Auckland have 12h time difference in northern hemisphere winter
|
||||||
|
$f = new DatetimeField('Datetime', 'Datetime', '2003-12-24 23:59:59');
|
||||||
|
$f->setConfig('usertimezone', 'Pacific/Auckland');
|
||||||
|
$this->assertEquals('25/12/2003 11:59:59', $f->Value(), 'User value is formatted, and in user timezone');
|
||||||
|
$this->assertEquals('25/12/2003', $f->getDateField()->Value());
|
||||||
|
$this->assertEquals('11:59:59', $f->getTimeField()->Value());
|
||||||
|
$this->assertEquals('2003-12-24 23:59:59', $f->dataValue(), 'Data value is unformatted, and in server timezone');
|
||||||
|
|
||||||
|
date_default_timezone_set($oldTz);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTimezoneFromFormSubmission() {
|
||||||
|
$oldTz = date_default_timezone_get();
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
// Berlin and Auckland have 12h time difference in northern hemisphere summer, but Berlin and Moscow only 2h.
|
||||||
|
$f = new DatetimeField('Datetime', 'Datetime');
|
||||||
|
$f->setConfig('usertimezone', 'Pacific/Auckland'); // should be overridden by form submission
|
||||||
|
$f->setValue(array(
|
||||||
|
// pass in default format, at user time (Moscow)
|
||||||
|
'date' => '24/06/2003',
|
||||||
|
'time' => '23:59:59',
|
||||||
|
'timezone' => 'Europe/Moscow'
|
||||||
|
));
|
||||||
|
$this->assertEquals('24/06/2003 23:59:59', $f->Value(), 'View composite value matches user timezone');
|
||||||
|
$this->assertEquals('24/06/2003', $f->getDateField()->Value(), 'View date part matches user timezone');
|
||||||
|
$this->assertEquals('23:59:59', $f->getTimeField()->Value(), 'View time part matches user timezone');
|
||||||
|
// 2h difference to Moscow
|
||||||
|
$this->assertEquals('2003-06-24 21:59:59', $f->dataValue(), 'Data value matches server timezone');
|
||||||
|
|
||||||
|
date_default_timezone_set($oldTz);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTimezoneFromConfig() {
|
||||||
|
$oldTz = date_default_timezone_get();
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Berlin');
|
||||||
|
// Berlin and Auckland have 12h time difference in northern hemisphere summer, but Berlin and Moscow only 2h.
|
||||||
|
$f = new DatetimeField('Datetime', 'Datetime');
|
||||||
|
$f->setConfig('usertimezone', 'Europe/Moscow');
|
||||||
|
$f->setValue(array(
|
||||||
|
// pass in default format, at user time (Moscow)
|
||||||
|
'date' => '24/06/2003',
|
||||||
|
'time' => '23:59:59',
|
||||||
|
));
|
||||||
|
$this->assertEquals('2003-06-24 21:59:59', $f->dataValue(), 'Data value matches server timezone');
|
||||||
|
|
||||||
|
date_default_timezone_set($oldTz);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user