martes, 23 de octubre de 2012

Fechas en Yii Framework usando CJuiDatePicker y CDateTimeParser.

CJuiDatePicker es un componente de Yii que viene de paquete y sirve para elegir fechas. Este componente usa a jQueryUI.datepicker.

1. Supongamos que tienes una tabla cuyo campo fecha debe ser establecido, leido, listado etc.  Ok, primero, por optimizacion no vamos a usar un campo VARCHAR(11) para almacenar una fecha porque no es práctico por muchas razones. Usaremos en campo BIGINT para almacenarla, lo cual es mas eficiente y útil.

2. La tabla en mysql sería como esta:
CREATE TABLE IF NOT EXISTS `prueba` (
  `idprueba` int(11) NOT NULL AUTO_INCREMENT,
  `fecha` bigint(20) NOT NULL,
  PRIMARY KEY (`idprueba`)
);

Fechas en Bigint versus CHAR   Si la fecha esta almacenada como un CHAR quiza para ti sea mas fácil...pero a la hora de usar vistas, comparaciones de fechas y ordenamientos será una pesadilla. Aparte del espacio que gasta.   Por otra parte una fecha en bigInt es una ventaja, porque permite llevar esa fecha de una forma a otra, la fecha en bigint queda almacenada como un numero secuencial, de ese modo la fecha de ayer sera menor que la de hoy por obligación matemática.

3. Ahora, con GII creas un Modelo llamado Prueba, que representa a la tabla "prueba".

4. De nuevo con GII, creas un CRUD para el modelo Prueba.
Hasta aqui el objetivo es que tengas una base de para hacer las pruebas a continuación.

5. Para recibir la fecha en el formulario del CRUD creado por GII, en este caso:
views.prueba._form.php,  quitaras el campo FECHA que GII te puso por defecto y en cambio insertarás el widget:


  1.  <?php
  2.   // widget
  3.   $this->widget('zii.widgets.jui.CJuiDatePicker', array(
  4.    'language'=>'es',
  5.    'model'=>$model,
  6.    'attribute'=>'fechaString',
  7.    'flat'=>false,
  8.    'options'=>array(
  9.     'firstDay'=>1,
  10.     'showOn'=>"both",
  11.     //'buttonImage'=>"images/calendar.gif",
  12.     'buttonImageOnly'=> true,
  13.     //'minDate'=>-31,
  14.     //'maxDate'=>0,
  15.     'constrainInput'=>true,
  16.     'currentText'=>'Now',
  17.     'dateFormat'=>'dd/mm/yy',
  18.    ),
  19.    'htmlOptions'=>array(
  20.    ),
  21.   ));
  22.  ?>


6. Nota que he puesto "model"=>$model y "attribute"=>"fechaString" y no "fecha" como estarías esperando, ya que el modelo "models/Prueba.php" tiene solo el campo fecha, el cual representa a un BIGINT, entonces, por qué "fechaString" ? bueno porque una cosa es el bigint que la base de datos va a almacenar, y otra el string que vamos a recibir de CJuiDatePicker, el cual maneja strings y no enteros.

7. En models/Prueba.php vas a crear un atributo llamado "fechaString", y haras algunos cambios en las reglas:


  1.  <?php
  2.  class Prueba extends CActiveRecord
  3.  {
  4.   public $fechaString;
  5.   public function rules()
  6.   {
  7.    return array(
  8.     array('fechaString','convertir_fecha'),  // <---AQUI
  9.     array('fecha, fechaString', 'required'),  // <--- AQUI
  10.     array('fechaString', 'length', 'max'=>20),  // <--- Y AQUI
  11.     array('idprueba, fecha', 'safe', 'on'=>'search'),
  12.    );
  13.   }
  14.  
  15.    public function convertir_fecha($attr, $params){
  16.    $this->fecha = CDateTimeParser::parse($this->fechaString,'dd/MM/yyyy');
  17.    if($this->fecha == null)
  18.     $this->addError('fecha','La fecha es requerida.');
  19.   }
  20.  }


Qué acabamos de hacer aquí:
  • Primero, creamos un atributo que no pertenecerá a la base de datos, llamado fechaString, el cual solo servirá para traernos del formulario (usando CJuiDatePicker) a un string con la fecha que le pedimos al usuario.
  • Luego, en rules() hemos indicado que la fechaString es requerido, y que además dispone de un convertidor, para que tras submit podamos pasar lo que hay en "fechaString" a "fecha", claro está validando que no se introdujo basura.  Para esto último en el metodo "convertir_fecha" se hace una conversión usando a CDateTimeParser, quien recibirá el texto de fechaString y lo convertirá.
  • CDateTimeParser necesita saber como es el formato de aquello que le estamos pasando, en la documentacion de CDateTimeParser hallarás los detalles, pero aqui basta con indicar que esperamos un string asi:  "dd/MM/yyyy".  

