Using different Date and Datetime format in CakePHP 1.2

This is a really old post. I’ve wrote a new one which you can find it here.

Here I would like to show you a behaviour which will help you to handle different date format than yyyy-mm-dd supported from the database. The reason why I created this behaviour is because in most of my projects I had a requirement to display the date format in human readable format.

So, without extra words here is the behaviour class /Update: thanks to Shark from the comments below we have datetime support as well/:

<?php
/**
 * Prevent deletion if child record found
 *
 * @author  Nik Chankov
 * @url http://nik.chankov.net
 * @see http://nik.chankov.net/2007/12/20/using-different-date-format-in-cakephp-12/
 */

class DateFormatterBehavior extends ModelBehavior {
   
    /**
     * Class Vars
     * All these variables can be set from Configure class
     */

    //Data format for humans
    var $dateFormat = 'dd/mm/yyyy';
    //Dataformat for database
    var $databaseFormat = 'yyyy-mm-dd';
    //delimeted for humans
    var $delimiterDateFormat = '/';
    //delimiter for database
    var $delimiterDatabaseFormat = '-';
    //delimiter between date and time
    var $delimiterDateTimeFormat = ' ';
    /**
     * Empty Setup Function
    */

    function setup(&$model) {
        //Getting user defined vars
        $dateFormat = Configure::read('DateBehaviour.dateFormat');
        if($dateFormat != null){
            $this->dateFormat = $dateFormat;
        }
        $databaseFormat = Configure::read('DateBehaviour.databaseFormat');
        if($databaseFormat != null){
            $this->databaseFormat = $databaseFormat;
        }
        $delimiterDateFormat = Configure::read('DateBehaviour.delimiterDateFormat');
        if($delimiterDateFormat != null){
            $this->delimiterDateFormat = $delimiterDateFormat;
        }
        $delimiterDatabaseFormat = Configure::read('DateBehaviour.delimiterDatabaseFormat');
        if($delimiterDatabaseFormat != null){
            $this->delimiterDatabaseFormat = $delimiterDatabaseFormat;
        }
        $delimiterDateTimeFormat = Configure::read('DateBehaviour.delimiterDateTimeFormat');
        if($delimiterDateTimeFormat != null){
            $this->delimiterDateTimeFormat = $delimiterDateTimeFormat;
        }
        $this->model = $model;
    }
   
    /**
     * Function which convert one date from format1 to format2
     * basically this function play with those three elements of the date - dd, mm, yyyy
     * with delimiter you define which one of the elements is where
     *
     * @param string $date date string formated with format1
     * @param string $format1 format in which is formatted the $date variable by if it's comming from database is yyyy-mm-dd
     * @param string $format2 new format for the date.
     * @param char $delimiter separater between different elements of the date string /i.e. dash (-), dot(.), space ( ), etc/
     * @return string date formated with $format2
     * @access restricted
     */

    function _convertDate($date, $format1, $format2, $delimiterDateFormat, $delimiterDatabaseFormat){
        if($date == null OR $date == ''){
            return '';
        }
        //Split date and time
        $date = explode($this->delimiterDateTimeFormat, $date);
        $date_array = explode($delimiterDateFormat, $date[0]);
        $format1_array = explode($delimiterDateFormat, $format1);
        $format2_array = explode($delimiterDatabaseFormat, $format2);
       
        $current_array = array_combine($format1_array, $date_array);
        $new_array = array_combine($format2_array, $date_array);
        foreach($new_array as $key=>$value){
            $new_array[$key] = $current_array[$key];
        }
        if(isset($date[1])){
            //merge date and time again
            return implode($delimiterDatabaseFormat, $new_array).$this->delimiterDateTimeFormat.$date[1];
        }else{
            return implode($delimiterDatabaseFormat, $new_array);
        }
    }
   
    /**
     *Function which handle the convertion of the data arrays from database to user defined format and up side down
     * @param array $data data array from and to database
     * @param int $direction with 2 possible values '1' determine that data is going to database, '2' determine that data is pulled from database
     * @return array converted array;
     * @access restricted
     */

    function _changeDate($data, $direction){
        //just return false if the data var is false
        if($data == false){
            return false;
        }
        //Detecting the direction
        switch($direction){
            case 1:
                $format1 = $this->dateFormat;
                $format2 = $this->databaseFormat;
                $delimiterDateFormat = $this->delimiterDateFormat;
                $delimiterDatabaseFormat = $this->delimiterDatabaseFormat;
                break;
            case 2:
                $format1 = $this->databaseFormat;
                $format2 = $this->dateFormat;
                $delimiterDateFormat = $this->delimiterDatabaseFormat;
                $delimiterDatabaseFormat = $this->delimiterDateFormat;
                break;
            default:
                return false;
        }
        //result model
        foreach($data as $key=>$value){
            if($direction == 2){
                foreach($value as $key1=>$value1){
                    if($this->model->name == $key1){ //if it's current model;
                        $columns = $this->model->getColumnTypes();
                    } else {
                        //Fix for loading models on the fly
                        if(isset($this->model->{$key1})){
                            $columns = $this->model->{$key1}->getColumnTypes();
                        } else {
                            if($key1 != 'Parent'){
                                App::import('Model', $key1);
                                $model_on_the_fly = new $key1();
                                $columns = $model_on_the_fly->getColumnTypes();
                            }
                        }
                    }
                    foreach($value1 as $k=>$val){  
                        if(!is_array($val)){
                            if(in_array($k, array_keys($columns))){
                                if(($columns[$k] == 'date' || $columns[$k] == 'datetime') && ($k != 'created' && $k != 'modified')){
                                    if($val == '0000-00-00' || $val == '0000-00-00 00:00:00' || $val == ''){ //also clear the empty 0000-00-00 values
                                        $data[$key][$key1][$k] = null;
                                    } else {
                                        $data[$key][$key1][$k] = $this->_convertDate($val, $format1, $format2, $delimiterDateFormat, $delimiterDatabaseFormat);
                                    }
                                }
                            }
                        }
                    }  
                }
            } else {
                if($this->model->name == $key){ //if it's current model;
                    $columns = $this->model->getColumnTypes();
                } else {
                    //Fix for loading models on the fly
                    if(isset($this->model->{$key})){
                        $columns = $this->model->{$key}->getColumnTypes();
                    } else {
                        if($key != 'Parent'){
                            App::import('Model', $key);
                            $model_on_the_fly = new $key();
                            $columns = $model_on_the_fly->getColumnTypes();
                        }
                    }
                }
                foreach($value as $k=>$val){  
                    if(!is_array($val)){
                        if(in_array($k, array_keys($columns))){
                            if(($columns[$k] == 'date' || $columns[$k] == 'datetime') && ($k != 'created' && $k != 'modified')){
                                if($val == '0000-00-00' || $val == '0000-00-00 00:00:00' || $val == ''){ //also clear the empty 0000-00-00 values
                                    $data[$key][$k] = null;
                                } else {
                                    $data[$key][$k] = $this->_convertDate($val, $format1, $format2, $delimiterDateFormat, $delimiterDatabaseFormat);
                                }
                            }
                        }
                    }
                }
            }
        }
        return $data;
    }
   
    //Function before save.
    function beforeSave($model) {
        $model->data = $this->_changeDate($model->data, 1); //direction is from interface to database
        return true;
    }
   
    function afterFind(&$model, $results){
        $results = $this->_changeDate($results, 2); //direction is from database to interface
        return $results;
    }
}
?>

This code will work very good with the Advanced Date Picker Helper which I recently updated with CakePHP1.2 conventions /changing the way how to set initial variables/.

How to use it:
1. Save the class in /app/models/behaviours/date_formatter.php file.

2. If you have a requirement to set date format in a project, this probably mean that the requirement will apply to all fields and forms in the project, so it will be better if this behaviour apply for all models in the project. The easiest way is to add this to the AppModel class. Open your app_model.php and add the behaviour in it:

<?
class AppModel extends Model{
    ....
    var $actsAs = array(..., 'DateFormatter');
    ....
}

3. Set following Configuration parameters in configuration file /I prefer to use bootstrap.php/:

Configure::write('DateBehaviour.dateFormat', 'dd-mm-yyyy');
Configure::write('DateBehaviour.delimiterDateFormat', '-');

Basically the first one define the human date format and the second is the delimiter between day,month and year parts of the date string.

Bear in mind the if you want to use this behaviour in combination with DatePicker Helper you need to define also the Configure::write(‘DatePicker.format’, ‘%d-%m-%Y’)!

Here I will give an example of the configuration variables:

Configure::write('DateBehaviour.dateFormat', 'dd-mm-yyyy');
Configure::write('DateBehaviour.delimiterDateFormat', '-');
Configure::write('DatePicker.format', '%d-%m-%Y');

The following configuration will output format in the fields like: 29-12-2007 and when it’s going to be saved into DB the firmat will be 2007-12-29

