A API padrão de manipulação de data/hora do Java sempre foi alvo de críticas por parte da comunidade. Muitos a consideram complicada e até mesmo deselegante. Tarefas simples, como calcular dias entre datas não estão disponíveis na API e devem ser calculadas "no braço". Isso contraria o movimento atual do desenvolvimento de software, que a cada dia privilegia linguagens e frameworks menos "verbosos", que economizam digitação e favorecem a produtividade e expressividade.

Visando atacar as deficiências da API nativa, foi criado a biblioteca Joda-Time, que oferece uma solução de alto nível para manipular data/hora. Alguns pontos a destacar do Joda-Time:

  • Facilidade de uso
  • Simplicidade
  • Imutabilidade
  • Interface Fluente

Veremos como utilizar alguns dos recursos do Joda-Time para resolveremos problemas comuns do dia-a-dia. Nos exemplos abaixo, utilizou-se a versão 2.1 do Joda Time, que pode ser obtida nesse link.

Classe DateTime

A classe DateTime é a alternativa à classe Calendar. As classes Calendar e Date representam instantes no tempo. Um instante pode ser considerado a quantidade de milisegundos a partir da data de referência: 1970-01-01T00:00Z (GMT).


import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class InstanteTest {    
    public static void main(String... args) {        
        // Configurando a data referência
        TimeZone timeZone = TimeZone.getTimeZone("GMT");
        Calendar calendar = new GregorianCalendar(1970, 00, 01, 00, 00, 00);
        calendar.setTimeZone(timeZone);
        // Deve ser 0
        System.out.println("Diferenca em milisegundos = " + calendar.getTimeInMillis());
        
        // Configurando uma hora de diferença
        calendar.set(Calendar.HOUR_OF_DAY, 1);
        // Deve ser de 1 hora = 3600000 milisegundos
        System.out.println("Diferenca em milisegundos = " + calendar.getTimeInMillis());
        System.out.println("Diferenca em horas = " + calendar.getTimeInMillis()/1000/60/60);
    }    
}
Listagem 1. Compreendendo a data de referência

Diferenca em Milisegundos = 0
Diferenca em Milisegundos = 3600000
Diferenca em Horas = 1
Listagem 2. Resultado do código da listagem 1

Isso demonstra que o método getTimeInMillis() retorna sempre a diferença em milissegundos da data configurada no Calendar e a data referência.

Como DateTime utiliza a mesma data de referência, ela é facilmente intercambiável com a API Padrão do Java. Além disso, ela é imutável e provê várias facilidades que suas co-irmãs não possuem.


import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Hours;

public class InstanteJodaTimeTest {
    
    public static void main(String... args) {        
        // Configurando a data referência
        DateTimeZone timeZone = DateTimeZone.forID("GMT");
        DateTime dateTime1 = new DateTime(1970, 01, 01, 00, 00, 00, timeZone);
        // Deve ser 0
        System.out.println("Diferenca em milisegundos = " + dateTime1.getMillis());
        
        // Configurando uma hora de diferença
        DateTime dateTime2 = dateTime1.plusHours(1);
        // Deve ser de 1 hora = 3600000 milisegundos
        System.out.println("Diferenca em milisegundos = " + dateTime2.getMillis());
        System.out.println("Diferenca em horas = " + Hours.hoursBetween(dateTime1, 
        dateTime2).getHours());
    }    
}
Listagem 3. Versão utilizando Joda-Time

Diferenca em milisegundos = 0
Diferenca em milisegundos = 3600000
Diferenca em horas = 1
Listagem 4. Saída da versão utilizando Joda-Time

Existem algumas diferenças importantes entre os 2 códigos:

  • DateTime é imutável, por isso cada operação (como plusHour), retorna a cópia do original (no caso, com a hora acrescida). Não existe um set(field, value) como no Calendar. Isso é similar as operações da classe String, que também é imutável.
  • DateTime trabalha com o mês = 1 e não 0, como o Date/Calendar.
  • DateTime oferece várias métodos intuitivos para manipular os campos hora, minuto e segundo, como os métodos plus e minus.
  • Existe a classe Hours, que entre outras coisas, calcula a diferença de horas entre dois DateTimes. Além dela, temos as classes Days, Months, Minutes, Seconds, Weeks e Years que facilitam o cálculo de diferença de data/hora.

Formatação de data/hora com DateTime

A classe DateTime oferece diversos métodos para formatar data/hora e campos individuais, tornando o trabalho bem mais simples.


import org.joda.time.DateTime;

public class FormatacaoTest {