Si en CDateTimeParse esperamos un string con ese formato específico es lógico esperar que CJuiDatePicker entregue un string con ese formato, asi que por eso ves que mas atras en la configuración del widget de CJuiDatePicker ponemos:  'dateFormat'=>'dd/mm/yy'.  Nota que los formatos son distintos en sus letras, esto es algo que hay que cuidar. Lee bien la doc de cada componente.  Finalmente, si el widget de CJuiDatePicker pide ese formato tambien hay que considerar cual es el formato por defecto del componente 'format' (dateFormat) en config/main.php.  También debe coincidir. Se configura asi:
'format' => array(
'datetimeFormat'=>"d M, Y h:m:s a",
'dateFormat'=>'d/m/Y',   // <-----ESTE ES
),
EL FORMATO DE LA FECHA ES LA CLAVE, POR TANTO VERIFICA QUE:
  * CJuiDatePicker va a emitir un string en un formato indicado en el widget.
  * Prueba.php va a convertir ese string a un bigint usando CDateTimeParser, que tiene su propio formato.
  * Yii::app()->format->formatDate tiene su propio formato.

8. Si abres el form views/prueba/create.php, verás que éste presentara una cajita de texto para el campo "fechaString", cuando le haces clic aparece el CJuiDatePicker.  Cuando haces submity si todo esta bien se invocará a models/Prueba.php::convertir_fecha , esto debido a los rules(), quien pasará lo que hay en fechaString al atributo fecha, que será el que finalmente se guardará en la base de datos.

9. Si abres el form views/pruebas/admin.php verás que la fecha se muestra en el formato NUMERICO, así 1298928922, esto es porque la vista admin.php por defecto presenta al atributo fecha asi mismo como viene sin preprocesarlo.  Para que el CGridView de admin.php muestre la fecha en el formato correcto, se hace lo siguiente:


  • <?php $this->widget('zii.widgets.grid.CGridView', array(
  •  'id'=>'prueba-grid',
  •  'dataProvider'=>$model->search(),
  •  'filter'=>$model,
  •  'columns'=>array(
  •   'idprueba',
  •   array('name'=>'fecha','type'=>'date'),
  •   array(
  •    'class'=>'CButtonColumn',
  •   ),
  •  ),
  • )); ?>


  •   Si ves, puse 'type'=>'date'. Eso hace que CGridView invoque a: Yii::app()->format-  >formatDate(1298981222), lo cual formateará el valor ingresado (la fecha en bigint de la tabla "prueba") a una representación indicada por la configuracion hecha en config/main bajo:
    'format' => array(
    'datetimeFormat'=>"d M, Y h:m:s a",
    'dateFormat'=>'d/m/Y',   // <-----ESTE ES
    ),

    10. FINALMENTE, para actualizar el valor de un registro de la tabla prueba hay algo extra que hacer: Convertir la fecha desde un bigint a un string antes de mostarla en el formulario. Esto se hace en el controller, asi:


    1.  public function actionUpdate($id)
    2.  {
    3.   $model=$this->loadModel($id);
    4.  
    5. // AQUI...pasar a fechaString lo que tiene el bigint fecha usando:
    6.   $model->fechaString = Yii::app()->format->formatDate($model->fecha);
    7.  
    8.    // Uncomment the following line if AJAX validation is needed
    9.   // $this->performAjaxValidation($model);
    10.  
    11.    if(isset($_POST['Prueba']))
    12.   {
    13.    $model->attributes=$_POST['Prueba'];
    14.    if($model->save())
    15.     $this->redirect(array('view','id'=>$model->idprueba));
    16.   }
    17.  
    18.    $this->render('update',array(
    19.    'model'=>$model,
    20.   ));
    21.  }


    8 comentarios:

    1. Gracias... Me ha servido muchisimo...

      ResponderEliminar
    2. Amigo si quisiera trabajar con solo la hora, mas no la fecha que me recomiendas?

      ResponderEliminar
    3. Cristian:

      ?porque no utilizas un campo TIMESTAMP?

      ResponderEliminar
      Respuestas
      1. Yo también tengo esa duda... o un datetime, básicamente.

        Eliminar
      2. si puedes usarlo, el problema es que algunos motores usan un tipo y otros usaran otro distinto, para evitar eso me voy al principio: el tipo de dato fechahora es un numero enterlo largo, entonces bigint y se acaba el problema de la especificacion.

        Eliminar
    4. Buenas, soy nueva en yii, estoy trabajando con 2 CJuiDatePicker. uno para fecha de inicio y otro para la fecha de fin y no se como puedo establecer la fecha de inicio automaticamente como fecha_min de la fecha_fin.. Cualquier comentario seria de mucha ayuda.. Gracias :)

      ResponderEliminar