How to overcome timezone problem in Magento?

when you save updated date or created date,always use gmt time Mage::getModel('core/date')->gmtDate()


So after spending nearly a day fighting with this same problem in my own extension, I thought I'd share my solution here. Something to note is that my entities are not utilizing the EAV system, each entity has it's own flat table with a column for each attribute. My entity has four datetime attributes-- two of which are populated from user input (open_date,close_date), and two of which are populated automatically by the source code (create_date,close_date).

The Entity's Model Class

In the entity's model class I included the following:

  1. A way to define and fetch what attributes are of the type datetime and are populated via user inputted data supplied in the store's local time.
  2. A method which converts just these fields from GMT time to the store's local time (more on this later).
  3. A _beforeSave() method which sets the 'edit_date' and 'create_date' attributes (which are not populated via user input) in GMT for me automatically on save.

The source code:

/**
 * Model's datetime attributes that are populated with user data supplied
 * in the store's local time.
 *
 * @var array
 */
protected $_dateFields = array(
    'open_date',
    'close_date',
);

/**
 * Return the model's datetime attributes that are populated with user
 * data supplied in the store's local time.
 *
 * @return array
 */
public function getDateFields()
{
    return $this->_dateFields;
}

/**
 * (non-PHPdoc)
 * @see Mage_Core_Model_Abstract::_beforeSave()
 */
protected function _beforeSave()
{
    parent::_beforeSave();

    $date = Mage::getModel('core/date')->gmtDate();
    if (!$this->getId()) {
        $this->setData('create_date', $date);
    }
    $this->setData('edit_date', $date);

    return $this;
}

The saveAction of the Entity's Admin Controller

In my controller's saveAction method, I utilized the getDateFields() method defined in the model class to know which attributes I need to change from the store's local time (which was user inputted) to GMT time prior to saving to the datebase. Note that this is only a partial snippit of my save method:

....

$data = $this->getRequest()->getPost()

// Convert user input time from the store's local time to GMT time
$dateFields = $model->getDateFields();
if (is_array($dateFields) && count($dateFields)) {

    $data           = $this->_filterDateTime($data, $dateFields);
    $store_timezone = new DateTimeZone(Mage::getStoreConfig('general/locale/timezone'));
    $gmt_timezone   = new DateTimeZone('Europe/London');

    foreach ($dateFields as $key) if (isset($data[$key])) {
        $dateTime = new DateTime($data[$key], $store_timezone);
        $dateTime->setTimezone($gmt_timezone);
        $data[$key] = $dateTime->format('Y-m-d H:i:s');
    }
}

$model->addData($data);

try {
    $model->save();

....

The Admin Form Block for editing the Entity

Unlike the Magento's admin grid widget, which expects datetime values from collections to be supplied in GMT, with intentions to convert these values to the store's local time prior to displaying the the page, Magento's admin form widget does not follow this behavior. Instead the form widget will accept the datetime value as is, and display it without automatically adjusting the time. Therefore, since values are stored in the database in GMT, we must first convert our user entered datetime attributes to the store's local time before supplying that data to the form. This is where our #2 on The Entity's Model Class comes into play.

Here is a PORTION of the _prepareForm() method of my admin form's block class (which extends Mage_Adminhtml_Block_Widget_Form). I've omitted the majority of my function, trying to only include the bare minimum that is relevant to this question, and still provide a valid class method:

protected function _prepareForm()
{
    $form           = new Varien_Data_Form();
    $model          = Mage::registry('YOUR_MODEL_CLASS');
    $date_format    = Mage::app()->getLocale()->getDateTimeFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM);
    $time_zone      = $this->__('Time Zone: %s', Mage::getStoreConfig('general/locale/timezone'));
    $calendar_img   = $this->getSkinUrl('images/grid-cal.gif');

    $fieldset = $form->addFieldset('base_fieldset', array('legend'=> $this->__('General Information')));

    $fieldset->addField('open_date', 'datetime', array(
        'name'     => 'open_date',
        'label'    => $this->__('Open Date'),
        'title'    => $this->__('Open Date'),
        'time'     => 'true',
        'image'    => $calendar_img,
        'format'   => $date_format,
        'style'    => 'width:160px',
        'required' => true,
        'note'     => $time_zone
    ));

    $fieldset->addField('close_date', 'datetime', array(
        'name'     => 'close_date',
        'label'    => $this->__('Close Date'),
        'title'    => $this->__('Close Date'),
        'time'     => 'true',
        'image'    => $calendar_img,
        'format'   => $date_format,
        'style'    => 'width:160px',
        'required' => true,
        'note'     => $time_zone
    ));

    if ($model->getId()) {
        $form->setValues($model->getAdminFormData());
    }

    $this->setForm($form);

    return parent::_prepareForm();
}

Most of this is follows any other form widget for Magento. However the one key thing that is important to note here is that rather than calling $form->setValues($model->getData()) we call $form->setValues($model->getAdminFormData()). Which, if we review my code from the first segment of this answer, this method takes of converting all datetime attributes, that are user inputted, from GMT to the store's local time.

End Result:

  1. All values saved into DB in GMT time.
  2. User inputted values are converted from GMT to store's local time, prior to giving it to edit form
  3. Admin Grid works as it always has, taking in GMT values and converting to the store's local time prior to rendering the grid on the page.

Hope this proves as a valuable resource to help someone else out there someday. Come time that you work on front-end development, keep in mind that the datetime values are in GMT in the DB!