    public static void main(String... args) {

        DateTime dateTime = new DateTime(1970, 01, 01, 00, 00, 00);
        
        // Imprimindo a data no formato YYYY-MM-dd
        System.out.println("dateTime.toString() = " + dateTime.toString("YYYY-MM-dd"));
        
        // Imprimindo a data no formato YYYY-MM-dd HH:mm:ss
        System.out.println("dateTime.toString() = " + dateTime.toString("YYYY-MM-dd HH:mm:ss"));
        
        // Imprimindo o mês: Janeiro
        System.out.println("dateTime.toString() = " + dateTime.monthOfYear().getAsText());
        
        // Imprimindo o mês: Jan
        System.out.println("dateTime.toString() = " + dateTime.monthOfYear().getAsShortText());        

        // Imprimindo o mês em Inglês
        System.out.println("dateTime.toString() = " + dateTime.monthOfYear().getAsText(Locale.ENGLISH));
    }
}
Listagem 5. Explorando as opções de formatação do DateTime

dateTime.toString() = 1970-01-01
dateTime.toString() = 1970-01-01 00:00:00
dateTime.toString() = Janeiro
dateTime.toString() = Jan
dateTime.toString() = January
Listagem 6. Resultado das opções de formatação

Classes DateTimeFormatter e DateTimeFormat

Para realizar o parse e formatação de Strings para data/hora e vice-versa, temos as classes DateTimeFormatter (que efetua as operações de formatação e parse) e a DateTimeFormat (fábrica que cria objetos DateTimeFormatter a partir de padrões e estilos).


import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

public class DateTimeFormatterTest {
    
    public static void main(String ...args) {
        DateTime dateTime = new DateTime(1970, 01, 01, 00, 00, 00);
        DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MM-YYYYY");
        
        // Alternativa 1
        System.out.println(fmt.print(dateTime));
        // Alternativa 2
        System.out.println(dateTime.toString(fmt));
        
        // Efetuando parse da string no formato "dd-MM-YYYYY"
        dateTime = fmt.parseDateTime("21-12-2012");
        System.out.println(dateTime.toString(fmt));
        
        // Imprimindo no formato ISO8601 
        fmt = ISODateTimeFormat.dateTime();
        System.out.println(fmt.print(dateTime));
    }    
}
Listagem 7. Usando DateTimeFormatter

01-01-1970
01-01-1970
21-12-2012
2012-12-21T00:00:00.000-02:00
Listagem 8. Resultado do código usando DateTimeFormatter

A formatação/parse com essas classes é tão simples quando usar SimpleDateFormat, com a vantagem de ambas serem classes imutáveis e o SimpleDateFormat não.

Alias, a imutabilidade é usada intensamente na API JodaTime, o que permite o uso do method chaining, que é uma das formas de se implementar interface fluente.


import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

public class MethodChaining {

    public static void main(String... args) {
        
        DateTime dateTime = new DateTime();
        
        // Configurando o ano para 2010
        System.out.println(dateTime.withYear(2010));
        // Somando 20 dias
        System.out.println(dateTime.withYear(2010).plusDays(20));
        // Verificando se o ano é bissexto
        System.out.println(dateTime.withYear(2010).plusDays(20).year().isLeap());
        
        DateTimeFormatter fmt = new DateTimeFormatterBuilder().
                appendDayOfMonth(2). // 2 Digito (Valor mínimo) - 
                Preenche com 0 se for menor que 10
                appendLiteral('-'). // Separador
                appendMonthOfYearText(). // Mes como Texto
                appendLiteral('-'). // Separador
                appendYear(2, 4).   // Numero minimo para impressao (2) | 
                Numero maximo para parse (4)                
                toFormatter();
        
        // Imprime DD-Mes Por Extenso-Ano
        System.out.println(fmt.print(dateTime));
    }
    
}
Listagem 9. Demonstrando encadeamento de métodos

2010-12-02T00:19:46.984-02:00
2010-12-22T00:19:46.984-02:00
false
02-Dezembro-2012
Listagem 10. Resultado do encadeamento de métodos

A biblioteca JodaTime oferece diversos outros recursos para se trabalhar com data/hora no Java. Ela também pode ser utilizada em conjunto com Date/Calendar, visto que a classe DateTime aceita em seu construtor objetos dessas 2 classes, e também possui métodos que retornam tanto Date (toDate()), como Calendar (toCalendar(), toGregorianCalendar()).

Apenas arranhamos o universo de classes da API Joda Time. Espero que esse artigo contribua para a compreensão dessa importante API (O Java 8 poderá ter uma nova API de data/hora inspirada no JodaTime. ref: InfoQ).

Referências: