Yii 1.1: i18n-datetime-behavior

Automatic conversion for I18N date and datetime fields
40 followers

This extension is a behavior that can be used in models to allow them to automatically parse and format i18N date formats.

The behavior scans for date and datetime fields in the model attributes, and do the conversions needed.

Documentation

Requirements

  • Yii 1.0.9 or above
  • You need to have defined your desired language at the entry script.

Installation

  • Extract the release file and put it under protected/extensions

Usage

In you model, add the following code:

public function behaviors()
{
    return array('datetimeI18NBehavior' => array('class' => 'ext.DateTimeI18NBehavior')); // 'ext' is in Yii 1.0.8 version. For early versions, use 'application.extensions' instead.
}

IMPORTANT! This behavior changes afterFind and beforeSave events. If your model have to have coding, please, don't forget to add the parent reference:

protected function beforeSave(){
    if (!parent::beforeSave()) return false;
    ....your code
}

The same for afterFind method. Otherwise, this behavior will not make effect.

Note: This extension was made for MySQL. But, it can be easily changed to be useful to other databases.

Note2: Actually, the extension have problems with datetime fields, just working with date fields (FIXED)

Use version 1.1 if you are using Yii 1.0.9. If not, use the original version (with the bug described on note #2)

Change Log

September 30, 2009

Fixed issue with optional date/datetime fields and incorrect convertion on datetime fields (works only with Yii 1.0.9. If you use this version, download I18N-datetime-behavior 1.1. If not, use the original version)

August 17, 2009

  • Initial release.

Total 20 comments

#13976 report it
yiim at 2013/07/10 07:57am
Refactored and simplified

This is my refactored DateTimeI18NBehavior. Tested with yii 1.13 and MySQL with german date format (dd.mm.yyyy)

<?php
class DateTimeI18NBehavior extends CActiveRecordBehavior {
 
    public $dateTimeYiiFormat = 'Y-m-d H:i:s';
 
    /*
    * The beforeSave event is raised before the record is saved to database.
    * Converts input dateformat into yiis dateformat (for example: 31.12.2013 to 2013-12-31) .
    */
    public function beforeSave($event) {
 
        // search for date/datetime columns. Convert it to dateformat used in database (Y-m-d)
        foreach ($event->sender->tableSchema->columns as $columnName => $column) {
 
            if (($column->dbType != 'date') and ($column->dbType != 'datetime'))
                continue;
 
            if (!strlen( $event->sender->$columnName)) { 
                $event->sender->$columnName= null;
                continue;
            }
 
            $event->sender->$columnName= date( $this->dateTimeYiiFormat, strtotime( $event->sender->$columnName));
 
        }
        return true;
    }
 
    /* 
    * This event is raised after the record is instantiated (loaded from database)
    * Converts yiis database dateformat to input dateformat (for example: 2013-12-31 to 31.12.2013) .
    */
    public function afterFind($event) {
 
        foreach($event->sender->tableSchema->columns as $columnName => $column){
 
            if (($column->dbType != 'date') and ($column->dbType != 'datetime'))
                continue;
 
            if (!strlen($event->sender->$columnName)) { 
                $event->sender->$columnName= null;
                continue;
            }
 
            $event->sender->$columnName=
                Yii::app()->dateFormatter->formatDateTime(
                        strtotime( $event->sender->$columnName), 'medium', ($column->dbType != 'date') ? 'medium' : '');
 
        }
        return true;
    }
 
}
#13862 report it
ricardograna at 2013/07/03 12:24am
Sorry guys! ;(

I will take a look and update it as soon as possible to fix all those problems

#13800 report it
Maxxer at 2013/06/27 05:01am
Github

Guys, I've uploaded the 1.1 source to GitHub.

Please file your bugs there, fork and submit your patches, we'll try to mantain it there in the hope Ricardo will come back :)

#11639 report it
Anil Konsal at 2013/01/24 08:24am
Extension Does not work with Latest Yii i.e 1.1.13 version

Extension Does not work with Latest Yii i.e 1.1.13 version.

Always show Dec 31, 1969 09:00:00

Please help

#11227 report it
alexandre correia at 2012/12/29 12:12pm
It does not work with eng_us datetime format

Hi All,

I've got an simple app that uses Brazilian Portuguese and English US languages i18n. In Brazilian Portuguese it works 100% fine, but whenever I've moved (following that link http://www.yiiframework.com/wiki/208/how-to-use-an-application-behavior-to-maintain-runtime-configuration/) to English US (I've notice that 'medium' datetime format is 'Dec 29, 2012 1:59:38 PM') the app does not update any date nor datetime fields (its become null and its will set to 'Dec 31, 1969 9:00:00 PM').

Any ideas, I will appreciate.

#7726 report it
Jniops at 2012/04/12 10:11am
modifications to work in mysql and postgresql

I realized modifications to work in mysql and postgresql

<?php
 
/*
 * DateTimeI18NBehavior
 * Automatically converts date and datetime fields to I18N format
 * 
 * Author: Ricardo Grana <rickgrana@yahoo.com.br>, <ricardo.grana@pmm.am.gov.br>
 * Version: 1.1
 * Requires: Yii 1.0.9 version 
 */
 
class DateTimeI18NBehavior extends CActiveRecordBehavior {
 
    public $dateOutcomeFormat = 'Y-m-d';
    public $dateTimeOutcomeFormat = 'Y-m-d H:i:s';
    public $dateIncomeFormat = 'yyyy-MM-dd';
    public $dateTimeIncomeFormat = 'yyyy-MM-dd hh:mm:ss';
 
    public function beforeSave($event) {
 
        //search for date/datetime columns. Convert it to pure PHP date format
        foreach ($event->sender->tableSchema->columns as $columnName => $column) {
 
            $strTime = ( strpos($column->dbType, 'time') );
            $strDate = ( strpos($column->dbType, 'date') );
 
            if ($strTime === false && $strDate === false)
                continue;
 
            if (!strlen($event->sender->$columnName)) {
                $event->sender->$columnName = null;
                continue;
            }
 
            if ($strDate !== true) {
                $event->sender->$columnName = date($this->dateOutcomeFormat, CDateTimeParser::parse($event->sender->$columnName, Yii::app()->locale->dateFormat));
            } else {
 
                $event->sender->$columnName = date($this->dateTimeOutcomeFormat, CDateTimeParser::parse($event->sender->$columnName, strtr(Yii::app()->locale->dateTimeFormat, array("{0}" => Yii::app()->locale->timeFormat,
                                    "{1}" => Yii::app()->locale->dateFormat))));
            }
        }
 
        return true;
    }
 
    public function afterFind($event) {
 
        foreach ($event->sender->tableSchema->columns as $columnName => $column) {
 
            $strTime = ( strpos($column->dbType, 'time') );
            $strDate = ( strpos($column->dbType, 'date') );
 
            if ($strTime === false && $strDate === false)
                continue;
 
            if (!strlen($event->sender->$columnName)) {
                $event->sender->$columnName = null;
                continue;
            }
 
            if ($strDate !== true) {
                $event->sender->$columnName = Yii::app()->dateFormatter->formatDateTime(
                        CDateTimeParser::parse($event->sender->$columnName, $this->dateIncomeFormat), 'medium', null);
            } else {
 
                $event->sender->$columnName =
                        Yii::app()->dateFormatter->formatDateTime(
                        CDateTimeParser::parse($event->sender->$columnName, $this->dateTimeIncomeFormat), 'medium', 'medium');
            }
        }
        return true;
    }
 
}

remembering to add in main.php

'sourceLanguage' => 'pt_br',
'language' => 'pt_br',
'timeZone' => 'America/Sao_Paulo',
#7415 report it
mariusn at 2012/03/21 03:04pm
USE FREEZY'S CODE!

Thanks Freezy! Your code works well!

@Author: Please update this extention to include these and other improvements/issues.

#5578 report it
Roman Solomatin at 2011/10/21 11:20am
Thank you

Very handy.

#4792 report it
Athos at 2011/08/17 08:48am
Error using CDbExpression('NOW()')

We have a problem when using:

$model->log_dt_alter = new CDbExpression('NOW()');

The date is always recorded as 1969-12-31 21:00:00

You must include the following checks:

if ($event->sender->$columnName instanceof CDbExpression) continue;
#4107 report it
dinhtrung at 2011/06/06 09:27pm
More Flexible Date Time parser

After playing with JuiDatePicker widget, I think this behavior should be extend with more flexible date time parser. I use jQuery UI language option in the Datepicker, and after attach the behavior, it can not parse the date time format, because it is hard coded with 'medium' format ship with Yii. Besides, I use MySQL db all the time, so the 4 props $dateOutcomeFormat... are keep intact (yeah, have to read for a while to know this... At first, I think this is the input and output for the end user, not the DB server ) Anyway, this is my patch to the Behavior:

@@ -15,12 +15,17 @@
    public $dateTimeOutcomeFormat = 'Y-m-d H:i:s';
 
    public $dateIncomeFormat = 'yyyy-MM-dd';
-   public $dateTimeIncomeFormat = 'yyyy-MM-dd hh:mm:ss';
+   public $dateTimeIncomeFormat = 'yyyy-MM-dd hh:mm:ss';
+
+   public $inFormat = 'long|short|medium';
+   public $outFormat = 'medium';
 
    public function beforeSave($event){
+       $informat = explode('|', $this->inFormat);
 
        //search for date/datetime columns. Convert it to pure PHP date format
        foreach($event->sender->tableSchema->columns as $columnName => $column){
+           Yii::log("Convert $columnName from format " . Yii::app()->locale->dateFormat . ' to ' .$this->dateOutcomeFormat, 'warning', 'ext.behaviors.DateTimeI18N');
 
            if (($column->dbType != 'date') and ($column->dbType != 'datetime')) continue;
            if (!is_string($event->sender->$columnName)) continue;
@@ -30,14 +35,25 @@
            }
 
            if (($column->dbType == 'date')) {
-               $event->sender->$columnName = date($this->dateOutcomeFormat, CDateTimeParser::parse($event->sender->$columnName, Yii::app()->locale->dateFormat));
+               foreach ($informat as $dateWidth) {
+                   $timestamp = CDateTimeParser::parse($event->sender->$columnName, Yii::app()->locale->getDateFormat($dateWidth));
+                   if ($timestamp) {
+                       $event->sender->$columnName = date($this->dateOutcomeFormat, $timestamp);
+                       break;
+                   }
+               }
+
            }else{
-
-               $event->sender->$columnName = date($this->dateTimeOutcomeFormat,
-                   CDateTimeParser::parse($event->sender->$columnName,
+               foreach ($informat as $dateWidth) {
+                   $timestamp = CDateTimeParser::parse($event->sender->$columnName,
                        strtr(Yii::app()->locale->dateTimeFormat,
-                           array("{0}" => Yii::app()->locale->timeFormat,
-                                 "{1}" => Yii::app()->locale->dateFormat))));
+                           array("{0}" => Yii::app()->locale->getTimeFormat($dateWidth),
+                                 "{1}" => Yii::app()->locale->getDateFormat($dateWidth))));
+                   if ($timestamp) {
+                       $event->sender->$columnName = date($this->dateTimeOutcomeFormat, $timestamp);
+                       break;
+                   }
+               }
            }
 
        }
#4063 report it
dinhtrung at 2011/06/02 09:59am
Add formatDate and formatTime

Thank for your extension. I'd like to use build-in Dateformat and Timeformat in message/locale folder that Yii provide. This extension only provide 'medium' as only format in the output. I have to add 2 more public attribute to use long or short ones.

#3901 report it
dhampik at 2011/05/19 06:41am
CTimestampBehavior confilct

Also, if someone uses CTimestampBehavior, this extension will also currupt the fields. To fix it you need to add

if (!is_string($event->sender->$columnName)) continue;

to the cycle in afterFind/afterSave/beforeSave events.

#3895 report it
dhampik at 2011/05/18 01:06pm
There is bug

There is a bug in this extension.

If the model will be saved twice, then the datetime columns will be corrupted.

Example:

$news = new News();
$news->publish_date = '10.10.2011';
$news->save(); // after that publish_date will be internally corrupted
$news->someOtherField = 'change';
$news->save(); // this will cause to save 1970-01-01 03:00:00 to publish_date

To fix this you should override not only beforeSave event in this extension, but also afterSave to take all those changes in beforeSave back.

So, you need to add afterSave method and do the same code as in afterFind.

I've spent several hours to figure out what's wrong and now I'm out of estimation on my project! First I thought, that the problem is in CAdvancedArBehavior, but then I figured out that there is a bug in this extension.

It will also be very useful, if you add public methods to convert date to timestamp and to database format (to be able to build proper datetime conditions for DB queries)

#3877 report it
dans at 2011/05/17 02:37am
Modify this for timestamp fields?

Hi all,

I have some timestamp fields in my database which are automatically populated when the record is created or modified.

Is it possible to modify use this extension to do a conversion on this type of field too, so that the presentation matches the datetime fields?

#3171 report it
freezy at 2011/03/24 04:02am
Few fixes and some optimization

Да простят меня боги за английский язык! :) There is fix for CForm ;)

<?php
 
/**
 * DateTimeI18NBehavior
 * Automatically converts date and datetime fields to I18N format
 *
 * @author Ricardo Grana <rickgrana@yahoo.com.br>, <ricardo.grana@pmm.am.gov.br>
 * @version 1.1
 */
class DateTimeI18NBehavior extends CActiveRecordBehavior
{
    public $dateOutcomeFormat = 'Y-m-d';
    public $dateTimeOutcomeFormat = 'Y-m-d H:i:s';
 
    public $dateIncomeFormat = 'yyyy-MM-dd';
    public $dateTimeIncomeFormat = 'yyyy-MM-dd hh:mm:ss';
 
    /**
     * List of columns by model classes. Contains only date and datetime columns
     * cache = array(
     *  typeName => array(
     *    'date' => array() // Columns with 'date' type
     *    'datetime' => array() // Columns with 'datetime' type
     *  )
     * )
     *
     * @var array
     * @see DateTimeI18NBehavior::checkCache
     */
    private static $cache = array();
 
    public function beforeSave($event)
    {
        $this->convertToPhpFormat($event->sender);
        return true;
    }
 
    /**
     * We must reconvert columns after they saved (little hack for CForm)
     */
    public function afterSave($event)
    {
        $this->convertToLocaleFormat($event->sender);
        return true;
    }
 
