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.
** NEW (GITHUB) Extension added to GitHub
Worked perfectly!
Using this extension I don't need to use CDateFormatter in every view. And dont need to reconvert again before save!
Thanks!
That's great!!!
It works like a charm!!
Thanks, Ricardo!!
OK
Good work
Zip files are working
I just downloaded and opened it.
looks like all extention zip files are corrupt so ..
looks like all extention zip files are corrupt so .. 5 stars to kiss and make up.
zip files seem to be corrupt
zip files seem to be corrupt
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),
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***/
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; } } } }
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?
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)
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.
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.
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; + } + } } }
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;
Thank you
Very handy.
USE FREEZY'S CODE!
Thanks Freezy! Your code works well!
@Author: Please update this extention to include these and other improvements/issues.
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',
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.
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
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 :)
Sorry guys! ;(
I will take a look and update it as soon as possible to fix all those problems
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; } }
Re-Refactored
I always hated strtotime() so I changed this way. Hope it could be useful to somebody.
<?php class DateTimeI18NBehavior extends CActiveRecordBehavior { // From 31/12/2013 To 2013-12-31 public function beforeSave($event) { foreach ($event->sender->tableSchema->columns as $columnName => $column) { switch (true) { case (($column->dbType != 'date') and ($column->dbType != 'datetime')) : break; case (!strlen( $event->sender->$columnName)) : $event->sender->$columnName= null; break; case ($column->dbType == 'datetime') : $dummyDate = DateTime::createFromFormat('d/m/Y H:i:s', $event->sender->$columnName); $event->sender->$columnName = $dummyDate->format('Y-m-d H:i:s'); break; case ($column->dbType == 'date') : $dummyDate = DateTime::createFromFormat('d/m/Y', $event->sender->$columnName); $event->sender->$columnName = $dummyDate->format('Y-m-d'); break; } } return true; } // From 2013-12-31 To 31/12/2013) . public function afterFind($event) { foreach($event->sender->tableSchema->columns as $columnName => $column) { switch (true) { case (($column->dbType != 'date') and ($column->dbType != 'datetime')) : break; case (!strlen( $event->sender->$columnName)) : $event->sender->$columnName= null; break; case ($column->dbType == 'datetime') : $dummyDate = DateTime::createFromFormat('Y-m-d H:i:s', $event->sender->$columnName); $event->sender->$columnName = $dummyDate->format('d/m/Y H:i:s'); break; case ($column->dbType == 'date') : $dummyDate = DateTime::createFromFormat('Y-m-d', $event->sender->$columnName); $event->sender->$columnName = $dummyDate->format('d/m/Y'); break; } } return true; } } ?>
Added to GitHub
Due to my ausence, now I've added the extension to a GitHub repository for who's interested to contribute
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.