Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

ENHANCEMENT Added user timezone support to DatetimeField

  • Loading branch information...
commit c89bdbb268b14228d6659754ca62c19ee398812b 1 parent 0601384
@chillu chillu authored
View
115 forms/DatetimeField.php
@@ -18,6 +18,11 @@
* $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
* </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
* @subpackage forms
*/
@@ -37,77 +42,124 @@ class DatetimeField extends FormField {
* @var 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 = ""){
$this->dateField = new DateField($name . '[date]', false);
$this->timeField = new TimeField($name . '[time]', false);
+ $this->timezoneField = new HiddenField($this->Name() . '[timezone]');
parent::__construct($name, $title, $value);
}
-
+
function setForm($form) {
parent::setForm($form);
$this->dateField->setForm($form);
$this->timeField->setForm($form);
+ $this->timezoneField->setForm($form);
}
function Field() {
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'
* 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()}).
*/
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)) {
+ $this->value = null;
$this->dateField->setValue(null);
$this->timeField->setValue(null);
} 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)) {
- // split up in date and time string values.
- $valueObj = new Zend_Date($val, $this->getConfig('datavalueformat'), $this->locale);
- // set date either as array, or as string
+ $this->value = $val;
+ }
+ // 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')) {
$this->dateField->setValue($valueObj->toArray());
} else {
$this->dateField->setValue($valueObj->get($this->dateField->getConfig('dateformat'), $this->locale));
}
- // set time
$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() {
- $valDate = $this->dateField->dataValue();
- $valTime = $this->timeField->dataValue();
-
- // Only date is actually required, time is optional
- if($valDate) {
- if(!$valTime) $valTime = '00:00:00';
- return $valDate . ' ' . $valTime;
- } else {
- // TODO
- return null;
- }
+ function Value() {
+ $valDate = $this->dateField->Value();
+ $valTime = $this->timeField->Value();
+ if(!$valTime) $valTime = '00:00:00';
+
+ return $valDate . ' ' . $valTime;
}
/**
@@ -142,6 +194,11 @@ function getLocale() {
*/
function setConfig($name, $val) {
$this->config[$name] = $val;
+
+ if($name == 'usertimezone') {
+ $this->timezoneField->setValue($val);
+ $this->setValue($this->dataValue());
+ }
}
/**
View
6 forms/TimeField.php
@@ -118,10 +118,14 @@ function setValue($val) {
$this->value = $this->valueObj->get($this->getConfig('timeformat'));
}
// Fallback: Set incorrect value so validate() can pick it up
- else {
+ elseif(is_string($val)) {
$this->value = $val;
$this->valueObj = null;
}
+ else {
+ $this->value = null;
+ $this->valueObj = null;
+ }
}
/**
View
63 tests/forms/DatetimeFieldTest.php
@@ -44,12 +44,6 @@ function testDataValue() {
$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');
-
- $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() {
@@ -108,9 +102,66 @@ function testValidate() {
$f = new DatetimeField('Datetime', 'Datetime', '2003-03-29 23:59:38');
$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');
$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);
+ }
}
/**
Please sign in to comment.
Something went wrong with that request. Please try again.