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.</p>
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.</p> 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, , 3600000*1+60000*, // 01h00 Calendar.FEBRUARY, 25, , 3600000*2+60000*, // 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:
<br />
java -cp jar1.jar;jar2.jar ClassePrincipal arg1 arg2 arg3...<br />
Basta adicionar a classe no classpath, e colocá-la antes da classe principal, i.e.:
<br />
java -cp <font color=red>hv.jar;</font>jar1.jar;jar2.jar <font color=red>br.inf.chester.hv.HorarioDeVerao</font> ClassePrincipal arg1 arg2 arg3...<br />
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, , 3600000 * 1 + 60000 * , Calendar.FEBRUARY, 25, , 3600000 * 2 + 60000 * , 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[]; // 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[] = 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[] = removeElemento(args, ); // 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 < || pos >= a.length) return a; String[] aa = new String[a.length - 1]; if (pos > ) System.arraycopy(a, , aa, , 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)
<?xml version="1.0" encoding="UTF-8"?> <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>
Comments
Juan Carlos
Conheço isso de algum lugar.... :P
Graaande Chester!
Abraço!
shirleno monteiro
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
PHN_DEVELOPER
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!
Chester
É 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!