chester's blog

technology, travel, comics, books, math, web, software and random thoughts

timefix (ou: Acertando o Horário de Verão em Java II)

24 Feb 2007

Ano passado escrevi um texto sobre o problema do Java com o horário de verão, explicando as causas, apontando soluções comuns e sugerindo uma nova – cuja principal vantagem é dispensar alterações no código-fonte da aplicação afetada.

Sua maior desvantagem é que o arquivo com o código de correção tem que ser recompilado a cada mudança de regra do horário de verão (i.e., uma vez por ano, pelo menos) e re-copiado em cada servidor – o que fica complicado quando se cuida de dezenas ou centenas deles.

Em vista disso (e com a ajuda da classe ZoneInfo, de autoria de Stuart D. Gathman), criei o timefix – uma biblioteca que lê automaticamente o arquivo /etc/localtime do servidor, ajusta o timezone default e chama a aplicação final. Desta forma, basta reiniciá-la quando o arquivo for atualizado parq que as novas datas de início e fim do horário de verão entrem em vigor.

A página do projeto no Google Code hospeda o código-fonte e a biblioteca compilada (disponíveis sob a licença LGPL).

Download e Instalação

Para usar, basta baixar o timefix-1.0.jar (ou a versão mais atual, se houver) e fazer duas mudanças no script de inicialização do servidor (ex.: no catalina.sh do Tomcat, ou no /etc/init.d/minha_aplicacao):

  1. Adicionar o .jar no CLASSPATH. Ex.: export CLASSPATH=$CLASSPATH:timefix-1.0.jar
  2. Colocar o texto “tiimefix” antes do nome da classe original. Por exemplo, se o script original era: java com.xyz.ClassePrincipalDaMinhaApp param1 param2 param3 mude para:

    ` java timefix com.xyz.ClassePrincipalDaMinhaApp param1 param2 param3</li> </ol> Desenvolvedores de aplicações preocupados com a questão podem também efetuar a chamada ao método [fixTimeZone()`]6 ao inicializar a mesma (ou em intervalos regulares, dispensando o restart quando houver mudança nas regras). Mas este método torna a aplicação dependente do SO hospedeiro (pessoalmente, não recomendo isso).

    Sistemas operacionais suportados

    Esta solução só funciona em Linux e outros sistemas estilo Unix que usem o formato do banco de dados tz (e exige que o arquivo de timezone compilado – ou um link simbólico para ele – esteja no /etc/localtime). Testei em Linux (Ubuntu 6.06.1 / Kernel 2.6.15) e em Mac OS X (10.4.8).

    Administradores de servidores Windows podem tentar criar o diretório \etc\localtime no drive onde a aplicação roda e copiar um arquivo de timezone compilado para lá (ex.: copiado de uma máquina Linux ou da web). Não tentei fazer isso, mas teoricamente funciona – se alguém fizer funcionar na prática vale deixar um comentário.

Comments


krico

Cara, o Fabio Kung da Caelum vai participar da JSR-310.

Pedi pra ele olhar tuas manobras com timefix (espero que podia).

[s]

Krico



Chester

Legal... claro, a JSR-310 é um negócio *bem* mais sério que esse fix (embora ele seja usado em mais de um grande provedor de Terra Brasilis ;-) ). Eu tenho um certo otimismo com relação a isso - particularmente se sair algo tão amigável quanto o Joda Time.

Aliás, eu só não fui mais a fundo no Joda Time na prática pelo simples fato de que é quase impossível convencer todos os envolvidos em um projeto grande a trocar algo tão fundamental quanto o suporte a datas por uma solução não-oficial. Nesse sentido JSRs como esta vêm muito a calhar!


Fabio Kung

Legal, manobras anotadas!

Prover uma forma simples e fácil de atualizar as regras de timezones é um dos objetivos da jsr-310. A Joda Time já usa as regras do tz database, o que indica que muito provavelmente partiremos daí.

Na minha opinião a abordagem da Joda Time não é flexível o suficiente ainda. Trabalho à vista... ;)


Cássio

Olá, parabéns pelo trabalho desenvolvido.

Realmente aquela solução de recompilar a jvm era bastante complicada.. principalmente para quem trabalha com clientes que estão sempre dando um update em sua jvm.

Gostei desta segunda solução.

Mas, no momento ainda estou tendo problemas com o bendito horário de verão. Porque temos um produto que é distribuído a nível nacional, com diferentes timezones, sendo que alguns horários de verão não são definidos previamentes. No Estado da Bahia por exemplo, é uma questão política e pode ser implantado ou não. Em minha aplicação preciso garantir que o java do cliente tenha sempre exatamente o horário da máquina cliente. Independente se a opção de ajuste automático para horário de verão esta setado ou não.


Chester

