Horário de verão dos infernos

2 respostas
TaQ

Estou curioso como o java.util.Date, java.sql.Date e o Calendar calculam as horas no horário de verão.

Como vocês sabem algum esperto me coloca o mardito para começar bem no finados, Terça-Feira, legal né, e termina no dia 20 de Fevereiro.
Fiz um programinha para criar uma TimeZone customizada para dar conta disso (vocês sabiam que o Java não pega essas configurações do SO, pelo menos no Linux, e sim de umas configurações malucas que ele tem no $JAVA_HOME/jre/lib/zi? vai entender…)
e tentar fazer um cálculo mais exato das datas (não reparem que está em inglês que eu postei em developers.sun.com):

import java.sql.*;
import java.util.*;
import java.text.*;

public class DateParsing {
	public static void main(String args[]){
		// here I run the program the default way
		System.out.println("
BEFORE
--------------------------------------------------------------------");
		showDate("01/11/2004");
		showDate("02/11/2004");
		showDate("03/11/2004");
		
		// here I create a new TimeZone, with user defined
		// start and end rules
		SimpleTimeZone mtz = new SimpleTimeZone(TimeZone.getDefault().getRawOffset(),"America/Sao_Paulo");
		mtz.setStartRule(10,2,0);	// November, 2, 00:00, starts, goes to November, 3, 01:00, right?
		mtz.setEndRule(1,20,0);		// February, 20, 00:00, ends, goes to February, 19, 23:00, right?
		TimeZone.setDefault(mtz);	// set this zone as the default one
		
		// now print the values
		System.out.println("
AFTER
---------------------------------------------------------------------");
		showDate("01/11/2004");
		showDate("02/11/2004");
		showDate("03/11/2004");
	}

	public static void showDate(String date){
		try{
			SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy");
		
			// regular Date	
			java.util.Date dt = fmt.parse(date);
			System.out.println("java.util.date ("+date+") : "+dt);

			// SQL date
			String day	= date.substring(0,2);
			String month= date.substring(3,5);
			String year	= date.substring(6);
			String sql  = year+"-"+month+"-"+day;
			java.sql.Date  sd = java.sql.Date.valueOf(sql);
			System.out.println("java.sql.date  ("+sql+") : "+sd);

			// Calendar date
			Calendar cal = Calendar.getInstance();
			cal.set(cal.DAY_OF_MONTH,Integer.parseInt(day));
			cal.set(cal.MONTH,Integer.parseInt(month)-1);
			cal.set(cal.YEAR,Integer.parseInt(year));
			cal.set(cal.HOUR_OF_DAY,0);
			cal.set(cal.MINUTE,0);
			cal.set(cal.SECOND,0);
			System.out.println("calendar date  ("+sql+") : "+cal.getTime());
			
			// Calendar date plus one hour
			cal = Calendar.getInstance();
			cal.set(cal.DAY_OF_MONTH,Integer.parseInt(day));
			cal.set(cal.MONTH,Integer.parseInt(month)-1);
			cal.set(cal.YEAR,Integer.parseInt(year));
			cal.set(cal.HOUR_OF_DAY,1);
			cal.set(cal.MINUTE,0);
			cal.set(cal.SECOND,0);
			System.out.println("calendar date +("+sql+") : "+cal.getTime());
			System.out.println("");

		}catch(Exception e){
			System.err.println("ERROR SHOWING "+date+" : "+e.getMessage());
		}	
	}
}

Rodando esse programa eu tenho esse output:

BEFORE
--------------------------------------------------------------------
java.util.date (01/11/2004) : Mon Nov 01 00:00:00 BRST 2004
java.sql.date  (2004-11-01) : 2004-11-01
calendar date  (2004-11-01) : Mon Nov 01 00:00:00 BRST 2004
calendar date +(2004-11-01) : Mon Nov 01 01:00:00 BRST 2004

java.util.date (02/11/2004) : Tue Nov 02 00:00:00 BRST 2004
java.sql.date  (2004-11-02) : 2004-11-02
calendar date  (2004-11-02) : Tue Nov 02 00:00:00 BRST 2004
calendar date +(2004-11-02) : Tue Nov 02 01:00:00 BRST 2004

java.util.date (03/11/2004) : Wed Nov 03 00:00:00 BRST 2004
java.sql.date  (2004-11-03) : 2004-11-03
calendar date  (2004-11-03) : Wed Nov 03 00:00:00 BRST 2004
calendar date +(2004-11-03) : Wed Nov 03 01:00:00 BRST 2004

AFTER
---------------------------------------------------------------------
java.util.date (01/11/2004) : Mon Nov 01 00:00:00 BRT 2004
java.sql.date  (2004-11-01) : 2004-11-01
calendar date  (2004-11-01) : Mon Nov 01 00:00:00 BRT 2004
calendar date +(2004-11-01) : Mon Nov 01 01:00:00 BRT 2004

java.util.date (02/11/2004) : Mon Nov 01 23:00:00 BRT 2004
java.sql.date  (2004-11-02) : 2004-11-01
calendar date  (2004-11-02) : Mon Nov 01 23:00:00 BRT 2004
calendar date +(2004-11-02) : Tue Nov 02 01:00:00 BRST 2004

java.util.date (03/11/2004) : Wed Nov 03 00:00:00 BRST 2004
java.sql.date  (2004-11-03) : 2004-11-03
calendar date  (2004-11-03) : Wed Nov 03 00:00:00 BRST 2004
calendar date +(2004-11-03) : Wed Nov 03 01:00:00 BRST 2004

Com a time zone default, tudo funcionou ok (até que entrasse no daylight da zona definida pelo Java né), mas olhem os valores DEPOIS que eu alterei a time zone com os valores corretos do horário de verão:

  • 1 de Novembro está ok;

  • 2 de Novembro (quando começa o horário de verão) SEMPRE é calculado como 1 de Novembro, no java.util.Date, java.sql.Date e Calendar. Quando eu seto a HOUR_OF_DAY do Calendar para 1, fica correto. E deêm uma olhada na timestamp do java.util.Date and Calendar: 23:00! Mas por que ele decrementa a hora, se ele precisa incrementar? Desse modo, um simples parse usando java.util.Date, java.sql.Date e Calendar resulta em um erro. Eu tive alguns valores errados inseridos em umas tabelas do banco de dados por causa desse cálculo maluco.

  • 3 de Novembro está ok de novo;

Então, o que acontece? Por que quando eu digo “ei, dê um parse nessa string ‘02/11/2004’” o Java insiste que é 1 de Novembro? Me parece que as timestamps criadas sem hora definida explicitamente pegam por default 00:00, mas por que o horário de verão está decrementando e não adicionando uma hora ali?
Ah, e cadê a 00:00 do dia 2 nesse rolo todo? E por que dia 3 está correto?
Arrrgh. :slight_smile:

Obrigado!

2 Respostas

hmichel

Cara, da uma olhada nesta solução para o problema de horário de verão.

http://www.portaljava.com.br/home/modules.php?name=News&file=article&sid=149

Espero ter ajudado.

TaQ

Oi!
Obrigado por responder. Eu dei uma olhada lá no site, obrigado por apontar, mas vou ter que entrar em contato lá com o Benício Silva Gontijo por que o bug que mencionei aparece ali também!
O problema é justamente o dia que troca o horário, que aparece aquela coisa maluca de converter a data para a data anterior as 23:00.
Eu alterei o programa de teste dele para usar aquela função showDate(s) que fiz ali em cima:

import java.util.*;
import java.text.*;

public class TesteHorarioVerao
{
  public static void main(String[] args){
     // Cria uma TIME ZONE correspondente ao horário de Brasília
     SimpleTimeZone pdt = new
           SimpleTimeZone(-3 * 60 * 60 * 1000,"GMT-3:00");

     // Seta as regras para o horário de verão Brasileiro
     // Começando no primeiro domingo após o dia primeiro
     pdt.setStartRule(Calendar.NOVEMBER, 1, Calendar.SUNDAY,0);

     // Terminando no último domingo do mês de Fevereiro
     pdt.setEndRule(Calendar.FEBRUARY, -1, Calendar.SUNDAY,0);
	  
	  TimeZone.setDefault(pdt);
	  
     // Instanciando um GregorianCalendar com com a time zone de BSB
     // e levando em consideração as regras do horário de verão.
     Calendar dataHoje = new GregorianCalendar();

     System.out.println(dataHoje.get(Calendar.HOUR_OF_DAY) + ":" +
                  dataHoje.get(Calendar.MINUTE) + ":" +
                  dataHoje.get(Calendar.SECOND));
						
	  showDate("01/11/2004");
	  showDate("02/11/2004");
	  showDate("03/11/2004");
	  showDate("07/11/2004");
  }
  
	public static void showDate(String date){
		try{
			SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy");
		
			// regular Date	
			java.util.Date dt = fmt.parse(date);
			System.out.println("java.util.date ("+date+") : "+dt);

			// SQL date
			String day	= date.substring(0,2);
			String month= date.substring(3,5);
			String year	= date.substring(6);
			String sql  = year+"-"+month+"-"+day;
			java.sql.Date  sd = java.sql.Date.valueOf(sql);
			System.out.println("java.sql.date  ("+sql+") : "+sd);

			// Calendar date
			Calendar cal = Calendar.getInstance();
			cal.set(cal.DAY_OF_MONTH,Integer.parseInt(day));
			cal.set(cal.MONTH,Integer.parseInt(month)-1);
			cal.set(cal.YEAR,Integer.parseInt(year));
			cal.set(cal.HOUR_OF_DAY,0);
			cal.set(cal.MINUTE,0);
			cal.set(cal.SECOND,0);
			System.out.println("calendar date  ("+sql+") : "+cal.getTime());
			
			// Calendar date plus one hour
			cal = Calendar.getInstance();
			cal.set(cal.DAY_OF_MONTH,Integer.parseInt(day));
			cal.set(cal.MONTH,Integer.parseInt(month)-1);
			cal.set(cal.YEAR,Integer.parseInt(year));
			cal.set(cal.HOUR_OF_DAY,1);
			cal.set(cal.MINUTE,0);
			cal.set(cal.SECOND,0);
			System.out.println("calendar date +("+sql+") : "+cal.getTime());
					
			System.out.println("");

		}catch(Exception e){
			System.err.println("ERROR SHOWING "+date+" : "+e.getMessage());
		}	
	}
}

Note que:

1 - Eu tive que por a TimeZone criada usando a TimeZone.setDefault(t), para que todas as datas criadas com o java.sql.Date e java.util.Date pudessem assumir essa TimeZone - eu poderia ter criado o Calendar com a TimeZone criada, mas deixei ele no default;

2 - Inseri a data 07/11/2004, que segundo as regras que ele definiu, é o dia que começa o horário de verão;

Resultado:

9:42:25
java.util.date (01/11/2004) : Mon Nov 01 00:00:00 GMT-3:00 2004
java.sql.date  (2004-11-01) : 2004-11-01
calendar date  (2004-11-01) : Mon Nov 01 00:00:00 GMT-3:00 2004
calendar date +(2004-11-01) : Mon Nov 01 01:00:00 GMT-3:00 2004

java.util.date (02/11/2004) : Tue Nov 02 00:00:00 GMT-3:00 2004
java.sql.date  (2004-11-02) : 2004-11-02
calendar date  (2004-11-02) : Tue Nov 02 00:00:00 GMT-3:00 2004
calendar date +(2004-11-02) : Tue Nov 02 01:00:00 GMT-3:00 2004

java.util.date (03/11/2004) : Wed Nov 03 00:00:00 GMT-3:00 2004
java.sql.date  (2004-11-03) : 2004-11-03
calendar date  (2004-11-03) : Wed Nov 03 00:00:00 GMT-3:00 2004
calendar date +(2004-11-03) : Wed Nov 03 01:00:00 GMT-3:00 2004

java.util.date (07/11/2004) : Sat Nov 06 23:00:00 GMT-3:00 2004
java.sql.date  (2004-11-07) : 2004-11-06
calendar date  (2004-11-07) : Sat Nov 06 23:00:00 GMT-3:00 2004
calendar date +(2004-11-07) : Sun Nov 07 01:00:00 GMT-3:00 2004

Olha lá no dia 07/11/2004, lá vai ele de novo converter a data para 06/11/2004 23:00h! No dia que começa o horário de verão (e pelo que pude perceber, as 00:00), ele retrocede para o dia anterior, uma hora antes.
E no caso do exemplo, ele estava pegando a timestamp atual. Se precisarmos de uma timestamp construída através de algum parametro (coisa comum: você vai comparar duas datas, vai gerar uma java.sql.Date para gravar em uma tabela etc), vai dar pau, mesmo usando a TimeZone definida por ele (especificando a TimeZone default ainda como aquela definida ):

Calendar daylight = new GregorianCalendar(pdt);
daylight.set(daylight.DAY_OF_MONTH,7);
daylight.set(daylight.MONTH,10);
daylight.set(daylight.YEAR,2004);
daylight.set(daylight.HOUR_OF_DAY,0);
daylight.set(daylight.MINUTE,0);
daylight.set(daylight.SECOND,0);
System.out.println("daylight:"+daylight.getTime());

Vai retornar 06/11/2004 23:00. :-p
E talvez mesmo no dia que começar o horário de verão, bateu 00:00 do dia 07/11 (no exemplo), ele vai retornar 06/11/2004 23:00 e vai processar a data incorretamente, até pelo menos dia 07/11/2004 01:00, onde vai voltar “ao normal”.
Olha que interessante isso agora, sem alterar a TimeZone default, usando a TimeZone criada por ele no construtor do GregorianCalendar:

Calendar d = new GregorianCalendar(pdt);
for(int i=5;i<10;i++){
   d.set(d.DAY_OF_MONTH,i);
   d.set(d.MONTH,10);
   d.set(d.YEAR,2004);
   d.set(d.HOUR_OF_DAY,0);
   d.set(d.MINUTE,0);
   d.set(d.SECOND,0);
   System.out.println("Calendar:"+d.getTime()+" "+(pdt.inDaylightTime(d.getTime())?"verão":"normal"));
}

retorna

Calendar:Fri Nov 05 01:00:00 BRST 2004 normal Calendar:Sat Nov 06 01:00:00 BRST 2004 normal Calendar:Sun Nov 07 00:00:00 BRST 2004 normal Calendar:Mon Nov 08 00:00:00 BRST 2004 verão Calendar:Tue Nov 09 00:00:00 BRST 2004 verão
Vai entender.
Acho que isso é um bug @#$@$@# mesmo no Java. :frowning:

Criado 15 de outubro de 2004
Ultima resposta 16 de out. de 2004
Respostas 2
Participantes 2