Resolvendo problemas de intervalo de data com Records em Delphi
Record é um tipo de dado que lhe permite estruturar dados, a partir do Delphi 2006 Record se tornou quase uma classe, suportando:
- Construtores
- Sobreposição de operadores
- Declaração de métodos não-virtuais
- Métodos e propriedades estáticos
Podemos utilizar os Record de várias maneiras, no meu caso utilizo muito em parâmetros de métodos que devem representar chaves primárias (Primary Key), desta forma facilita a leitura e manutenção do código, é verdade que você não fica mudando as chaves primárias das tabelas do seu banco de dados o tempo todo, mas quando precisar o mudança no código será simples. Outro exemplo seria informar intervalo de dados.
Imagine que você precise gerar um conjunto de boletos de um determinado período, você iria declarar algo assim.
procedure GerarBoleto( DataInicial, DataFinal : TDateTime );
Geralmente temos problema com periodos, porque os bancos de dados armazenam data e hora no mesmo campo, desta forma temos que na data inicial estar certo que a hora está acertada para ’00:00:00′ e para a data final teremos que estar certos de ter como hora final ’23:59:59′, existem outros aspectos e artifícios dependendo do banco de dados para contornar esta situação, mas isso depende de cada banco.
Record é uma excelente solução para estes casos, para isso representamos os parâmetros do método GerarBoleto como um Record e ele irá fazer todo o trabalho para evitar os problema de data e hora, assim como facilitar a leitura, entendimento e manutenção do código.
O método passaria a ser declarado assim:
procedure GerarBoleto( periodo : TPeriodo );
A declaração do Record teria as propriedades DataInicial e DataFinal, estas por suas vez quando receberam valores terão os mesmo ajustados de acordo com os método Set.
Abaixo a declaração do Record e a seguir algumas explicações para o mesmo.
unit Perido; interface uses SysUtils, DateUtils; type TPeriodo = Record private FDataFinal: TDateTime; FDataInicial: TDateTime; procedure SetDataFinal(const Value: TDateTime); procedure SetDataInicial(const Value: TDateTime); public property DataInicial: TDateTime read FDataInicial write SetDataInicial; property DataFinal: TDateTime read FDataFinal write SetDataFinal; Constructor Create(Di, Df: TDateTime); procedure SetIntervaloAnual( Anoi, Anof : Integer ); end; implementation { TPeriodo } constructor TPeriodo.Create(Di, Df: TDateTime); begin DataInicial := Di; DataFinal := Df end; procedure TPeriodo.SetDataFinal(const Value: TDateTime); begin FDataFinal := EncodeDateTime(Yearof(Value), MonthOf(Value), Dayof(Value), 23, 59, 59, 1000); end; procedure TPeriodo.SetDataInicial(const Value: TDateTime); begin FDataInicial := EncodeDateTime(Yearof(Value), MonthOf(Value), Dayof(Value), 0, 0, 0, 0); end; procedure TPeriodo.SetIntervaloAnual(Anoi, Anof: Integer); begin DataInicial := EncodeDate(Anoi, 1, 1); DataFinal := EncodeDate(Anof, 12, 31); end; end.
- Toda e qualquer atribuição de valor para DataInicial e DataFinal serão ajustados de acordo com o horário inicial e final
- Foi criado um método adicional SetIntervaloAnual, onde você pode informar o ano inicial e final para o período e o método irá gerar os intervalos.
Abaixo uma das formas onde podemos utilizar este Record, informando período inicial e final;
var periodo : TPeriodo; begin periodo.DataInicial := EncodeDate( 2009, 1, 1); periodo.DataFinal := Now; GerarBoleto(periodo);
Outra forma é passar o período utilizando o construtor do Record
var periodo : TPeriodo; begin periodo.Create(EncodeDate( 2009, 1, 1), Now); GerarBoleto(periodo);
Outra forma seria utilizar o método SetIntervaloAnual, onde você apenas especifica o período anual.
var periodo : TPeriodo; begin periodo.SetIntervaloAnual(2008, 2009); GerarBoleto(periodo);
Em todos os casos, os valores horários foram acertados.
Mas este record pode fazer muito mais por nós, porque não gerar a declaração where do SQL para as datas de acordo com os valores? Dois simples métodos pode resolver todos os problemas.
Field, é o nome do campo que representa a data em sua tabela.
function TPeriodo.DataDBFormat(const Value: TDateTime): String; begin Result := FormatDateTime('mm/dd/yyyy hh:mm:ss', Value); end; function TPeriodo.GenerateSQL(Field: String): String; Const sql: String = '%s between ''%s'' and ''%s'' '; begin Result := Format(sql, [FieldI, DataDBFormat(DataInicial), DataDBFormat(DataFinal)]); end;
Na prática usamos desta forma:
var periodo : TPeriodo; begin periodo.SetIntervaloAnual(2008, 2009); ShowMessage( periodo.GenerateSQL('DATA_BOLETO'));
O resultado no ShowMessage será: DATA_BOLETO between ’01/01/2008 00:00:00′ and ’12/31/2009 23:59:59′
Espero que este post seja útil e lhe ajude no seu dia-a-dia. Download código fonte
Até o próximo post.
Andreano, muito interesante esta aplicação de record! A maioria dos codigos que já vi, utilizam ainda a sintaxe anterior deixando os novos recursos do lado.
Bom dia ,
Boa solução, aprecio este tipo de disposição.
Mas existe um bug ….
Quando é atribuído o valor da datafinal o parâmetro milissegundos não pode ser igual a 1000.
Sendo assim com o código corrigido o parâmetro deve ser 999.
@Wagner,
No código fonte que foi disponibilizado está correto, faltou acertar aqui no post.
Obrigado!