Problema trabalhando com datas

All,

Estou com um problema na formatação de datas. Já li inumeras vezes as definições da classe CDateFormatter mas acho que ela não se aplica ao meu caso.

O meu cenário é o seguinte, tenho alguns teares e preciso registrar suas paradas.

Ai devo armazenar a data e a hora do inicio da parada e a data e a hora do fim.

Pra facilitar a vida dos usuarios estou utilizando um CMaskedTextField da seguinte maneira.

$masked = new CMaskedTextField();


$masked->mask = '99/99/9999 99:99';


$masked->model = $model;


$masked->attribute = 'paradaInicio';


$masked->run(); 

mas quando eu chego no model ele não reconhece este valor como uma data entao eu tratei esse valor para que se transformasse em data no beforeSave do model.

até ai tudo bem.

Mas no caso de existir um erro na minha data ele da um erro

htmlspecialchars() expects parameter 1 to be string, object given

acho que ele exibe isso porque o CMaskedTextField espera uma string e o tipo que está retornando é um objeto DateTime.

Queria que alguem me desse uma luz de como resolver isso.

Um solução pra isso Josafá seria você guardar o timestamp e a formatação da data você escolheria na hora de recuperar, e o legal é que você não precisa validar apenas gravar o timestamp

:)

Depois pode formatar a data com desejar

http://www.yiiframew…time-formatting

não da pra fazer com time stamp. Como eu disse ali em cima a data e a hora são digitadas pelos usuários.

To com o mesmo problema aqui, com uma pequena diferença, não exibe nenhum erro e a data gravada é = 0.

Aparentemente ele interpreta a data no formato do MySQL(yyyy-mm-dd), mas descobri que, para visualizar corretamente, vc pode usar:




  Yii::app()->dateFormatter->formatDateTime



Neste caso, vc formata a data para o modo corrente da sua aplicação(A formatação da data depende da língua configurada no seu main.php).

Atualmente no controller eu to fazendo a operação inversa, mas, não achei nada no Yii que faça isso automaticamente. Provisóriamente estou fazendo assim:




    public static function DateToDBDate($date){

    	$ar = explode('/', $date);

      return $ar[2].'-'.$ar[1].'-'.$ar[0]; 

    }



Quanto a validação de datas, ví que tem um validador, mas não consegui usar ele :(

Vê se ajuda em alguma coisa.

Daniel

eu estou fazendo algo paracido com isso, porém estou armazenando no banco agora valores inteiros com o Unix Time.

Apesar de exister o dateFormatter, achei ele não muito intuitivo. Espero que esse suporte seja melhorado nas próximas versoes do Yii.

Pra validar eu estou usando a função checkdate do PHP.

Para gravar

quebre a data-hora com um explode (ou com pattern, se desejar), e obtenha um time com a função mktime. Referencia: http://www.w3schools.com/php/func_date_mktime.asp

Para exibir as datas

Utilize Yii:app()->dateFormatter

Para utilizar a validação de data você pode utilizar o CTypeValidator, assim:


array('data', 'type', 'type'=>'date', 'dateFormat'=>'dd/MM/yyyy'),

Nesse caso, ele vai validar no formato que utilizamos no Brasil.

Para trabalhar com CMaskedTextField aqui eu faço o seguinte:

No afterValidate eu converto do formato dd/MM/yyyy para yyyy-MM-dd, da seguinte maneira:


date('Y-m-d', CDateTimeParser::parse($this->data, 'dd/MM/yyyy'));

Prefiro o CDateTimeParser do que explode + mktime para deixar o código mais legível.

No afterFind eu faço o processo inverso. Assim, todo registro recuperado do banco já vem no formato que eu trabalho:


date('d/m/Y', CDateTimeParser::parse($this->data, 'yyyy-MM-dd'));

Acho que assim o código fica bem legível e bem automatizado.

Consegui resolver o problema do validador:

vc pode usar o validador do próprio Yii desta forma:




  public function rules(){

    return array(

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

    );

  }



este codigo é dentro do seu Model. Repare que, quando a data chega no model, ela já está invertida, então é necessário passar a máscara invertida também.

ficou razoável, ainda não está perfeito, mas já ta ajudando.

Uma coisa que não entendo são essas máscaras, até entendo mas acho chato, já que o php trabalha com um formato e o Yii com outro, tipo no php ‘Y’ representa um ano com 4 dígitos já no Yii eu tenho que colocar ‘yyyy’.

Eu, particularmente, acho a leitura da máscara do Yii mais intuitiva, mas também não sou a favor dessas coisas despadronizadas. Na minha opnião o padrão do php deveria ser seguido já que é um framework php.

Apesar de ser legível creio que utilizar o explode e o mktime pode deixar o desempenho melhor, já que fazem parte do php e internamente são escritos em C.

Porém não sei até que ponto é válido optar por eles só por causa do desempenho.

Também não gosto de usar dois padrões diferentes. Talvez, esse problema seja elimidado utilizando o CDateTimeParser junto com o CDateFormatter. Ambos tem o mesmo padrão.

No geral, otimização precoce não é uma prática recomendável. Afinal, não faz sentido otimizar se nem sabemos onde estão os problemas de desempenho. A boa prática é desenvolver um código simples e legível, sem se preocupar com desempenho. Somente com o software pronto podemos efetuar medições de desempenho, utilizando-se, por exemplo, de profiling para identificar os "gargalos" na aplicação. É muito difícil que simples instruções como essas vão causar alguma diferença notável na aplicação.

Boa Josafa, não conhecia CDateTimeParser :)

é uma alternativa!

vou tentar me lembrar disso na próxima app que estiver fazendo, agora que jah comecei de uma maneira não é bom mudar.

dependendo do que estiver sendo feito com simples instruções podemos ganhar em performance e muito. mas isso é outra história e também não da pra se exigir performance extrema com um framework

acho que o crédito vai pra o davi_alexandre

Ah! certo, hehehe

Muito bem, só agora trabalhei pra valer com as datas e, graças às várias dicas úteis daqui, consegui fazer!!

Gostaria de quotar alguns posts e sugerir uma solução.

Muito obrigado, seu post ajudou bastante. Porém, sugiro duas mudanças: ao invés de fazer a conversao no afterValidate, faça-a no beforeSave.

Colocando no primeiro evento, se houver algum problema de validação, a data volta invertida ao form. Se vc fizer no beforeSave, a data somente é invertida se passar pela validacao.

Se fizer a inversão no beforeSave, vc pode utilizar a máscara como ‘dd/MM/yyyy’.

Utilizei suas dicas e dei mais uma incrementada, para utilizar as datas com i18N. No seu model, adicione o seguinte codigo:


protected function beforeSave(){

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

		return true;

	}

	

	protected function afterFind(){

		$this->mydate = Yii::app()->dateFormatter->formatDateTime(

					CDateTimeParser::parse($this->mydate, 'yyyy-MM-dd'),'medium',null);

		return true;

	}


        public function rules()

	{

		return array(			

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

		);

	}



Sim, o codigo da conversao está grande. Porém, penso em criar um componente para efetuar esta conversao ou entao estender o CActiveRecord para reconhecer os campos do tipo data para efetuar a conversao.

Tambem tenho uma duvida existencial: como diferenciar um date de um datetime? no trecho


CDateTimeParser::parse($this->mydate, 'yyyy-MM-dd'),'medium',null);

eu tive de assim fazer pq estava me traduzindo a hora quando não havia. Porém, e quando tiver a hora? :confused:

Salve galera.

Tava meio sem tempo, então só vi as msgs hoje :(

rickgrana

Vc tem razão. A solução do Davi é bem melhor que a minha. Quando respondi não tinha visto o post dele. (Na verdade, o post dele só fui ver hoje, heheh)

E, a questão do CDateTimeParser, eu tb não o conhecia. :)

A questão do i18N ficou muito boa. Vou implementar algo parecido. :)

Na verdade no afterFind não acontece esse problema, afinal a inversão é feita depois da validação. Basta apenas checar a existência de erros com o método hasErrors();

Eu usei o afterFind justamente por isso, para poder fazer a validação com essa máscara ;D

Muito legal a idéia de i18n!

Estranho, aqui o CDateTimeParser não funciona se não informar a formato certo da data. Por exemplo




$data = '1986-02-24 16:15:22';

CDateTimeParser::parse($data, 'yyyy-MM-dd');



O código acima não efetua a conversão porque o formato está errado. Por consequência, o formatDateTime também não funciona. Então o "X" da questão aqui é como passar o formato certo para o parse.

Talvez tentando pegar o tipo de dado da coluna, utilizando CDbColummSchema e setando a mascara adequada seja uma solução, no caso de um componente. Para aplicações mais simples, acho que não há problema em deixar o formato fixo.

Mas falei que dava problema com afterValidate, nao com o afterFind, esse ta perfeito. O problema do afterValidate é que ele é executado depois da validacao independentemente se houve erro ou nao. Por isso, prefiro o beforeSave, aonde já nao preciso checar hasErrors()

Realmente nao funciona. O que me acontecia era o contrario: minha datanao tinha hora e a hora era adicionada quando convertia (tava me referindo ao resultado do formatDateTime, nao ao parse)

Mas A questao do date/datetime ja resolvi (na mesma linha de pensanmento que a sua).

Postei em http://www.yiiframework.com/forum/index.php?/topic/3649-dealing-with-i18n-date-formats/page__pid__19669__st__0&#entry19669

e reproduzo aqui:


protected function beforeSave(){

        foreach($this->metadata->tableSchema->columns as $columnName => $column){

                if ($column->dbType == 'date'){

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

                }elseif ($column->dbType == 'datetime'){

                        $this->$columnName = date('Y-m-d H:i:s', CDateTimeParser::parse($this->$columnName, Yii::app()->locale->dateFormat));

                }

        

        }

        

        return true;

}

        


protected function afterFind(){

                                                

        foreach($this->metadata->tableSchema->columns as $columnName => $column){

                        

                if (!strlen($this->$columnName)) continue;

                        

                if ($column->dbType == 'date'){                         

                        $this->$columnName = Yii::app()->dateFormatter->formatDateTime(

                                        CDateTimeParser::parse($this->$columnName, 'yyyy-MM-dd'),'medium',null);

                        }elseif ($column->dbType == 'datetime'){

                                $this->$columnName = Yii::app()->dateFormatter->formatDateTime(

                                        CDateTimeParser::parse($this->$columnName, 'yyyy-MM-dd hh:mm:ss'));

                        }

        }

        return true;

}

Neste codigo, o ActiveRecord já vai atrás de todas as colunas date/datetime e efetua as devidas conversoes.

Falha minha ^^ Eu quis dizer afterValidate mesmo. Mas realmente, o beforeSave é um lugar melhor mesmo.

Ficou bem legal a forma como você fez! Agora é só colocar em um componente e utilizar nos modelos onde for necessário!

Ah, uma dica, você pode trocar o $this->metadata->tableSchema por $this->tableSchema.