    public function afterFind($event)
    {
        $this->convertToLocaleFormat($event->sender);
        return true;
    }
 
    private function convertToLocaleFormat(CActiveRecord $model)
    {
        $this->checkCache($model);
        $type = get_class($model);
        $columns = &self::$cache[$type];
 
        // Convert all columns with 'date' type
        foreach ($columns['date'] as $columnName)
        {
            if (strlen($model->$columnName) > 0)
                $model->$columnName = Yii::app()->dateFormatter->formatDateTime(
                    CDateTimeParser::parse($model->$columnName, $this->dateIncomeFormat),
                    'medium',
                    null
                );
        }
 
        // Convert all columns with 'datetime' type
        foreach ($columns['datetime'] as $columnName)
        {
            if (strlen($model->$columnName) > 0)
                $model->$columnName = Yii::app()->dateFormatter->formatDateTime(
                    CDateTimeParser::parse($model->$columnName, $this->dateTimeIncomeFormat),
                    'medium',
                    'medium'
                );
        }
    }
 
    private function convertToPhpFormat(CActiveRecord $model)
    {
        $this->checkCache($model);
        $type = get_class($model);
        $columns = &self::$cache[$type];
 
        // Convert all columns with 'date' type
        foreach ($columns['date'] as $columnName)
        {
            if (strlen($model->$columnName) > 0)
                $model->$columnName = date(
                    $this->dateOutcomeFormat,
                    CDateTimeParser::parse($model->$columnName, Yii::app()->locale->dateFormat)
                );
        }
 
        // Convert all columns with 'datetime' type
        foreach ($columns['datetime'] as $columnName)
        {
            if (strlen($model->$columnName) > 0)
                $model->$columnName = date(
                    $this->dateTimeOutcomeFormat,
                    CDateTimeParser::parse(
                        $model->$columnName,
                        strtr(
                            Yii::app()->locale->dateTimeFormat,
                            array(
                                "{0}" => Yii::app()->locale->timeFormat,
                                "{1}" => Yii::app()->locale->dateFormat
                            )
                        )
                    )
                );
        }
    }
 
    /**
     * Check cache for type of $model, and make if need
     *
     * @param CActiveRecord $model
     */
    private function checkCache(CActiveRecord $model)
    {
        $type = get_class($model);
        if (!isset(self::$cache[$type]))
        {
            self::$cache[$type] = array(
                'date' => array(),
                'datetime' => array()
            );
 
            $columns = &self::$cache[$type];
            foreach ($model->tableSchema->columns as $columnName => $column)
            {
                if ($column->dbType == 'date' || $column->dbType == 'datetime')
                    $columns[$column->dbType][] = $columnName;
            }
        }
    }
}
#2102 report it
research4oscar at 2010/11/11 12:46pm
Unable to get i18n-datetime-behavior extension working

Following the Yii Blog Tutorial using yii-1.1.4, I created created the Post model along with the Post CRUD for tbl_post. I did however made a modification in the tbl_post table prior to running Gii. I changed the create_time to datetime. I was wondering if perhaps someone could help one out by showing exactly the file modifications needed to get this extension working for the blog example. So far what I've done is I've extracted the one (DateTimeI18NBehavior.php) file found within DateTimeI18NBehavior1.1.zip and placed it in the webroot:blog/protected/extensions folder. Lastly the only other changed I made was in the webroot:/blog/protected/views/post/_form.php where I added CJuiDatePicker to the create_time field. Here is what the Post model and Post-view-_form looks like:

