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>