sexta-feira, 3 de abril de 2009

Ano Bissexto no PostgreSQL!

Às vezes o mais difícil de fazer são coisas realmente simples. Os exemplo de código abaixo são implementações de uma rotina que calcula se um ano é bissexto ou não.

Segundo a Wikipedia, as regras do ano bissexto são poucas, mas não são triviais:
* São bissextos todos os anos múltiplos de 400, p.ex: 1600, 2000, 2400, 2800
* Não são bissextos todos os múltiplos de 100 e não de 400, p.ex: 1700, 1800, 1900, 2100, 2200, 2300, 2500...
* São bissextos todos os múltiplos de 4 e não múltiplos de 100, p.ex: 1996, 2004, 2008, 2012, 2016...
* Não são bissextos todos os demais anos.

Com base no que é apresentado neste post, peço que você que me responda três perguntas:
- Os dois exemplos abaixo estão corretos?
- Qual dos dois apresentaria alguma vantagem em relação ao outro? Ou são ambos equivalentes?
- É possível melhorar as implementações? De que formas?

As duas funções criadas retornam 1 se ano for bissexto, 0 se não for e 99 se for anterior a 1582, para anos acima de 1582.

Exemplo 1:

CREATE OR REPLACE FUNCTION ano_bissexto (pAno integer) RETURNS integer AS $$
DECLARE
ret integer;
BEGIN
IF $1<1582 THEN RETURN 99; END IF;
ret :=0; --Inicialização
IF ($1%400=0) THEN
ret:=1; /*Bissexto*/
ELSE
IF ($1%4=0) AND ($1%100<>0) THEN
ret:=1; /*Bissexto*/
END IF;
END IF;
RETURN ret;
END;
$$ LANGUAGE plpgsql;

SELECT ano_bissexto(1600);

Exemplo 2:

CREATE OR REPLACE FUNCTION ano_bissexto_menor (pAno integer) RETURNS integer AS $$
DECLARE
ret integer;
BEGIN
IF $1<1582 THEN RETURN 99; END IF;
ret :=$1; --Inicialização
IF (ret%100=0) THEN
ret:=ret/100;
END IF;
IF ret%4=0 THEN
RETURN 1; --Bissexto
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql;

SELECT ano_bissexto_menor(1600);

4 comentários:

Osvaldo disse...

Considerando que a cada 400 anos apenas 97 são bissextos creio que seria mais eficiente testar primeiro os não bissextos (ano%4 <> 0) e depois as demais condições. Em média você faria menos operações.
Pergunta: Por que você não retorna um booleano?

Cláudio Leopoldino disse...

Osvaldo deu uma boa idéia. Testando o que não é bissexto, poderíamos ganhar tempo de processamento.

A função não retorna booleano por retornar três valores 1, 0 e 99 (não se aplica), mas poderia ser implementado com um SMALLINT ao invés de integer.

Gente, temos mais sugestões de melhoria?

Walter Cruz disse...

template1=> select date '2002-02-29';
ERRO: valor do campo date/time está fora do intervalo: "2002-02-29"

Funcionaria tentar o dia 29 de fevereiro do ano, e verificar o possível erro lançado?

Anônimo disse...

Eu nunca fui de responder a perguntas.... mas como eu sempre usei respostas da web, aqui vai uma ajuda para a comunidade.

Acredito que a melhor função seria:

CREATE OR REPLACE FUNCTION "public"."isAnoBissexto" (ano integer) RETURNS boolean AS
$body$
begin
return (( ano % 4 = 0 and ano % 100 != 0 ) or ( ano % 400 = 0 ) );
end;
$body$
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;

Atenciosamente,
Winston
Cuiabá-MT