/****START of webroot:/blog/models/Post.php*****/

<?php
 
/**
 * This is the model class for table "{{post}}".
 *
 * The followings are the available columns in table '{{post}}':
 * @property integer $id
 * @property string $title
 * @property string $content
 * @property string $tags
 * @property integer $status
 * @property string $create_time
 * @property string $update_time
 * @property integer $author_id
 * @property integer $category
 *
 * The followings are the available model relations:
 * @property Comment[] $comments
 * @property User $author
 * @property LookupPostStatus $status0
 */
class Post extends CActiveRecord
{
    /**
     * Returns the static model of the specified AR class.
     * @return Post the static model class
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    /**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return '{{post}}';
    }
 
    /**
     * @return array validation rules for model attributes.
     */
    public function rules()
    {
        // NOTE: you should only define rules for those attributes that
        // will receive user inputs.
        return array(
            array('title, content, status, author_id, category', 'required'),
            array('status, author_id, category', 'numerical', 'integerOnly'=>true),
            array('title', 'length', 'max'=>128),
            array('tags, create_time, update_time', 'safe'),
            // The following rule is used by search().
            // Please remove those attributes that should not be searched.
            array('id, title, content, tags, status, create_time, update_time, author_id, category', 'safe', 'on'=>'search'),
                        //array('create_time', 'type', 'type'=>'date', 'dateFormat'=>'MM/dd/yyyy'),
 
 
 
 
        );
    }
 
    /**
     * @return array relational rules.
     */
    public function relations()
    {
        // NOTE: you may need to adjust the relation name and the related
        // class name for the relations automatically generated below.
        return array(
            'comments' => array(self::HAS_MANY, 'Comment', 'post_id'),
            'author' => array(self::BELONGS_TO, 'User', 'author_id'),
            'status0' => array(self::BELONGS_TO, 'LookupPostStatus', 'status'),
        );
    }
 
    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        return array(
            'id' => 'ID',
            'title' => 'Title',
            'content' => 'Content',
            'tags' => 'Tags',
            'status' => 'Status',
            'create_time' => 'Create Time',
            'update_time' => 'Update Time',
            'author_id' => 'Author',
            'category' => 'Category',
        );
    }
 
    /**
     * Retrieves a list of models based on the current search/filter conditions.
     * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
     */
    public function search()
    {
        // Warning: Please modify the following code to remove attributes that
        // should not be searched.
 
        $criteria=new CDbCriteria;
 
        $criteria->compare('id',$this->id);
        $criteria->compare('title',$this->title,true);
        $criteria->compare('content',$this->content,true);
        $criteria->compare('tags',$this->tags,true);
        $criteria->compare('status',$this->status);
        $criteria->compare('create_time',$this->create_time,true);
        $criteria->compare('update_time',$this->update_time,true);
        $criteria->compare('author_id',$this->author_id);
        $criteria->compare('category',$this->category);
 
        return new CActiveDataProvider(get_class($this), array(
            'criteria'=>$criteria,
        ));
    }
 
}