Configure::write('DateBehaviour.dateFormat', 'mm/dd/yyyy');
Configure::write('DateBehaviour.delimiterDateFormat', '/');
Configure::write('DatePicker.format', '%m/%d/%Y');

Will output: 12/29/2007 , when it’s going to be saved into DB the format will be 2007-12-29 and so on.

Notice: There are some limitations about this behaviour here I will list them to prevent any confusions:

  • The behaviour working only for CakePHP 1.2
  • The behaviour support only numeric values of date format i.e. 01-12-2007, 2007-05-31 etc. I know it’s possible to handle full features of the date format, but I will appreciate if somebody give me some ideas how to make this quick and easy.
  • This behaviour working only for first level of Model relations, so if you set $recursive of the model to be more than 1 the children records wont be affected. I decided to leave this as it is now, because there is very nice Time Helper which will handle time formatting for presentation purpose without any problems. The main goal of this behaviour is to handle “fetch from DB and load into inputs of the form” and “fetch from the form and format the inputs properly for the DB insertion”.

I would appreciate if there are any suggestions or comments for this class!

Happy coding! πŸ˜‰

54 thoughts on “Using different Date and Datetime format in CakePHP 1.2

  1. DrSkie (Yevgeny Tomenko)

    On the trac.cakephp.org opened ticket (or two) with
    solution how to extend Dbo class and enable afterFind callback for non-primary models.
    This solution work well but only for afterFind callback.
    beforeFind method require global changes in Dbo and model classes.

  2. Daniel Hofstetter

    Just a detail, but I would rename the variables $delimiter1 and $delimiter2 to something that says what the variables are about, maybe $delimiterForOutput and $delimiterForDatabase?

  3. Nik Chankov Post author

    @all – thanks for youur comments.
    @Daniel you are right I also made some mistakes while using these vars so I changed them according to other 2 vars now they are $delimiterDateFormat and $delimiterDatabaseFormat. Thanks for the hint πŸ˜‰

  4. heavyboots

    This is a thing of beauty!!! I was just about to sit down to rewrite my ancient, grungy procedural PHP date formatting code for the new version of the company intranet and thank god “cakephp 1.2 afterfind” popped this up first. πŸ™‚

    One very, very minor note: Spelling-wise, date_formatter should actually have two t’s in it. I initially saved my behavior file as date_formatter.php and it took about 15 minutes to figure out why it wasn’t triggering, lol!

    Again, thanks very much. I am going to check out Advanced DatePicker next!

  5. Trav

    Hey Nik,

    Great work! I have followed your instructions but it just wont work. There are no errors reported. I just don’t think its running it or something. I would appreciate any assistance you can give me. Let me know if you need more information (as i know i haven’t given you much to work with).

    Cheers,
    Trav

  6. Pingback: Blog Dot Php With cakePHP » ???????? ?????? ?? cakePHP

  7. Drazen

    Hi, well I have problem.

    I have simple model with User, Customer, Invoice and InvoiceItem, and needed date format “dd.mm.yyyy”.

    invoice index page ist generated with bake.
    and than I have

    Notice (8): Undefined property: User::$Invoice [APP\models\behaviors\date_formatter.php, line 110]

    on line
    $columns = $this->model->{$key1}->getColumnTypes();

  8. Trav

    I think the Issue lie with the model setup. A temp fix is:

    Replace line 110:
    $columns = $this->model->{$key1}->getColumnTypes();

    With:
    $mod = new $key1;
    $columns = $mod->getColumnTypes();

    warning….this may be a bad hack! I’m just new to cake!

    Cheers,
    Trav.

  9. Nik Chankov Post author

    Guys, sorry for this delay.

    Indeed there was a problem in this behaviour and so far the solution is the same as Trav suggest – to create new model on the fly /if the model is not in the closest relation/ and to get the fields from there.

    The code snippet has been updated.

    In fact the difference from he Trav’s suggestion is that I check if this model class is loaded in the memory. Trav we thinking the same πŸ˜‰

    I don’t think it’s a problem using this method and I also think that it’s not the most elegant solution. It’s possible to store columns into Session var, but so far I will leave it as it is now.

    Hope how it wont give any troubles.

  10. Drazen

    inbetween I was going in this direction, but hadn’t cakephp knowledge for a solution.

    occures when there is a relation belongsTo and haveMany in model. There is also a “Check” query generated. (query content is a “Check” string)

    this fix do the trick (no it’s not a trick its solution for a while πŸ™‚

    I need this also for a decimal separator conversion.
    Number format in Europe shuld be comma not a point.
    http://en.wikipedia.org/wiki/Image:DecimalSeparator.png
    and I did’n find other solution.

    I’m making a link in cakephp-de group.

    thank You all!

  11. Fernando Mormul

    Hi,

    Just a correction to work properly wih PHP4

    Line 189:
    function beforeSave($model) {

    Add & to work properly:
    function beforeSave(&$model) {

  12. Pepito grillo

    Hi,

    There’s a problem in the behaviour. Related with created and modified fields. The model class put this fiels automatically, and it consults the database format directly (cake/libs/model/model.php, line 1150). That happens before the beforeSave function, so, when the behaviour change the date and try to save it it is in the incorrect format.

    I think that modifying the behaviour to don’t touch ‘modified’ and ‘created’ fields it will work fine.

    Hope it could help.

  13. Pepito grillo

    Ups, also another thing. The code was modified with delimiterDateFormat instead delimiter1, but in the text instruction, following the code, the config params are:
    Configure::write(‘DateBehaviour.delimeter1’, ‘-‘);
    So it doesn’t work until you change it. I don’t know if you can change this in the article easily, but I think that it could help people to use the behaviour quicker.

    Thanks for the code!

  14. Nik Chankov Post author

    @Pepito grillo – Thanks for the correction about the variable issue. About your other comment related to the “created” and “modified” fields. I just check my example and I haven’t seen any problem with these fields. Is it possible this problem to be caused by other reason? I am working on latest version of CakePHP1.2 on XAMPP and my created and modified field are working properly.
    By the way is it possible that instead od DATETIME for them you set as type a DATE? This could be the root of the problem πŸ™‚

  15. Mark

    Does this only work for Datetime fields?
    or for date fields as well?

    how about multilangual support?
    lets say, german and englisch.
    how could you switch between those two?
    dd.mm.yyyy – and dd/mm/yyyy etc

    thx, mark

  16. Nik Chankov Post author

    Mark, this behavior is working for mainly for Date fields.
    About multilingual – it’s support only numeric formats such as, 23-03-2008, 03-23-2008 etc. so it doesn’t need to be multilingual.
    Yes you can switch between formats by setting following vars in your configuration. I usually do this in bootstrap.php file with code:

    Configure::write('DateBehaviour.dateFormat', 'dd.mm.yyyy');
    <br>
    Configure::write('DateBehaviour.delimiterDateFormat', '.');

    basically this define the date format which should be in the input fields as well as delimeter, so the script would know what are the parts of the date and how to reformat them in order to fit properly in the database.

    hope this make sense.

  17. Mark

    i though more about beeing able to switch the “Format Settings” like dd.mm.yyyy to mm/dd/jjjj at runtime – lets say, if a user changes the language from german to englisch e.g.
    so he gets his familiar kind of date format.
    thats what i meant with multiligual^^

    but i think this is possible – in the language changing component, there has to be function to alter the Configure:: setting.
    didnt test it yet, though.

  18. Nik Chankov Post author

    Mark, yep this is the solution. When you switching the lang you need to Configure::write the language specific time format and users directly will see their familiar format in the fields.

  19. Josh Crowder

    I’m glad I found this! I was just about to spend endless hours making a new helper that handles the form->input() but this is a much much cooler. Is there a way to change it so it can handle datetime

  20. Nik Chankov Post author

    Hi Josh,

    This behavior is done especially for date format. I think it would be possible to make it datetime, but it need different function or at least to extend the current one. The good thing is that time format is unified to hh:mi:ss πŸ™‚

    If I was on you I would extend _convertDate() function so before converting the dates I would separate the time string from the date one.

    In this case in settings I need to have $timeDelimeter so if the date is let’s say in format:
    dd-mm-yyyy hh:mi:ss and the database require:
    yyyy-mm-dd hh:mi:ss I need to set $timeDelimeter = ” “;

    You know what? I think datetime format is pretty good idea and if you not in a hurry I will modify this behavior to support datetime as well πŸ™‚

  21. hugh

    The I’ve just changed _convertDate. It seems to work with non numeric date values. I’ve only tested it with dates like 19-Sep-2008. I also had to change the default date formats to Y-m-d supported by strtotime() and the delimiter fields are no longer requried.

    /**
     * @param $date Date string to be converted
     * @param $format The format string the date should be converted to.
     */

    function _convertDate($date, $format)
    {
            if($date == null OR $date == ''){
                return '';
            }

            $time = strtotime($date);
            $dateStr = date($format, $time);
            return $dateStr;
    }
  22. Nik Chankov Post author

    Hugh,

    I know that this way the code is more short and elegant, but especially for numeric date format it’s quite difficult to judge what is the format. Let’s say What is the date format of: 10.09.2008? Is it 9-th of October, or 10-th of September? πŸ™‚

    I also know that it’s possible to set date_default_timezone_set(). But I don’t know why, but I don’t trust this convention at all.

    In another hand, this behavior is made mainly to support date inputs, where it’s possible to add the date by keyboard and I believe that only digits are more convenient.

    I would say that I could put this function as additional let’s say __convertFreeDate() and if you want to use it, there could be set a trigger which will switch between these 2 functions.

    Anyway, it’s good that this behavior helps πŸ™‚

  23. Shark

    Hi Nik,

    Nice behavior!!, but i’ll need it for datetime too haha.

    Did you modify this behavior to support datetime already?

  24. Nik Chankov Post author

    Wow, Shark that was quick – 30 minutes for datatime support. In fact I had the same idea, But I wanted to put date-time delimiter in a parameter of the class just in case.

    With your permission I will replace your code in that post so everybody could use it.

    Thanks again.

  25. Nik Chankov Post author

    You know why? Because recently this behavior was only for date fields and these fields were datetime πŸ™‚

    I will add exception for them right now.

  26. Ezequiel Nuske

    Great code! Just what I was looking for. By the way, I had some trouble with models that worked with Alias.

    The problem was solved by replacing:

    if($this->model->name == $key1){ //if it's current model;

    with

    if($this->model->alias == $key1){ //if it's current model;

    Thanks again for releasing this code!

  27. Steve

    This is great, thanks! But.. it works on my local computer fine, but I just uploaded it to my server (Linux/Apache) and it stopped working, all dates are 0000-00-00 00:00:00! (It also messes up an account expiry date I have for my login users (using authake), so some users cant login). Hmmm I am stumped!

  28. Nik Chankov Post author

    Well, if it’s working on your machine, and on the server doesn’t this mean that settings on the server are messed up or the locale variable in php or mysql is set to different than yyyy-mm-dd HH:ii:ss format.

  29. Pingback: DateFormatter. Um behaviour simples e ΓΊtil « echo ‘mfandrade’ > /dev/net

  30. d-fens

    hi,

    i have problems with validation for dd.mm.yyyy: somehow everything is accepted, how must this be done?

    ‘birthday’ => array(
    ‘rule’ => array(‘rule’ => array(‘date’, array(‘dmy’))),
    ‘message’ => ‘Please supply a valid date.’
    ),

  31. dipali

    how to use this behaviour in my list page to make modified,created date dispalyed in dd/mm/yy format? what should i change additionally, i’ve made behaviour, config file chenges ….

  32. Nik Chankov Post author

    dipali, this behaviour is created to serve the data in forms, so, let’s say you want to allow users to fill some data fields in a form.

    If you want to display formated date, just use Time Helper and especially it’s format function. πŸ™‚

  33. Pingback: CakePHP - PHP Framework, ????? ? ????? ?? ???

  34. Daniel

    I have enjoyed this date behaviour but recently encountered a problem with it.

    I was getting the following error.

    Fatal error: Class ‘OrganisationLegalInformation ‘ not found in /websites/sitename/app/models/behaviors/date_formatter.php on line 159

    I wanted to post the solution in case someone else encounters the same problem.

    The site worked perfectly withought the behavior and at first I could not figure out what the problem was.

    I found that the class name had an extra space that would not allow it to be called.

    If you modify the code

    157 if($key != ‘Parent’){
    158 App::import(‘Model’, $key);
    159 $model_on_the_fly = new $key();

    to

    157 if($key != ‘Parent’){
    158 $key = trim($key);
    159 App::import(‘Model’, $key);

    It solves the problem. I hope this helps some people out their that may encounter the same issue.

    If their is another way please let me know.

  35. Jose Luis

    Hi, I’m trying to set up your behaviour, and I’m getting this error:

    Fatal error: Class ‘Otherdemandado’ not found in /home/joseluisgonl/web/juridico/models/behaviors/date_formatter.php on line 130

    I see it is about loading on the fly models, and specially a HABTM that is not called the same as its class.

    Thanks in advance for your help! great behaviour

Leave a Reply

Your email address will not be published. Required fields are marked *