Hmm... nesse caso, a "máquina cliente" é a máquina onde está rodando a JVM, ou a máquina que está acessando o sistema (ex.: via web)? No primeiro caso, me parece que aplicar o fix e acertar o /etc/timezone da máquina para não entrar no horário de verão resolve o problema (e evita problemas laterais com outras aplicações, ex.: crond, que podem afetar a sua).

No segundo caso o buraco é *bem* mais embaixo...


Cássio

É um terceiro caso...

Imagine que em uma rede local temos uma máquina servidora que se comunica com diversos clientes. gerenciando-os. pode gerenciar por exemplo o momento em que todas estas máquinas cliente serão desligadas. Imagine também que esta máquina servidora se realiza comunicação com um outro servidor [desta vez web] já que este agendamento também pode ser feito através da internet. Imagine ainda que a rede local é uma rede mista, quanto aos sistemas operacionais utilizados. pode haver por exemplo uma rede com máquinas w2k e wxp. não me recordo de nenhuma ocorrência de rede com máquinas linux e windows, porém, não é impossível.

É (*bem*)² mais em baixo mesmo...


Chester

É, bem mais embaixo mesmo... :-)

Nem sonho em dar uma solução de bate-pronto, claro. Mas no mínimo tem que agregar uma solução de sync com o horário local (o timefix rola pra máquinas Linux, teria que ter algo semelhante que lesse o zone loca para máquinas Windows) aliada a uma solução de sync do horário local com o global (e essa teria que ser nativa).

Essa solução nativa é que são elas. Talvez um policy de rede aplicado por um controlador de domínio - que eu não sei se considera zona geográfica com essa granularidade, nem se permite setar um timezone customizado. Outra alternativa seria um programa que aplique manualmente as configurações de timezone na máquina (possivelmente orientado por uma configuração geográfica local *e* info do servidor, para que as políticas locais possam ser refletidas a partir da localização central).

Uma outra alternativa (bem mais radical) seria usar sua própria implementação de Date/Calendar (que consultaria o horário GMT no servidor ou em algum time server e adaptaria à configuração geográfica local), mas aí é chamar outro caminhão de problemas.

É, nessas horas eu lembro por que cliente-servidor realmente é complicado de escalar, especialmente em ambientes geograficamente dispersos. :-(

Boa sorte!



Chester

Olhando no catalina.sh do Tomcat 6.0.14, vejo que todas as possibilidades de execução são do tipo:

exec "$_RUNJAVA" $JAVA_OPTS $CATALINA_OPTS \

...

org.apache.catalina.startup.Bootstrap "$@" start

Desta forma, o passo 2 consiste em substituir todas as ocorrências de "org.apache.catalina.startup.Bootstrap" por "timefix org.apache.catalina.startup.Bootstrap" (sem as aspas).

O passo 1 você até pode fazer em algum ponto do arquivo, mas eu vi que ele chama um setclasspath.sh (que limpa o classpath antes de construí-lo). Desta forma, o mais limpinho parece ser colocar no final deste arquivo a linha

export CLASSPATH=$CLASSPATH:/xyz/timefix-1.0.jar

(/xyz é o lugar onde o jar foi colocado, altere conforme o seu caso)

Veja se rolou e dê um toque. Abraço!


Diego

Tentei setar no tomcat, mas olha o erro que deu:

Exception in thread "main" java.lang.ClassNotFoundException: org.apache.catalina.startup.Bootstrap

at java.net.URLClassLoader$1.run(URLClassLoader.java:200)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:188)

at java.lang.ClassLoader.loadClass(ClassLoader.java:306)

at java.lang.ClassLoader.loadClass(ClassLoader.java:251)

at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

at java.lang.Class.forName0(Native Method)

at java.lang.Class.forName(Class.java:164)

at br.blog.chester.timefix.Timefix.main(Timefix.java:112)

Alguem sabe o que pode ser?


Chester

Hmm... pela mensagem, ele encontrou o TimeFix (ou seja, o CLASSPATH inclui ele), mas não encontrou a classe principal do Tomcat (ou seja, o CLASSPATH não inclui ela). Veja se você está realmente *adicionando* o path do timefix-1.0.jar no CLASSPATH, ou se você está *substituindo* por ele (que é o que a mensagem sugere).

O comando export CLASSPATH=$CLASSPATH:/xyz/timefix-1.0.jar deve preservar o CLASSPATH original. Testei o equivalente dele aqui (estou numa máquina Windows) e ele setou o classpath apropriadamente.




Daniel

Olá, sei que o post é antigo, mas li e surgiu uma duvida:

meu script não está em /etc/init.d/

vai funcionar em outro caminho ?

Chester

E o meu sistema deficiente de notificações da época me fez não responder a tempo. Sim, deve funcionar independente do caminho. Sobre o "permission denied" abaixo, tente um chmod dando direito de leitura para qualquer usuário (não deve gerar um issue de segurança, o arquivo é público mesmo).