/****END of webroot:/blog/protected/models/Post.php*****/

/****START of webroot:/blog/protected/views/post/_form*****/

<div class="form">
 
<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'post-form',
    'enableAjaxValidation'=>false,
)); ?>
 
    <p class="note">Fields with <span class="required">*</span> are required.</p>
 
    <?php echo $form->errorSummary($model); ?>
 
    <div class="row">
        <?php echo $form->labelEx($model,'title'); ?>
        <?php echo $form->textField($model,'title',array('size'=>60,'maxlength'=>128)); ?>
        <?php echo $form->error($model,'title'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'content'); ?>
        <?php echo $form->textArea($model,'content',array('rows'=>6, 'cols'=>50)); ?>
        <?php echo $form->error($model,'content'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'tags'); ?>
        <?php echo $form->textArea($model,'tags',array('rows'=>6, 'cols'=>50)); ?>
        <?php echo $form->error($model,'tags'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'status'); ?>
        <?php echo $form->textField($model,'status'); ?>
        <?php echo $form->error($model,'status'); ?>
    </div>
 
        <div class="row">
            <?php echo $form->labelEx($model,'create_time'); ?>
            <?php
                $this->widget('zii.widgets.jui.CJuiDatePicker', array(
                    'id'=>'create_time',
                    'model'=>$model,
                    'attribute'=>'create_time',
                    // additional javascript options for the date picker plugin
                    'options'=>array(
                        'showAnim'=>'fold',
                        'dateFormat'=>'yy-mm-dd',
                    ),
                    'htmlOptions'=>array(
                        'style'=>'height:20px;'
                    ),
                ));
            ?>
            <?php echo $form->error($model,'create_time'); ?>
        </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'update_time'); ?>
        <?php echo $form->textField($model,'update_time'); ?>
        <?php echo $form->error($model,'update_time'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'author_id'); ?>
        <?php echo $form->textField($model,'author_id'); ?>
        <?php echo $form->error($model,'author_id'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'category'); ?>
        <?php echo $form->textField($model,'category'); ?>
        <?php echo $form->error($model,'category'); ?>
    </div>
 
    <div class="row buttons">
        <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
    </div>
 
<?php $this->endWidget(); ?>
 
</div><!-- form -->

/****END of webroot:/blog/protected/views/post/_form*****/

#1954 report it
Mike at 2010/10/19 08:12am
Nice one!

Works pretty good!

You can also validate the date in the right format with the type validator. Just add a rule like:

array('somedate','type','type'=>'date','dateFormat'=>Yii::app()->locale->dateFormat),
#633 report it
rmrcoxa at 2010/04/04 03:47pm
Worked perfectly!

Using this extension I don't need to use CDateFormatter in every view. And dont need to reconvert again before save!

Thanks!

#874 report it
Junior - df9 at 2010/02/14 08:08am
That's great!!!

It works like a charm!!

Thanks, Ricardo!!

#1108 report it
FlyBot at 2009/12/07 02:52pm
OK

Good work

Leave a comment

Please to leave your comment.

Create extension
  • Yii Version: 1.1
  • License: Other Open Source License
  • Developed by: ricardograna
  • Category: Date and Time
  • Votes: +41 / -1
  • Downloaded: 3,688 times
  • Created on: Aug 17, 2009
  • Last updated: Sep 30, 2009