É muito comum a manipulação de datas na linguagem Java, pois este artifício faz-se necessário em quase todos os tipos de sistemas. É perceptível que as classes que trabalham com Data, do Java 8 pra trás, possuem poucos recursos e para realizar tarefas simples acaba sendo uma grande dor de cabeça.

Neste artigo veremos uma biblioteca muito conhecida e utilizada quando tratamos de Data no Java: a Joda-Time. Esta biblioteca oferece inúmeros recursos que possibilitam tornar tarefas antes complexas em simples, é o velho ditado: “Porque reinventar a roda se ela já foi inventada?”.

Usaremos a IDE Eclipse para teste das nossas aplicações e importaremos a biblioteca joda-time-2.7.jar no classpath do nosso projeto para que seja possível usar as classes oferecidas pela mesma. Este arquivo jar pode ser encontrado na opção código fonte deste artigo.

Joda-Time

A primeira pergunta a se fazer é: Porque eu deveria usar o Joda-Time? Porque não continuar usando as próprias classes oferecidas pelo Java? Vejamos:

  1. A produtividade aumentará muito com o uso de uma biblioteca que lhe forneça resultados simples para processos que antes eram considerados complexos.
  2. Proporciona melhor performance do que a classe Calendar, usualmente utilizada para fins de manipulação de datas. Isso ocorre devido ao mínimo de cálculo realizado no acesso de algum campo.
  3. Possui vasta comunidade e documentação para auxiliar-lhe em dúvidas ou problemas que possam ocorrer.
  4. Cálculos de Time Zone são atualizados várias vezes por ano, para garantir consistência dos dados. Estes são atualizados a partir de http://www.iana.org/time-zones
  5. Possui métodos mais simples do que a classe Calendar.
  6. É Open-source, isso significa que qualquer um pode estudar seu código fonte e contribuir se achar relevante.

Para usar o Joda-Time em seu projeto basta adicionar o jar da seção de downloads no classpath do seu projeto, ou caso você use Maven você poderá adicionar as linhas da Listagem 1 no seu pom.xml

Listagem 1. Adicionando joda-time no Maven


  <dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.7</version>
  </dependency>

Estudaremos neste artigo o uso de cinco classes frequentemente utilizadas: Instant, DateTime, LocalDate, LocalTime, LocalDateTime.

DateTime

A primeira classe que devemos aprender é a DateTime, que trata-se de uma substituição da classe Calendar nativa do Java.

Esta é classe é imutável e possui uma grande variedade de construtores. Um destes construtores recebe um Object, o que possibilita uma enorme variedade de argumentos, vejamos:

  • Date, do JDK.
  • Calendar, do JDK.
  • String, no formato ISO8601.
  • Long, em milissegundos.
  • Qualquer uma das classes mencionadas acima (Instant, DateTime, LocalDate, LocalTime e LocalDateTime).

Veja como é prático o uso do joda-time, já que ele consegue entender que queremos converter o nosso Date (JDK) para um DateTime do joda-time, como mostra a Listagem 2.

Listagem 2. Convertendo java.util.Date para DateTime do Joda-Time


  java.util.Date juDate = new Date();
    DateTime dt = new DateTime(juDate);

Quando conseguimos capturar o objeto DateTime, já podemos começar a utilizar seus métodos, assim como mostra a Listagem 3.

Listagem 3. Usando DateTime pela primeira vez


    java.util.Date juDate = new Date();
    DateTime dt = new DateTime(juDate);
    int month = dt.getMonthOfYear();
    int year = dt.getYear();

O que temos nessa listagem é a criação do nosso objeto DateTime a partir de um java.util.Date. Depois capturamos o mês e o ano da data que foi passada. Para o Joda-Time, Janeiro é representando pelo inteiro 1 e dezembro pelo inteiro 12.

Logo no início falamos que a classe DateTime é imutável, mas o que significa isto? Significa que após a sua criação ela não pode ser alterada, assim como a classe String. Se você criar uma DateTime passando a um java.util.Date, você poderá manipular este retornando novos valores mas não alterando o valor interno. Vejamos como funciona na Listagem 4.

Listagem 4. DateTime é imutável


      java.util.Date juDate = new Date();
     DateTime dt = new DateTime(juDate);
    DateTime year2000 = dt.withYear(2000);
    DateTime twoHoursLater = dt.plusHours(2);

Quando chamamos o método withYear(), o Joda-Time irá usar a data configurada para criar uma nova data com o ano 2000, e o mesmo é feito no plusHours(). Ele usa a data configurada (sem mudá-la) e cria uma nova data adicionando mais duas horas. É por esse motivo que quando chamamos ambos os métodos atribuímos a um novo objeto DateTime. Isso nos garante integridade a informação que foi repassada, pois a qualquer momento podemos recuperar a data original sem nenhuma alteração.

O Joda-Time possui uma classe chamada Property, que é responsável disponibilizar métodos importantes para campos que gostaríamos de manipular. Vejamos um exemplo na Listagem 5.

Listagem 5. Usando a classe Property para manipular campos


  DateTime dt = new DateTime();
               String monthName = dt.monthOfYear().getAsText();
               String frenchShortName = dt.monthOfYear().getAsShortText(Locale.FRENCH);
               boolean isLeapYear = dt.year().isLeap();
               DateTime rounded = dt.dayOfMonth().roundFloorCopy();
               System.out.println(frenchShortName);

O que temos acima é o uso da classe Property para nos auxiliar na manipulação dos dados. Logo na primeira linha usamos o getAsText() para retornar o nome do mês por extenso e não apenas um inteiro, como seria o normal. Logo em seguida retornamos o mesmo conteúdo da linha anterior porém em francês. Em isLeap() verificamos se o ano é bissexto e, por último, o roundFloorCopy() arredonda o valor para “baixo”. Veja um exemplo para demonstrar seu uso na Listagem 6.

Listagem 6. Exemplo roundFloorCopy()


  import org.joda.time.DateTime;
   
  public class JodaTimeMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               DateTime dt = new DateTime();
               DateTime rounded = dt.dayOfMonth().roundFloorCopy();
               System.out.println("Antes: "+dt.toString());
               System.out.println("Depois: "+rounded.toString());
         }
   
  }
   
  Saída:
  Antes: 2015-01-14T20:32:38.344-03:00
  Depois: 2015-01-14T00:00:00.000-03:00

O que podemos concluir com a Listagem 6 é que o roundFloorCopy() cria uma nova data, baseada na data original, arredondando-a para um valor inferior. Isso é perceptível quando olhamos a saída da Listagem 6, pois a data antes do arredondamento é maior (em horas) do que a data gerada depois do roundFloorCopy().

Instant

No Joda-Time, a classe Instant representa exatamente um instante no tempo contando em milissegundos desde 01/01/1970 00:00, ou seja, se hoje for 14/01/2015 20:44, quantos milissegundos há desde a data citada como inicial? A resposta disto será o instante exato em que estamos.

A classe Instant é usada pelo DateTime, por exemplo, quando você usar o método getYear() para retornar o ano, assim ele faz uma conversão interna de milissegundos para o que você deseja. Se você olhar a estrutura hierárquica das classes date-time do joda-time, irá perceber que elas implementam direta ou indiretamente a interface ReadableInstant, o que garante o retorno de milissegundos desde 01/01/1970 00:00 como citamos anteriormente. Vejamos um exemplo na Listagem 7.

Listagem 7. Entendendo o Instant


  import org.joda.time.DateTime;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               DateTime dt = new DateTime();
               System.out.println(dt.getYear());
         }
   
  }
   
  Visualizando dentro do dt.getYear():
   
  /**
       * Get the year field value.
       *
       * @return the year
       */
      public int getYear() {
          return getChronology().year().get(getMillis());
      }

Perceba que, na verdade, o retorno do ano só é possível, pois temos o instante em milissegundos, retornado através do método getMillis().

O Joda-Time suporta múltiplos tipos de calendários, assim como o JDK. A diferença é que no JDK esses outros tipos de calendários devem ser uma subclasse da classe Calendar, enquanto que a estratégia do joda-time é um pouco diferente, trabalhando com uma arquitetura de “calendários plugáveis”. A classe Chronology permite o uso de oito tipos de calendários distintos, sendo eles: Buddhist, Coptic, Ethiopic, Gregorian-Julian cutover, Gregorian, Islamic, ISO e Julian.

No JDK, para cada um dos calendários citados você teria que chamar uma nova classe estendendo a classe Calendar, implementando o calendário que você deseja, um tanto quanto trabalhoso. No caso do Joda-Time, você usa a classe Chronology para retornar o calendário desejado, como podemos ver no exemplo da Listagem 8.

Listagem 8. Retornando calendário Coptic com Chronology


  import org.joda.time.Chronology;
  import org.joda.time.DateTime;
  import org.joda.time.DateTimeZone;
  import org.joda.time.chrono.CopticChronology;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               DateTimeZone zone = DateTimeZone.forID("Europe/London");
               Chronology coptic = CopticChronology.getInstance(zone);
   
               
               DateTime dt = new DateTime(coptic);
   
               int year = dt.getYear();   
               int month = dt.getMonthOfYear();
               System.out.println(year);
               System.out.println(month);
         }
   
  }

Antes de você questionar, o mês e o ano retornado não estão errados, pois trata-se de outro calendário com outras datas. Nós, brasileiros, usamos o calendário gregoriano (Gregorian Calendar) representado pela classe GregorianCalendar.

Perceba que para usar o calendário Coptic apenas retornamos a instância do CopticChronology e podemos trabalhar com ele como um Chronology, sem se preocupar com o tipo de calendário, usando os mesmos métodos que usaríamos para outros calendários.

LocalDate, LocalTime e LocalDateTime

Diferente do DateTime, que é a forma mais completa de representar uma data, com ano, mês, dia, horas, minutos, segundos e time-zone, o LocalDate, LocalTime e LocalDateTime representam a mesma data com informações ausentes, ou seja, de uma forma mais simplista, mas o objetivo final é o mesmo: representar uma data.

O LocalDate representa uma data sem considerar horas, minutos, segundos e o time-zone. Nas listagens abaixo veremos as três classes e sempre a saída comparada de um DateTime com a classe referenciada, para que você leitor possa ter uma melhor visualização da diferença.

Listagem 9. Usando LocalDate


  import org.joda.time.DateTime;
  import org.joda.time.LocalDate;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               LocalDate ld = new LocalDate();
               System.out.println("LocalDate: "+ld.toString());
               
               DateTime dt = new DateTime();
               System.out.println("DateTime: "+dt.toString());;
         }
   
  }
  Resultado:
  LocalDate: 2015-01-14
  DateTime: 2015-01-14T21:17:39.028-03:00

Perceba na Listagem 9 que o LocalDate retorna apenas ano, mês e dia.

O LocalTime retorna apenas as horas, minutos, segundos e milissegundos contidos na data, sem considerar o time-zone, como mostra a Listagem 10.

Listagem 10. Usando LocalTime


  import org.joda.time.DateTime;
  import org.joda.time.LocalTime;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               LocalTime ld = new LocalTime();
               System.out.println("LocalTime: "+ld.toString());
               
               DateTime dt = new DateTime();
               System.out.println("DateTime: "+dt.toString());;
         }
   
  }
   
  Resultado:
   
  LocalTime: 21:19:20.481
  DateTime: 2015-01-14T21:19:20.543-03:00

O LocalTime retornou 21 horas, 19 minutos, 20 segundo e 481 milissegundos. O DateTime possui os milissegundos diferentes porque foi executado alguns milissegundos depois do LocalTime, isso é questão apenas de desempenho do processador.

O LocalDateTime retorna o ano, mês e dia, juntamente com as horas, minutos, segundos e milissegundos, porém sem o time-zone, e essa é a grande diferença do LocalDateTime e do Datetime, o TIMEZONE. Veja um exemplo na Listagem 11.

Listagem 11. Usando LocalDateTime


  import org.joda.time.DateTime;
  import org.joda.time.LocalDateTime;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               LocalDateTime ld = new LocalDateTime();
               System.out.println("LocalDateTime: "+ld.toString());
               
               DateTime dt = new DateTime();
               System.out.println("DateTime: "+dt.toString());;
         }
   
  }
   
  Resultado:
  LocalDateTime: 2015-01-14T21:22:29.537
  DateTime: 2015-01-14T21:22:29.655-03:00

O que você pode perceber de diferença é que o DateTime possui o número “-03:00” no final, que simboliza o TimeZone que está configurado para nosso sistema.

Podemos fazer aqui uma mistura de conceitos, usando o DateTime dentro de um dos exemplos apresentados acima, extraindo assim apenas a informações que desejamos mostrar ao usuário. Assim, temos o novo exemplo na Listagem 12.

Listagem 12. Usando DateTime com LocalDate, LocalDateTime e LocalTime


  import org.joda.time.DateTime;
  import org.joda.time.LocalDate;
  import org.joda.time.LocalDateTime;
  import org.joda.time.LocalTime;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               DateTime dt = new DateTime();
               System.out.println("Data completa com timezone: "+dt.toString());
               
               LocalDateTime ldt = new LocalDateTime(dt);
               System.out.println("Data completa sem timezone: "+ldt.toString());
               
               LocalDate ld = new LocalDate(dt);
               System.out.println("Apenas data: "+ld.toString());
               
               LocalTime lt = new LocalTime(dt);
               System.out.println("Apenas horas, minutos, segundos e milissegundos: "+lt.toString());
   
         }
   
  }
   
  Resultado:
   
  Data completa com timezone: 2015-01-14T21:26:48.940-03:00
  Data completa sem timezone: 2015-01-14T21:26:48.940
  Apenas data: 2015-01-14
  Apenas horas, minutos, segundos e milissegundos: 21:26:48.940

Duration

A classe Duration tem um conceito bem simples, mas muito útil, pois ela é capaz de medir a duração entre determinados períodos através do Instant, ou seja, em milissegundos. Como sabemos, o tempo em milissegundos pode ser convertido para qualquer tipo de data (ano, mês, dia e etc.), consequentemente conseguimos capturar a duração entre dias entre determinadas datas, por exemplo.

A fórmula é a seguinte: instant + duration = instant. Isso significa que dado um instante qualquer (em milissegundos) mais uma duração qualquer (em milissegundos) resulta em um novo instante. Com base na fórmula acima você consegue ver outras fórmulas, por exemplo: como saberemos a duração entre dois instantes distintos? A resposta é Duration = instant1 – instant2.

Listagem 13. Usando Duration para retornar quantidade de dias


  import org.joda.time.DateTime;
  import org.joda.time.Duration;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               DateTime start = new DateTime(2015, 01, 10, 0, 0, 0, 0);
               DateTime end = new DateTime(2015, 01, 15, 0, 0, 0, 0);
   
               Duration dur = new Duration(start, end);
   
               System.out.println(dur.getStandardDays());
               
   
         }
   
  }
  Resultado: 5

Na Listagem 13 nós estamos criando dois DateTime: um em 10/01/2015 e outro em 15/01/2015, ambos estão sendo passados para o construtor da classe Duration como sendo início e fim, respectivamente. Quando chamamos o getStandardDays() estamos solicitando a quantidade de dias que há entre essas duas datas, e o resultado obviamente não poderia ser diferente de 5.

Formatando a data

O Joda-Time ainda trás consigo vários recursos além dos citados acima, e um deles muito poderoso é a formatação de datas no padrão desejado. No JDK você teria que usar uma classe como a SimpleDateFormat para converter sua data para o formato desejado, mas no joda-time você apenas precisa configurar como suas datas deverão sair e ao chamar o método toString() do DateTime o formato desejado será usado.

Listagem 14. Usando DateTimeFormat


  import org.joda.time.LocalDate;
  import org.joda.time.format.DateTimeFormat;
  import org.joda.time.format.DateTimeFormatter;
   
   
  public class JodaMain {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               LocalDate date = LocalDate.now();
               DateTimeFormatter fmt = DateTimeFormat.forPattern("d MMMM, yyyy");
               String str = date.toString(fmt);
               System.out.println(str);
               
   
         }
   
  }
   
  Resultado:
   
  14 Janeiro, 2015

Com a classe DateTimeFormat basta chamar o método forPattern() passando o padrão desejado e usar o objeto DateTimeFormatter sempre que for chamar o toString() da classe respectiva, em nosso caso um LocalDate.

Extras

O Joda-time conta também com outras funcionalidades e não apenas relacionadas a Data, este artigo teve foco do “carro-chefe” desta biblioteca, que é a manipulação de datas, mas com o passar dos anos e a evolução desta, foram surgindo novas funcionalidades também interessantes. Veja algumas:

  • Joda Money: provê a manipulação de valores monetários, o que muitas vezes torna-se uma dor de cabeça quando trabalhamos com sistema que exigem uma moeda específica.
  • Joda Beans: proveem diversas funcionalidades as propriedades dos beans no Java, tais como validação através de anotação.
  • Joda Convert: é um pequeno módulo capaz de realizar conversões entre Objetos e Strings.

Estudamos neste artigo as principais classes no joda-time, com exemplos de manipulação e qual o objetivo delas. Vale ressaltar que existem muitas outras, mas estas são as mais utilizadas e com este conhecimento você está totalmente apto a usá-la.