seg 16 out 2006
ATUALIZAÇÃO: O método sugerido aqui não lê a configuração de timezone do servidor (e, portanto, exige atualização a cada ano) . Se o seu servidor é Linux ou assemelhado, sugiro usar o timefix.
Se você desenvolve aplicações Java para rodar em servidores, já deve ter se deparado com este problema: a máquina virtual nunca “acerta” o horário de verão, mesmo que você tenha configurado corretamente o timezone do sistema operacional do servidor para acertar o relógio.Este artigo aponta algumas soluções para o problema – incluindo uma que não exige recompilação de código e não se restringe à JVM da Sun.
Por que isso acontece?
Dois dos meus vilões favoritos são os culpados deste quiproquó: o governo brasileiro e a Sun.
O governo muda a regra do horário a cada ano – em 2006, a justificativa foram as urnas eletrônicas, e a questão é historicamente complicada no nosso país. Como dizia um administrador de sistemas que conheci, “no mundo todo o horário de verão é uma fórmula; no Brasil é uma lei”.
A Sun, por outro lado, deciciu que a máquina virtual Java deve gerenciar por si só todo o esquema de timezones, essencialmente ignorando o sistema hospedeiro (que se limita a informar o lugar onde o servidor se encontra e o horário UTC) – uma idéia razoável no passado, mas que desconsidera o fato de que Windows, Linux e tantos outros S.O.s modernos já resolvem este problema.
Como eu resolvo?
A solução comummente adotada é alterar o código para criar um timezone com as novas regras, e setá-lo como default. Por exemplo:
import java.util.SimpleTimeZone; import java.util.TimeZone; ... TimeZone.setDefault( new SimpleTimeZone( TimeZone.getDefault().getRawOffset(), "America/Sao_Paulo", Calendar.NOVEMBER, 05, 0, 3600000*1+60000*0, // 01h00 Calendar.FEBRUARY, 25, 0, 3600000*2+60000*0, // 02h00 3600000));
Neste caso, ele está ajustando o horário de verão para iniciar em 05/Nov às 01h00 e terminar em 25/Fev às 02h00 (é preferível usar estes horários para evitar que a mudança de data bagunce outros processos, mas você pode começar/terminar à meia-noite, se preferir).
O problema é que você tem que chamar o código na incialização da aplicação. Se ela for uma aplicação stand-alone até que é fácil – no entanto, se ela residir em um web container (Tomcat, WebSphere, etc.) fica mais difícil. Fora que você tem que recompilar todas as aplicações a cada ano, o que nem sempre é desejável. Mas é um caminho.
Outra solução: mexer na máquina virtual
O Vitor Buitoni dá uma solução coerente para o problema: se a máquina virtual decide agir como um sistema operacional, vamos tratá-la como tal, e trocar a configuração de timezone nela também (brinde: um jeito bem esperto de fazer o Tomcat/WebSphere/qualquer web container executar a atualização sem precisar reiniciar).
Requer uma certa “pedalada”, pois o utilitário necessário para fazer a mudança (JavaZic) não é público, ele usa uma manobra para obter seu fonte e compilar. Além disso, ela se restringe à JVM da Sun. E também há quem alegue que a seção “Java Technology Restrictions” da licença da Sun para o runtime da máquina virtual se aplique neste caso (o texto da versão 5.0 é menos restritivo, mas eu não sou advogado, então não me arrisco aqui).
Se nada disso te atrapalha, essa é a solução mais “limpinha”, sob o ponto de vista técnico, e eu recomendo.
Um caminho intermediário
Num antigo trabalho onde não era viável usar a solução do Buitoni nem alterar todas as aplicações, lancei mão de um expediente, que decidi publicar neste artigo: a criação de uma classe que faça o ajuste do timezone da JVM sem que tenhamos que alterar a aplicação a cada ano.
O código-fonte segue abaixo. Uma vez compilada e colocada no hv.jar (você pode usar o script Ant para fazer isto), você pode alterar o script de incialização do seu servidor para acessá-la. Suponhamos que, neste script, você tenha a chamada:
java -cp jar1.jar;jar2.jar;...;jar999.jar ClassePrincipal arg1 arg2 arg3...
Basta adicionar a classe no classpath, e colocá-la antes da classe principal, i.e.:
java -cp hv.jar;jar1.jar;jar2.jar;...;jar999.jar br.inf.chester.hv.HorarioDeVerao ClassePrincipal arg1 arg2 arg3...
Com isso, a máquina virtual irá chamar a classe para fazer o ajuste, e essa carregará o programa original, com os parâmetros originais.
O mais importante: no ano seguinte, bastará gerar a nova versão do hv.jar, substitui-la no servidor e reiniciar. Sem maiores complicações. Se preferir, você pode usar a classe apenas para centralizar os ajustes, chamando o método ajustaTimeZone() de dentro do seu código.
Melhorias
Seria bacana ter uma opção de hot-deployment, como na solução do Buitoni. Além disso, a classe poderia pegar os parâmetros do s.o. hospedeiro (dispensando a substituição anual, ao custo de não ser mais tão multiplataforma assim). Um dia desses eu faço essa mudança, mas quem quiser fica livre pra tentar.
Listagem 1: HorarioDeVerao.java (classe que ajusta o Horário de Verão)
package br.inf.chester.hv;
/*
* Copyright © 2006 Carlos Duarte do Nascimento (Chester)
* cd@pobox.com
*
* Este programa é um software livre; você pode redistribui-lo e/ou
* modifica-lo dentro dos termos da Licença Pública Geral GNU como
* publicada pela Fundação do Software Livre (FSF); na versão 2 da
* Licença, ou (na sua opnião) qualquer versão.
*
* Este programa é distribuido na esperança que possa ser util,
* mas SEM NENHUMA GARANTIA; sem uma garantia implicita de ADEQUAÇÂO
* a qualquer MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a Licença
* Pública Geral GNU para maiores detalhes.
*
* Você deve ter recebido uma cópia da Licença Pública Geral GNU
* junto com este programa, se não, escreva para a Fundação do Software
* Livre(FSF) Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
/**
* Permite que as aplicações Java ajustem o timezone
* da JVM para o horário de verão do Brasil, de duas formas:
* <p>
* - Chamando o método <code>ajustaTimeZone()</code><br>
* - Trocando a chamada da jvm, passando esta classe (que por sua
* vez irá chamar a classe original), vide método <code>main</code> abaixo.
* @author chester
*/
public class HorarioDeVerao {
/**
* Timezone para o horário de verão de 2006
* Inicio: 05/Nov; Fim: 25/Fev;
* Hora início: 01h00; Hora fim: 02h00
*/
public static TimeZone tz =
new SimpleTimeZone(
TimeZone.getDefault().getRawOffset(),
"America/Sao_Paulo",
Calendar.NOVEMBER,
05,
0,
3600000*1+60000*0,
Calendar.FEBRUARY,
25,
0,
3600000*2+60000*0,
3600000);
/**
* Seta o timezone da máquina virtual Java para o nosso
* timezone customizado
*/
public static void ajustaTimeZone() {
TimeZone.setDefault(tz);
}
/**
* Ajusta o timezone e chama o método <code>main</code> de uma
* classe.
* <p>
* A classe é determinada pelo primeiro parâmetro.
* Na prática, este método permite que se substitua:
* <p>
* <code>java minhaClasse p1 p2 p3 </code>
* <p>por<p>
* <code>java HorarioDeVerao minhaClasse p1 p2 p3</code>
* <p>
* e a classe será executada, só que sem problemas de horário
* @param args Argumentos que serão passados para a classe
*/
public static void main(String args[]) throws Throwable {
// Nome da classe cujo método main() queremos chamar
String nomeClasse = args[0];
// Vamos encontrar o método main
Method metodoMain;
// Cria um array cujo único elemento é a classe de um array
// de strings (já que main() recebe apenas um array de strings)
Class[] tiposArgs = new Class[1];
tiposArgs[0] = String[].class;
// Cria um array de argumentos a passar para o método main
// cujo único elemento são os argumentos que recebemos
// (tirando o primeiro, que é o próprio nome da classe)
Object[] listaArgs = new Object[1];
listaArgs[0] = removeElemento(args, 0);
// Acerta o timezone
ajustaTimeZone();
// Recupera a classe que ia ser executada originalmente
// e o seu método main
metodoMain =
Class.forName(nomeClasse).getDeclaredMethod("main", tiposArgs);
// Executa o método main, passando os argumentos que recebemos
try {
metodoMain.invoke(null, listaArgs);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
/**
* Remove um elemento de um array de strings
* @param a Array cujo elemento se quer remover
* @param pos Posição do elemento a remover
* @return Array com o elemento removido
*/
private static String[] removeElemento(String[] a, int pos) {
if (pos < 0 || pos >= a.length)
return a;
String[] aa = new String[a.length - 1];
if (pos > 0)
System.arraycopy(a, 0, aa, 0, pos);
if (pos < a.length - 1)
System.arraycopy(a, pos + 1, aa, pos, aa.length - pos);
return aa;
}
}
Listagem 2: build.xml (exemplo de script para gerar o hv.jar)
<project name="hv" default="gera_tudo" basedir=".">
<property name="SOURCE" value="src" /> <!-- onde ficam os fontes (.java) -->
<property name="BUILD" value="temp" /> <!-- onde sao gerados os .class -->
<path id="classpath">
<pathelement path="${java.class.path}" />
</path>
<target name="gera_tudo" description="Compila todos os fontes e gera o jar">
<delete dir="${BUILD}" />
<mkdir dir="${BUILD}" />
<javac srcdir="${SOURCE}" destdir="${BUILD}/" target="1.3" source="1.3">
<classpath refid="classpath"/>
</javac>
<jar jarfile="hv.jar" basedir="${BUILD}">
</jar>
<delete dir="${BUILD}" />
</target>
</project>
outubro 17th, 2006 at 12:28 pm
Conheço isso de algum lugar…. :P
Graaande Chester!
Abraço!
fevereiro 15th, 2007 at 11:14 pm
ESTE HORARIO DE VERAO É UMA PORCARIA FALAO QUE PARA ECONOMIZAR NAS CONTA DE LUZ
DEPOIS O GOVERNO ANUNCIA UM NOVO AUMENTO, ISTO É UMA PALHAÇADA DEVERIAO ACABAR COM ESTA DROGA QUE SO NOS TRAZ DESCONFORTO E CANÇASSO,POIS COM CERTEZA QUE CRIOU
ESTA DROGA NAO ACORDA CEDO DEVE SER UM EMGRAVATADO IDIOTA
julho 13th, 2007 at 5:46 pm
Excelente seu artigo, mas a ultima opção, apesar de ser a melhor, é muito trabalhosa!
Que tal implementar o SimpleTimeZone a partir de parâmetros armazenados em um arquivo de properties, por exemplo.
Dessa forma não é necessário recompilar o código!
Outra sacadinha simples, pra quem quer executar o ajuste todas as vezes que o contâiner for inicializado, basta implementr o método SimpleTimeZone, em uma classe que implemente ServletContextListener dentro do método
public void contextInitialized(ServletContextEvent sce).
Abraços e parabéns pelo conteudo!
julho 13th, 2007 at 6:10 pm
É verdade: um arquivo de parâmetros evitaria o ciclo de recompilação (e, com sorte, a aplicação já tem algum mesmo).
A solução apontada no “update” no início do artigo vai além: ela pega os dados de início e fim do horário de verão do próprio servidor (os quais costumam ser atualizados, manual ou automaticamente).
A idéia de usar o ciclo de inicialização é legal – se estivermos usando um web container, o que nem sempre é o caso. Outra idéia que me deram outro dia foi usar o Spring para incializar (novamente, tem uma dependência – a aplicação já estar usando Spring).
Enfim, são bastante idéias interessantes, e creio que conforme o contexto, cada uma delas possa ser apropriadamente usada.
Obrigado pela contribuição!