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);
}
}
Diferenca em Milisegundos = 0
Diferenca em Milisegundos = 3600000
Diferenca em Horas = 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());
}
}
Diferenca em milisegundos = 0
Diferenca em milisegundos = 3600000
Diferenca em horas = 1
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));
}
}
dateTime.toString() = 1970-01-01
dateTime.toString() = 1970-01-01 00:00:00
dateTime.toString() = Janeiro
dateTime.toString() = Jan
dateTime.toString() = January
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));
}
}
01-01-1970
01-01-1970
21-12-2012
2012-12-21T00:00:00.000-02:00
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));
}
}
2010-12-02T00:19:46.984-02:00
2010-12-22T00:19:46.984-02:00
false
02-Dezembro-2012
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).