Usar Locale para formatear fecha automáticamente

Hola,

ya tengo acabada mi aplicación (usando Yii 1.1.4) y actualmente las fechas y los números con decimales los trato en formato inglés, es decir, "yyyy-MM-dd" para fechas y "." para separadores de decimales.

En el modelo tengo un campo fecha definido en las reglas de esta forma:




public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('id_asociado, id_estado, fecha', 'required'),

			array('id_asociado, id_estado', 'length', 'max'=>10),

                        array('fecha', 'type', 'type'=>'date', 'dateFormat'=>'dd-MM-yyyy'),

			array('notas', 'safe'),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id_historial, id_asociado, id_estado, fecha, notas, timestamp', 'safe', 'on'=>'search'),

		);

	}



La cuestión es que he estado leyendo el API para cambiar la localización a ES esto de forma automática y no he encontrado la solución. He probado lo siguiente:

1.- Añadir el locale en el main.php del config.




'locale'=>array(

     'id'=>'es_ES',

     'dateFormat'=>'dd-MM-yyyy',

     'dateFormatter'=>'dd-MM-yyyy',

),



2.- También he añadido en el modelo lo siguiente según he visto en el Issue 1747 (http://code.google.com/p/yii/issues/detail?id=1747):




public function getFecha()

        {

           //return date('Y-m-d', CDateTimeParser::parse($this->mydate, Yii::app()->locale->dateFormat));

           return date('d-m-Y', CDateTimeParser::parse($this->fecha, Yii::app()->locale->dateFormat));

           //return Yii::app()->locale->dateFormatter->formatDateTime($this->fecha);

        }


        public function setFecha($value)

        {

           $this->mydate = Yii::app()->dateFormatter->formatDateTime(CDateTimeParser::parse($value, 'yyyy-MM-dd'),'medium',null);

            //$this->fecha = Yii::app()->dateFormatter->formatDateTime(CDateTimeParser::parse($value, 'dd-MM-yyyy'),'medium',null);

        }



La verdad que no se por donde seguir o si debo cambiar algo más. ¿Se puede hacer un cambio de localización, como el que estoy haciendo yo, de EN a ES tan fácilmente?

Saludos y gracias por adelantado.

No tienes que hacer nada de eso… tan solo has de cambiar tu lenguaje (‘language’=>‘es_es’)

El objeto CLocale no se puede editar directamente (si miras el API veras que tiene read only marcado). Las configuraciones, tanto de lenguaje como de fechas, etc estan en Yii/i18n/data/tulenguage_turegion.php ---- es_es.php.

Tambien puedes cambiar esa configuración haciendo tu propia configuracon locale y luego especificando donde se encuentra con localeDataPath (http://www.yiiframework.com/doc/api/1.1/CApplication#localeDataPath)

Si deseas utilizar un cambio de fecha, tan solo debes usar el CDateFormatter una vez especificado el lenguage a utilizar. Espero haberte ayudado.

Recursos

http://www.yiiframework.com/doc/guide/1.1/en/topics.i18n#locale-and-language

Muchas gracias.

Lo pruebo y te digo algo a ver que tal.

Saludos.

Estaría bien que alguien, una vez hayas probado y encontrado la solución a ese problema, escribiera un buen wiki con la respuesta :)

Hola,

Sobre este tema no se como hacer para que en los formularios me pinte una fecha de un atributo de un modelo con la localización.

Es decir, siguiendo estos pasos funciona la localización perfectamente en todos los sitios donde se tenga una variable "suelta" (no ligada a un modelo en un formulario):

1.- Añadir el locale en el main.php del config




'language'=>'es_es',



2.- Usar la clase CDateFormatter en una vista o en un controlador para darle el formato a la variable. En mi caso estoy rellenando un objeto Json:




foreach($asociados as $n=>$asociado) {

            foreach($asociado->historial_estados as $n2=>$historial_estados) {

                $responce->rows[$i]['id']=CHtml::encode($i+1);

                $responce->rows[$i]['cell']=array(

                    CHtml::encode($asociado->id_asociado),

                    CHtml::encode($asociado->nombre),

                    CHtml::encode($asociado->apellidos),

                    CHtml::encode($asociado->firma),

                    //CHtml::encode($historial_estados->fecha)

CHtml::encode(Yii::app()->locale->dateFormatter->formatDateTime($historial_estados->fecha,'long',null))

                );

                $i++;

            }

        }


        echo json_encode($responce);



De esta manera la variable $historial_estados->fecha en la vista se muestra así:




Nombre	Apellidos	Firma	Fecha

======================================================

Prueba2 Prueba2         Firma2  3 de enero de 2011

PRUEBA	PRUEBA	        FIRMA1  9 de noviembre de 2010



Para ver el formateo de la fecha usar el API de la clase CDateFormatter.

Como he dicho antes esto es para una variable que no vaya en un formulario y dependa de un modelo.

La cuestión está en formatear la variable de un modelo en un formulario:

Modelo:

=======

Ya he añadido en las reglas el formato según el locale y si intento guardar la fecha en otro formato me salta la validación diciéndome que no está bien el formato:




	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('id_asociado, id_estado, fecha', 'required'),

			array('id_asociado, id_estado', 'length', 'max'=>10),

                        array('fecha', 'type', 'type'=>'date' ,'dateFormat'=>Yii::app()->locale->dateFormat),

			array('notas', 'safe'),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id_historial, id_asociado, id_estado, fecha, notas, timestamp', 'safe', 'on'=>'search'),

		);

	}



Vista/Formulario:

=================

Ahora bien, en la vista al seleccionar un registro y rellenar el formulario según el modelo, el campo fecha sigue sin formatearlo según el locale, es normal porque no he indicado en el formulario nada para que lo haga.




	<div class="row">

                <div style="min-width: 150px; float: left">

		<?php echo $form->labelEx($model,'fecha'); ?> (AAAA-MM-DD)

                </div>

		<?php

                echo $form->textField($model,'fecha');

                ?>

                

		<?php echo $form->error($model,'fecha'); ?>

	</div>



Esto es lo que no consigo realizar, poner en el $form->textField($model,‘fecha’); la variable fecha del modelo ya formateada según el locale.

¿Alguna sugerencia?

Saludos.

[font=arial, sans-serif][size=6]yo uso behaviors en el modelo, pero mis sistemas son bien argentos[/size][/font]

[font=arial, sans-serif][size=6]ningun multilenguajes[/size][/font][color=#888888][font=arial, sans-serif][size=2][color=#1111CC][/color][/size][/font][/color]

Varios::dateconvert lo que hace es manejo de strings

tenes que armarlo segun la base de datos que uses (mysql, oracle, etc)

saludos

Horacio

mi codigo (tambien lo uso para pasar a mayusculas algunas cosas)





public function afterFind(){


    parent::afterFind();


    $this->fecha_ini = Varios::dateconvert($this->fecha_ini,2);

    $this->fecha_fin = Varios::dateconvert($this->fecha_fin,2);

    $this->fecha = Varios::dateconvert($this->fecha,2);


    

	}




public function beforeSave() {


    $this->fecha_ini = Varios::dateconvert($this->fecha_ini,1);

    $this->fecha_fin = Varios::dateconvert($this->fecha_fin,1);

    $this->fecha = Varios::dateconvert($this->fecha,1);

    

    $this->ciudad_institucion=strtoupper($this->ciudad_institucion);

    // no sacar return , lo usa CActiveRecord.update

    return parent::beforeSave();


}




mira esta extensión, nunca la use, pero tiene buenos comentarios

http://www.yiiframework.com/extension/i18n-datetime-behavior

La estuve viendo y la cuestión es que solamente vale para unidades de tiempo, pero para números (separador de decimales y de miles) no vale…

Voy a ver si termino de investigar y hay algo que no necesite componentes externos, sino que use el propio core del Yii.

Gracias.

Horacio,

para salir del paso he usado Behaviours, en concreto el beforeSave.

public function beforeSave()

    {


        &#036;this-&gt;fecha = Utilidades::spanish2mysql(&#036;this-&gt;fecha);





        return parent::beforeSave();


    }

Me he creado una clase Utilidades con varias funciones estáticas y así funciona. Pero claro, no resuelve el tema de la localización.

Saludos.

Antonio, Horacio,

a ver que pensáis sobre todo lo recopilado hasta ahora.

El issue que había puesto si creo que sirve para el propósito. En este issue el estado se deja en wontfix (no necesita arreglar porque no es un bug) y comenta que depende mucho de la BD y que por tanto hay que usar otros mecanismos aparte de cambiar el core de Yii para no hacerlo dependiente de la BD.

Todo lo que hablamos se basa en un lenguaje origen y un lenguaje destino. El origen es, normalmente, el en_us y el destino puede ser variado y se configuran en el main.php en las variables "sourceLanguage" y "language".




return array(

	'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',

	'name'=>'Asociados',

        'defaultController'=>'site',

        'sourceLanguage'=>'en_us',

	'language'=>'es_es',

        'layout'=>'asociados',

...



Hay que destacar que el lenguaje origen debería coincidir con el lenguaje de los productos usados por la aplicación, en este caso hablamos de la base de datos. Queremos que en las vistas se presenten los datos en el formato del lenguaje destino pero se almacenen en la BD y se traten los datos en Yii en el lenguaje origen.

Sobre todos estos supuestos entonces tenemos un camino bidireccional, Source Language <==> Target Language (Locale).

Siguiendo lo que qiang.xue responde en el issue lo primero que habría que realizar son las funciones GET y SET del atributo del modelo que necesite un formato según la localización (normalmente son los numéricos y los referentes al tiempo, date & datetime).

Voy a poner un modelo ejemplo:




public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('id_asociado, id_estado, fecha', 'required'),

			array('id_asociado, id_estado', 'length', 'max'=>10),

                        array('fecha', 'type', 'type'=>'date' ,'dateFormat'=>Yii::app()->locale->dateFormat),

			array('notas', 'safe'),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id_historial, id_asociado, id_estado, fecha, notas, timestamp', 'safe', 'on'=>'search'),

		);

	}



Vemos que tengo el atributo fecha que es de tipo date y le aplico como regla de validación que siga el formato del lenguaje objetivo, es decir, el locale.

Ahora agregamos dos funciones nuevas en el modelo para el trato con el atributo fecha y así poder realizar las conversiones entre los lenguages origen y destino según la necesidad:




        public function getFecha()

        {

            return Yii::app()->locale->dateFormatter->formatDateTime($this->fecha,'medium',null);

        }


        public function setFecha($value)

        {

            //Aquí podemos usar cualquier función para pasar al formato aceptado por la BD

            //$this->fecha=date_format(new DateTime($value),'Y-m-d');

            $this->fecha=date_format(DateTime::createFromFormat('j/n/Y',$value),'Y-m-d');

        }



Ya tenemos la función getFecha que la podemos usar para recoger su valor original en el modelo pero con el formato definido en el locale que queremos.

También tenemos la funcion setFecha que la podemos usar para cambiar el valor del atributo en el modelo y dejarlo en el formato del lenguaje origen. En este caso como no tenemos funciones para el lenguaje origen tenemos que definir un formato de forma implicita, por eso indicamos ‘Y-m-d’, que es el formato aceptado por la BD, en mi caso MySQL.

Bien, ya tenemos armadas las funciones de los atributos que necesitan cambiar el formato según el locale. Ahora vamos a ver cuando usarlas.

Lenguaje origen -> Lenguaje destino (Locale):

=============================================

Normalmente las situaciones en las que queremos tener los atributos en el formato del lenguaje destino van a ser en las vistas, pero el trato de estos atributos y las operaciones internas que deseemos utilizar las haremos en el lenguaje origen.

Para ello antes de enviar a la vista el atributo o el modelo entero con el atributo debemos modificar su valor.

En la función del controlador haríamos las consultas necesarias y antes de pasarle el modelo u otro resultado (un objeto Json) debemos hacer la conversión.

Si es un modelo se podría cambiar el atributo directamente:




//Primero recogeríamos los datos del origen necesario $_POST, $_GET u otro sitio y rellenaríamos la variable $model

//Por ejemplo recoger de un POST

$model->attributes=$_POST['formName'];

$model->fecha=$model->getFecha();


//Y ahora enviaríamos el modelo a la vista

$this->render('nameView',array('model'=>$model));



En la vista veríamos el atributo con el formato correcto:




<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'formName',

	'enableAjaxValidation'=>false,

)); ?>


...

	<div class="row">

		<?php echo $form->labelEx($model,'fecha'); ?> (DD/MM/AAAA)

		<?php

                echo $form->textField($model,'fecha');

                ?>

                

		<?php echo $form->error($model,'fecha'); ?>

	</div>

...



Recordar que en las reglas de validación hemos puesto que valide según el formato del locale que tengamos configurado, lo digo para luego la parte del CRUD.

Ya hemos terminado con el paso de VER en el locale correcto los atributos.

Lenguaje destino (Locale) -> Lenguaje origen:

=============================================

Aquí suponemos que tenemos una vista, un formulario por ejemplo, y vamos a realizar algo con los datos introducidos en ella. El flujo sería, introducir datos o modificar datos en la vista, darle al botón que hace submit del formulario e iremos a la acción del controlador seleccionada y haremos algo con ellos, por ejemplos, se actualizarán los datos en la BD.

Aquí tenemos dos caminos.

1.- Uno de ellos es el que comenta Horacio de usar Behaviours (beforeSave, etc…) en el modelo y usar la funcion set del atributo necesario. En este ejemplo lo que hacemos es llamar en la acción al método save del modelo como siempre y luego en el modelo sobreescribimos la función beforeSave para que cambie el valor antes de realizar el save() del modelo.




        public function beforeSave()

        {

            $this->setFecha($this->fecha);


            return parent::beforeSave();

        }



2.- En el controlador antes de llamar a las funciones donde sea necesario tener el formato del origen (la base de datos) realizamos el cambio del valor que viene de la vista.




public function actionUpdateModel()

    {

        $model=new Model();

        $model->setScenario('update');


        if(isset($_POST['formName']))

        {


            $model=Model::model()->findByPk($_POST['formName']['id_field']);

            $model->attributes=$_POST['formName'];

            $model->setFecha($model->fecha);


            $model->save(true);

        }

    }



OR




public function actionUpdateModel()

    {

        $model=new Model();

        $model->setScenario('update');


        if(isset($_POST['formName']))

        {


            $model=Model::model()->findByPk($_POST['formName']['id_field']);

            $model->attribute1=$_POST['formName']['attribute1'];

            $model->attribute2=$_POST['formName']['attribute2'];

            $model->setFecha($model->fecha); //aquí se podría haber hecho otra función en el modelo que devuelva el valor en formateado en vez de cambiarlo en el modelo directamente, getFechaOrigen. $model->fecha=$model->getFechaOrigen();


            $model->save(true);

        }

    }



Todo esto lo acabo de poner sobre la marcha, si podéis probarlo estaría bien para afinar la solución.

Lo único que falta es que se implementen funciones/propiedades para el lenguaje origen y de esta manera poder definir el formato de origen y no tener que poner el formato de forma implicita en las funciones SET.

Espero que os sirva.

Saludos.

Hola!! muy buen resumen!! seguro vas a ayudar a varios miembros

te comento que algo así(en la accion update del controlador) intente alguna vez,

pero tuve problemas cuando el save(true) retorna false (cuando hay errores de algún tipo al validar)

genealmente el codigo es algo asi







public function actionUpdateModel()

    {

        $model=new Model();

        $model->setScenario('update');


        if(isset($_POST['formName']))

        {


            $model=Model::model()->findByPk($_POST['formName']['id_field']);

            $model->attributes=$_POST['formName'];

            $model->setFecha($model->fecha);


         	if ($model->save(true))

             	$this->redirect(array('view','id'=>$model->id));

        }


     	$this->render('update',array(

            'model'=>$model,));

    }



lo que provoca que al hacer el render mandes la fecha en el formato mm-dd-yyyy (por haber hecho el setFecha)

se podría arreglar poniendo un getFecha si el save da error

otra cosa que falta aclarar son las busquedas por fecha, hay que manejar la condicion "a mano"

yo tengo codigo del estilo




$condicion= 'date_FORMAT( t.fecha,\'%d/%m/%Y\' ) like :fecha_busqueda';

//t.fecha es el campo de la base de datos

//:fecha_busqueda es el criterio de busqueda ingresado por el usuario



saludos

Ahora estoy probándolo en mi código y tengo un problemilla en el setFecha.

No se si al llegarle al DateTime un valor del tipo ‘19/10/2010’ lo formatea bien a ‘2010-10-19’.

Fíjate que me había equivocado al pegar y el set realmente tendría que tener esta otra línea:

$this->fecha=date_format(new DateTime($value),‘Y-m-d’);

Si lo anterior no funciona bien al tener "/" entonces se podría usar esta otra línea:

$this->fecha=implode(’-’, array_reverse(explode(’/’, $value )));

Ya con esto se tiene la fecha en el formato correcto.

Otra cosa que me está dando error es que si pongo ‘10/3/2010’ me da error de validación, me dice que tiene que ser de tipo date. Si pongo ‘10/03/2010’ si funciona… he probado a poner en el formato primero:

$this->fecha=date_format(new DateTime($value),‘Y-n-j’);

pero nada, me sigue diciendo lo mismo. Será del locale es_es imagino y hay que rellenar con ceros.

Sobre las búsquedas, si traes la fecha de una vista me imagino que realizando el setPropiedad y la dejas con el formato de la BD antes de realizar la SELECT debería hacerlo todo bien, no?

O no lo he entendido bien :D

Y sobre lo otro, llevas toda la razón, si da un error al validar habría que volver el dato al valor del locale para presentarlo en la vista de forma correcta. Habría que realizar un getFecha si da errror el save() y ya está.

No lo había tenido en cuenta porque yo normalmente lo que hago es una llamada por AJAX a la acción usando JQuery y si me da error lo pinto en una capa de la vista. De esta manera no refresco vistas y no me cambian los valores del formulario y por tanto no realizo la conversión que comentas.

Saludos.

He cambiado el setFecha:




public function setFecha($value)

        {

            //Aquí podemos usar cualquier función para pasar al formato aceptado por la BD

            //$this->fecha=date_format(new DateTime($value),'Y-m-d');

            $this->fecha=date_format(DateTime::createFromFormat('j/n/Y',$value),'Y-m-d');

        }



Pero me sigue saltando la validación si introduzco un día o un mes con un dígito sin rellenar con cero.

Estoy seguro que es por la regla de validación, pero depende del locale, por lo que me imagino que tendrá que ver el fichero es_es.php




  'dateFormats' => 

  array (

    'full' => 'EEEE d \'de\' MMMM \'de\' y',

    'long' => 'd \'de\' MMMM \'de\' y',

    'medium' => 'dd/MM/yyyy',

    'short' => 'dd/MM/yy',

  ),



Y habría que añadir con ceros si el día o mes tiene un dígito nada más…

Saludos.