Funcionalidades do JDK 8 (Java 8) - Parte XI - API de Datas

API de datas

No Java 8 surgiu o pacote java.time que nos trouxe uma nova API de datas. Essa API foi inspirada parcialmente no Joda Time que é uma API que já existe há algum tempo e é bem melhor para trabalhar com datas. O Joda-Time é uma biblioteca open source bastante conhecida.

Datas de Modo Mais Fluente

Vamos imaginar que você precise criar uma data com um mês a partir da data atual, no Java 7:

Calendar mesQueVem = Calendar.getInstance();
mesQueVem.add(Calendar.MONTH, 1);

Com a nova API de datas o código fica bem mais moderno utilizando sua interface fluente

LocalDate mesQueVem = LocalDate.now().plusMonths(1);

Além de plusMonths() ainda podemos adicionar dias plusDays(), anos plusYears() e por aí vai.

De forma semelhante podemos decrementar os valores

LocalDate mesPassado = LocalDate.now().minusMonths(1);

A classe LocalDate representa uma data sem time zone, algo como 25-01-1988, se as informações de horário forem importantes usamos a calsse LocalDateTime

LocalDateTime mesQueVem = LocalDateTime.now().plusMonths(1);
LocalDateTime mesPassado = LocalDateTime.now().minusMonths(1);

Com LocalDateTime a saída ficará mais ou menos assim:

25-01-1988T15:01.345

Outra forma de criar uma data com horário específico seria utilizar o método atTime() da classe LocalDate

LocalDateTime dateTime = LocalDate.now().atTime(12, 0);

Note que criamos a partir de LocalDate, porém atTime() retorna um LocalDateTime.

Assim como foi feito com o método atTime() podemo combinar os diferentes modelos

LocalTime agora = LocalTime.now();
LocalDate hoje = LocalDate.now();
LocalDateTime dataEHora = hoje.atTime(agora);

Se precisarmos de um horário baseado em um TimeZone, podemos contar com o método atZone()

LocalTime agora = LocalTime.now();
LocalDate hoje = LocalDate.now();
LocalDateTime dataEHora = hoje.atTime(agora);

ZonedDateTime dataComHoraETimeZone = dataEHora.atZone(ZoneId.of("America/Sao_Paulo"));

Podemos converter esses objetos para outras medidas de tempo utilizando os métodos to, por exemplo toLocalDateTime().

LocalDateTime dataEHoraSemTimeZone = dataComHoraETimeZone.toLocalDateTime();

As classes dessa nova API ainda contam com métodos estáticos of, que são factories methods para construção de novas instâncias

LocalDate date = LocalDate.of(1988, 01, 25);
LocalDateTime dataTime = LocalDateTime.of(1988, 01, 25, 10, 30);

Podemos converter Strings em datas com o método parse() porém esse método exige que a String esteja em um formato correto YYYY-MM-DD

LocalDate date = LocalDate.parse("1988-01-25");
System.out.println(date);

O modelo do java.time é imutável, cada operação devolve um novo valor nunca alterando o valor interno dos horários, datas e intervalos utilizados na operação. De modo semelhante aos setters, os modelos imutáveis possuem métodos with para alterar uma data. Por exemplo:

LocalDate date = LocalDate.now().withYear(1988);
System.out.println(date.getYear());

Aqui é criada uma data com o ano atual, por conta do now() o ano é 2023 nesse caso, depois com o o withYear() o ano é alterado para 1988 !

Outros comportamentos essenciais e interessantes é poder saber se alguma medida de tempo acontece antes, depois ou ao mesmo tempo que outra, para essa finalidade temos os métodos is

LocalDate hoje = LocalDate.now();
LocalDate amanha = LocalDate.now().plusDays(1);

System.out.println(hoje.isBefore(amanha));
System.out.println(hoje.isAfter(amanha));
System.out.println(hoje.isEqual(amanha));

Nesse exemplo apenas isBefore() vai retornar true.

Para comparar datas iguais mas em time zones diferentes também temos um método chamado isEqual() já que o `equals()`` não funcionaria.

ZonedDateTime tokyo = ZonedDateTime.of(1988, 5, 13, 10, 30, 0, 0, ZoneId.of("Asia/Tokyo"));

ZonedDateTime saoPaulo = ZonedDateTime.of(1988, 5, 13, 10, 30, 0, 0, ZoneId.of("America/Sao_Paulo"));

System.out.println(tokyo.isEqual(saoPaulo));

Para que esse resultado seja true precisaríamos acertar a diferença de 12 horas entre as duas time zones

ZonedDateTime tokyo = ZonedDateTime .of(1988, 5, 13, 10, 30, 0, 0, ZoneId.of("Asia/Tokyo"));

ZonedDateTime saoPaulo = ZonedDateTime.of(1988, 5, 13, 10, 30, 0, 0, ZoneId.of("America/Sao_Paulo"));

Uma curiosidade, experimente mudar o mês para 1 (janeiro) e você notará que o isEqual() falha, se você debugar vai notar o por que:

Um objeto ZonedDateTime tem alguns atributos, a saber:

  • dateTime: o date time propriamente dito algo como 1988-05-13T10:30+09:00[Asia/Tokyo]

  • offset: O deslocamento em horas a partir de UTC/Greenwich, algo como +09:00

  • zone: a zona propriamente dita, algo como Asia/Tokyo

Se você prestar atenção ao objeto gerado para São Paulo, vai notar que no mês de maio o offset é -03:00 mas em janeiro é -02:00, isso ocorre por que, nesse momento momento em que o horário de verão está em vigor no Brasil, o offset pode ser diferente. Durante o horário de verão, o Brasil pode mudar para o fuso horário de "-02:00" para aproveitar mais a luz do dia.

Sendo assim para que esse mesmo código funciona é preciso compensar essa 1 hora

ZonedDateTime tokyo = ZonedDateTime.of(1988, 1, 13, 10, 30, 0, 0, ZoneId.of("Asia/Tokyo"));

ZonedDateTime saoPaulo = ZonedDateTime.of(1988, 1, 13, 10, 30, 0, 0, ZoneId.of("America/Sao_Paulo"));

// compensando o horário de verão
tokyo = tokyo.plusHours(11);

System.out.println(tokyo.isEqual(saoPaulo));

A API possui ainda outros modelos que facilitam bastante o nosso trabalho como as classes MonthDay, Year e YearMonth, por exemplo:

System.out.println("hoje é dia: " + MonthDay.now().getDayOfMonth());

Podemos por exemplo obter a o mês de uma data

LocalDate hoje = LocalDate.now();
YearMonth yearMonth = YearMonth.from(hoje);
System.out.println(yearMonth.getMonth() + " " + yearMonth.getYear());

Enums vs Constantes

Calendar utilizava constantes para representar unidades temporais, a nova API faz isso por meio de enums, como exemplo podemos citar a enum Month, onde cada valor tem um valor inteiro e representa o mes, seguindo o intervalo de 1 (Janeiro), 2 (Fevereiro) até 12 (Dezembro). Você não precisa mas trabalhar com essas enums deixa seu código muito mais legível.

System.out.println(LocalDate.of(1988, 01, 25));
System.out.println(LocalDate.of(1988, Month.JANUARY, 25));

Outra vantagem de se utilizar as enums é a de poder contar com seus métodos auxiliares, por exemplo o firstMonthOfQuarter() para consultar o mês correspondente ao primeiro mês deste trimestre.

System.out.println(Month.NOVEMBER.firstMonthOfQuarter());
System.out.println(Month.JANUARY.plus(2));
System.out.println(Month.JANUARY.minus(1));

Note que ao imprimir o nome de um mês vamos sempre ver o mês em ingles, por exemplo JANUARY, JULY e por aí vai. Para obter o mes em outra configuração podemos contar com o Locales

Locale pt = new Locale("pt");
System.out.println(Month.JANUARY.getDisplayName(TextStyle.FULL, pt));

O argumento TextStyle é uma enum que informa o estilo de formatação:

Os valores possíveis são:

Locale pt = new Locale("pt");

System.out.println(Month.JANUARY.getDisplayName(TextStyle.FULL, pt)); // Janeiro

System.out.println(Month.JANUARY.getDisplayName(TextStyle.FULL_STANDALONE, pt)); // 1

System.out.println(Month.JANUARY.getDisplayName(TextStyle.NARROW, pt)); // J

System.out.println(Month.JANUARY.getDisplayName(TextStyle.NARROW_STANDALONE, pt)); // 1

System.out.println(Month.JANUARY.getDisplayName(TextStyle.SHORT, pt)); // jan

System.out.println(Month.JANUARY.getDisplayName(TextStyle.SHORT_STANDALONE, pt)); // 1

Outro método interessante introduzido na api java.time foi o dayOfWeek() com ele podemos representar facilmente um dia da semana.

Formatando datas

Para formatar datas podemos fazer algo como

LocalDateTime agora = LocalDateTime.now();

String dataFormatada = agora.format(DateTimeFormatter.ISO_LOCAL_TIME);

System.out.println(dataFormatada);

A saída seria algo como 03:12:31.428 ou seja usando o pattern hh:mm:ss.ms

O DateTimeFormatter possui diversas outras opções, mas vamos dizer que tenhamos um formato próprio ou queremos criar algo que não exista ainda, uma das formas seria utilizar o método ofPattern(), que recebe uma String como parâmetro

LocalDateTime agora = LocalDateTime.now();

String dataFormatada = agora.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));

System.out.println(dataFormatada);

Esse método ainda possui uma sobrecarga que além do pattern pode receber um Locale.

Lidando com datas inválidas

Qual a saída desse código ?

Calendar calendar = Calendar.getInstance();
calendar.set(2014, Calendar.FEBRUARY, 30);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
System.out.println(simpleDateFormat.format(calendar.getTime()));

Se você respondeu 30 de Fevereiro de 2014 você errou feio, errou rude ! Porém ao executar o código, veja que não há se quer um alerta sobre o o problema eminente, o Calendar simplesmente ajusta a data para 02/03/2014 e vida que segue ! Nem é preciso dizer que isso poderia causar diversos tipos de prejuízos né ?

Agora tente fazer o mesmo com a nova API de datas

LocalDate.of(2014, Month.FEBRUARY, 30);

E você receberá uma belíssima exception

Exception in thread "main" java.time.DateTimeException: Invalid date 'FEBRUARY 30'
	at java.time.LocalDate.create(LocalDate.java:431)
	at java.time.LocalDate.of(LocalDate.java:249)
	at br.com.jorgerabellodev.lambadas.datas.Main.main(Main.java:9)

O mesmo acontece com horários

LocalDateTime hour = LocalDate.now().atTime(25, 12);

Esse código produzirá um java.time.DateTimeException

Exception in thread "main" java.time.DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 25
	at java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
	at java.time.temporal.ChronoField.checkValidValue(ChronoField.java:703)
	at java.time.LocalTime.of(LocalTime.java:296)
	at java.time.LocalDate.atTime(LocalDate.java:1724)
	at br.com.jorgerabellodev.lambadas.datas.Main.main(Main.java:10)

Duração e período

Só quem já precisou trabalhar com diferença de alguma medida de tempo no Java até a versão 7 sabe o inferno que era fazer isso, apenas para compartilhar um pouco do sofrimento aqui vai um código que poderia ser utilizado pra tal finalidade caso você tenha tido a sorte de não ter precisado fazer isso até hoje.

 Calendar now = Calendar.getInstance();

Calendar anotherDate = Calendar.getInstance();
anotherDate.set(1988, Calendar.JANUARY, 25);

long difference = now.getTimeInMillis() - anotherDate.getTimeInMillis();

long milliSecondsInADay = 1000 * 60 * 60 * 24;

long dias = difference / milliSecondsInADay;

System.out.println(dias);

O problema é resolvido porém trabalhar com diferença entre datas utilizando milisegundos pode nem sempre ser uma boa ideia !

Com a nova API de datas, podemos fazer a mesma coisa de forma mais segura e e simples

LocalDate now = LocalDate.now();

LocalDate anotherDate = LocalDate.of(1988, Month.JANUARY, 25);

long dias = ChronoUnit.DAYS.between(anotherDate, now);

System.out.println(dias);

A enum ChronoUnit está presente no pacote java.time.temporal e possui uma representação para cada medida de tempo e data, além de vários métodos auxiliares que facilitam extrair informações úteis de datas

LocalDate now = LocalDate.now();

LocalDate anotherDate = LocalDate.of(1988, Month.JANUARY, 25);

long dias = ChronoUnit.DAYS.between(anotherDate, now);

long meses = ChronoUnit.MONTHS.between(anotherDate, now);

long anos = ChronoUnit.YEARS.between(anotherDate, now);

System.out.printf("%s dias, %s meses e %s anos", dias, meses, anos);

Agora se precisarmos obter os dias, meses e anos entre duas datas, podemos utilizar Period, essa classe da API também possui o método between() que recebe duas instâncias de LocalDate

LocalDate now = LocalDate.now();
LocalDate anotherDate = LocalDate.of(1988, Month.JANUARY, 25);

Period period = Period.between(anotherDate, now);

System.out.printf("%s dias, %s meses e %s anos",
            period.getDays(), period.getMonths(), period.getYears());

Pode ser que precisemos criar um período entre horas, minutos e segundos, nesse caso Period não servirá, para essa finalidade vamos utilizar Duration

LocalDateTime now = LocalDateTime.now();

LocalDateTime aFewHours = LocalDateTime.now().plusHours(4);

Duration difference = Duration.between(now, aFewHours);

if (difference.isNegative()) {
    difference = difference.negated();
}

System.out.printf("%s horas, %s minutos, %s segundos",
            difference.toHours(), difference.toMinutes(), difference.getSeconds());

Com esse último artigo, encerro a demonstração das features do Java 8 e espero que você que está lendo, tenha compreendido um pouco melhor essas funcionalidades, além disso espero que você escreve código de forma mais fluída e com menos sofrimento !

Caramba SeuJorge! Eu ainda não tinha tomado nota dessa série fenomenal

Obrigado por compartilhar conhecimento e trazer um pouco do Java para a plantaforma, afinal, o java é como o diabo: "você pode não acreditar nele, mas ele acredita em você!" kkkkkkk

Com certeza irei ler o restante e começando do inicio, parabéns pelo conteudo e continua postando conteúdos assim que soma bastante, principalmente para devs perdidos como eu haha.

Muito bom! Alguns detalhes para complementar:

Os métodos withYear, withMonth, etc, na verdade não modificam a data. As classes do java.time são imutáveis, então estes métodos sempre retornam outra instância com o valor modificado.

Por isso se vc fizer:

LocalDate data = LocalDate.now();
data.withYear(2000);
System.out.println(data);

Não vai mudar o ano para 2000, vai continuar imprimindo a data atual. Isso porque withYear retornou outra instância de LocalDate, que não foi atribuída a nenhuma variável, e portanto "se perdeu".

Para obter a data com ano alterado, deve-se usar o valor retornado:

LocalDate data = LocalDate.now();
LocalDate outra = data.withYear(2000);
System.out.println(outra);

O mesmo vale para os métodos plusXXX e minusXXX, eles sempre retornam outra instância com o resultado.

Outro ponto é que não precisaria ficar chamando now toda hora, então em vez disso:

LocalDate hoje = LocalDate.now();
LocalDate amanha = LocalDate.now().plusDays(1);

Poderia ser isso:

LocalDate hoje = LocalDate.now();
LocalDate amanha = hoje.plusDays(1);

Na maioria dos casos o resultado será o mesmo, mas tem um corner case: o código pode rodar muito próximo da meia-noite, então o primeiro now retorna um dia e o segundo retorna outro. O resultado é que amanha acabará com uma data dois dias à frente de hoje.


Quanto a este exemplo:

LocalDate hoje = LocalDate.now();
YearMonth yearMonth = YearMonth.from(hoje);
System.out.println(yearMonth.getMonth() + " " + yearMonth.getYear());

Se a ideia era apenas obter o mês e ano, não precisaria usar YearMonth, poderia obter diretamente:

LocalDate hoje = LocalDate.now();
System.out.println(hoje.getMonth() + " " + hoje.getYear());

O uso de YearMonth é quando vc precisa apenas desses dois campos (por exemplo, para data de expiração de cartão de crédito, que possui somente ano e mês).


Para formatação, eu tenho preferido usar uuuu em vez de yyyy para o ano. O motivo disto é que yyyy não funciona em caso de datas antes de Cristo. Para a maioria dos casos não faz diferença, pois ambos funcionam, mas nos casos em que faz diferença, o uuuu deve ser usado. Mais detalhes nesta resposta (em inglês).

Quanto a 30 de fevereiro, de fato não dá para criar usando LocalDate.of, mas e se tentarmos fazer o parsing?

DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/MM/uuuu");
LocalDate data = LocalDate.parse("30/02/2020", parser);
System.out.println(data); // 2020-02-29

Tentei fazer o parsing de 30 de fevereiro, e a data foi ajustada para o dia 29. Basicamente, é feito um "arredondamento" para o último dia válido do mês (lembrando que 2020 é ano bissexto: se não fosse, o ajuste seria feito para o dia 28).

Se a ideia é não aceitar datas inválidas e não fazer tal ajuste, basta mudar o ResolverStyle:

DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/MM/uuuu")
    .withResolverStyle(ResolverStyle.STRICT);
LocalDate data = LocalDate.parse("30/02/2020", parser);

Agora dá erro, porque a data é inválida:

java.time.format.DateTimeParseException: Text '30/02/2020' could not be parsed: Invalid date 'FEBRUARY 30'

Basicamente, existem 3 modos diferentes de tratar datas inválidas:

O modo LENIENT permite datas inválidas e faz ajustes automáticos. Por exemplo, 31/06/2017 é ajustado para 01/07/2017. Além disso, este modo aceita valores fora dos limites definidos para cada campo, como o dia 32, mês 15, etc. Por exemplo, 32/15/2017 é ajustado para 01/04/2018.

O modo SMART também faz alguns ajustes quando a data é inválida, então 31/06/2017 é interpretado como 30/06/2017. A diferença para LENIENT é que este modo não aceita valores fora dos limites dos campos (mês 15, dia 32, etc), então 32/15/2017 dá erro (lança um DateTimeParseException). É o modo default quando você cria um DateTimeFormatter.

O modo STRICT é o mais restrito: não aceita valores fora dos limites e nem faz ajustes quando a data é inválida, portanto 31/06/2017 e 32/15/2017 dão erro (lançam um DateTimeParseException).


Sobre o exemplo de Duration, só tem um pequeno detalhe na hora de mostrar os dados. Considere este exemplo:

// duração de 10 horas, 35 minutos e 20 segundos
Duration difference = Duration.ofHours(10).plusMinutes(35).plusSeconds(20);
System.out.printf("%s horas, %s minutos, %s segundos",
    difference.toHours(), difference.toMinutes(), difference.getSeconds());

A saída deveria ser "10 horas, 35 minutos e 20 segundos", mas na verdade foi:

10 horas, 635 minutos, 38120 segundos

Isso porque toMinutes retorna a quantidade total de minutos (o mesmo vale para getSeconds). Se quer quebrar em partes, pode usar os métodos toXXXPart, disponíveis a partir do Java 9:

Duration difference = Duration.ofHours(10).plusMinutes(35).plusSeconds(20);

// atenção: toXXXPart só funciona a partir do Java 9
System.out.printf("%s horas, %s minutos, %s segundos",
        difference.toHoursPart(), difference.toMinutesPart(), difference.toSecondsPart());

// para Java 8, tem que fazer na mão
long secs = difference.getSeconds();
long hours = secs / 3600;
secs %= 3600;
long mins = secs / 60;
secs %= 60;
System.out.printf("%s horas, %s minutos, %s segundos", hours, mins, secs);

Leitura complementar:

Graças a deus surgiu essa API, por que antigamente era horrível lidar com datas eu acho (experiência de ter precisaodo usar java velho na disciplina da faculdade)

Só uma duvida que eu fiquei. O que, especificamente é "um ponto no tempo"? Vi citar em muitos artigos que X classe não é um ponto no tempo, enquanto Y é, sendo que ambas, necessariamente, mexem em datas e datas nada mais são do que a abstração de tempo para uma convenção humana.

Fora essa pergunta, a outra seria a diferença entre ChronoUnit e ChronoField, por isso fiz a pergunta anterior, uma mede a quantidade de tempo enquanto a outra é, necessariamente, um ponto no tempo, acabando me confundindo entre ambas.

Excelentes perguntas, vou tentar responder e se ficarem dúvidas por favor me diga. Quanto a "ponto no tempo" quis dizer que são classes que marcam um determinado periodo de tempo em uma linha do tempo, de X a Y e não uma data avulsa, mas confesso que relendo e pensando fiquei intrigado e vou melhorar os textos atualizando eles para ser mais claro a respeito, muito obrigado pelo feedback ^^. Agora quato a ChronoUnit e ChronoField: ChronoUnit é uma enumeração que representa unidades de tempo padrão, como anos, meses, dias, horas, minutos, segundos, entre outros. É usada para realizar cálculos entre datas e períodos, como adicionar ou subtrair uma quantidade específica de unidades de tempo a uma data, por exemplo: ```java LocalDate.now().plus(3, ChronoUnit.MONTHS) ``` Isso adicionará 3 meses à data atual. ChronoField é uma enumeração que representa campos específicos de uma data ou hora, como ano, mês, dia do mês, hora, minuto, segundo, entre outros. É utilizada para acessar e manipular os valores individuais desses campos em uma data ou hora. Por exemplo: ```java LocalDate.now().get(ChronoField.YEAR) ``` Isso retorna o ano atual da data. ChronoUnit é usado para cálculos entre datas, enquanto ChronoField é usado para acessar e manipular componentes específicos da data ou hora.
Opa, muito obrigado pela explicação do que significa um ponto no tempo e uma data, clareou a mente. Um tem conhecimento do todo (a linha do tempo) e o significado de si mesmo neste todo, o outro nem sequer conhece o todo ou se identifica nele. Agora sobre ChronoUnit e ChronoField específicamente, o cálculo em si não seria uma forma de acessar e manipular data ou hora? Fazendo ambos, necessariamente, ter o mesmo objetivo? E referente a nomenclatura "unidade de tempo padrão" e "campos específicos de uma data ou hora" me parecem muito apenas mudar o nome para o que, em resultado, é a mesma coisa, ambos são enumerações que contém anos, meses, dias, horas, etc. Com a adição do ChronoField ter um detalhamento ainda maior tendo os dias no mes, semanas do ano, etc. Ou seja, um fazendo exatamente a mesma coisa que o outro só que com mais enriquecimento de detalhes. (Apesar de parecer uma afirmação, é só uma tentativa de explicar a minha linha de raciocínio que, inclusive, está me confundindo)
Opa boa boa excelentes pontos esses, me fizeram pensar em algumas coisas: De fato ChronoUnit e ChronoField são descritas de forma bem parecida, eu fui dar uma olhada no JavaDoc delas e saca só: ```java /** * A standard set of fields. *

* This set of fields provide field-based access to manipulate a date, time or date-time. * The standard set of fields can be extended by implementing {@link TemporalField}. *

* These fields are intended to be applicable in multiple calendar systems. * For example, most non-ISO calendar systems define dates as a year, month and day, * just with slightly different rules. * The documentation of each field explains how it operates. * * @implSpec * This is a final, immutable and thread-safe enum. * * @since 1.8 */ public enum ChronoField implements TemporalField { ``` ```java /** * A standard set of date periods units. *

* This set of units provide unit-based access to manipulate a date, time or date-time. * The standard set of units can be extended by implementing {@link TemporalUnit}. *

* These units are intended to be applicable in multiple calendar systems. * For example, most non-ISO calendar systems define units of years, months and days, * just with slightly different rules. * The documentation of each unit explains how it operates. * * @implSpec * This is a final, immutable and thread-safe enum. * * @since 1.8 */ public enum ChronoUnit implements TemporalUnit { ``` A `ChronoField` é um "conjunto de campos padrão" e `ChronoUnit` é descrita como "um conjunto padrão de unidades de período de datas". De fato por descrição são praticamente a mesma coisa. Mas vamos imaginar que eu quero somar 10 dias na data atual, eu faria algo mais ou menos assim: ```java LocalDate value = LocalDate.now().plus(10, ChronoUnit.DAYS); ``` Como a assinatura do método `plus()` espera receber um `TemporalAmount` ou um valor inteiro e um `TemporalUnit` então só é possível utilizar `ChronoUnit` que implementa a interface (é um) `TemporalUnit`. Da mesma forma, vamos dizer que eu queira saber em que mês estamos, eu teria de fazer ```java LocalDate.now().get(ChronoField.MONTH_OF_YEAR); ``` Nesse caso o método `get()` espera receber um `TemporalField`, logo não é possível utilizar `ChronoUnit` e por isso utilizamos `ChronoField` que implementa a interface (é um) `TemporalField`. Quanto a semântica, penso o seguinte: **ChronoUnit** Uma unidade deve ser utilizada para medir uma quantidade de tempo - anos, meses, dias, horas, minutos, segundos. Por exemplo, o segundo é uma unidade do S.I. **ChronoField** Por outro lado, os campos são como os humanos geralmente se referem ao tempo, que é em partes. Se você olhar para um relógio digital, os segundos contam de 0 a 59 e depois voltam para 0 novamente. Este é um campo - "segundo do minuto" neste caso, formado pela contagem de segundos dentro de um minuto. Da mesma forma, os dias são contados dentro de um mês e os meses dentro de um ano. Para definir um ponto completo na linha do tempo, você precisa ter um conjunto de campos vinculados, por exemplo: - segundo de minuto - minuto-a-hora - hora do dia - dia do mês - mês do ano - ano (-de-para-sempre) A API ChronoField expõe as duas partes do segundo do minuto.Podemos utilizar `getBaseUnit()` para obter "segundos" e `getRangeUnit()` para obter "minutos". A parte Chrono do nome refere-se ao fato de que as definições são cronologicamente neutras. Especificamente, isso significa que a unidade ou campo tem significado apenas quando associado a um sistema de calendário ou cronologia. Um exemplo disso é a cronologia copta, onde há 13 meses em um ano. Apesar de ser diferente do sistema de calendário civil/ISO comum, a constante ChronoField.MONTH_OF_YEAR ainda pode ser usada. As interfaces TemporalUnit e TemporalField fornecem a abstração de nível mais alto, permitindo que unidades/campos que não são cronologicamente neutros sejam adicionados e processados. Não sei se ficou mais claro ou mais confuso, mas pelo menos acho que eu entendi melhor um pouco sobre essa duas enums e sua utilização. Penso que talvez pudessem ter feito uma única enum pra tudo, porém acredito que tiverem alguns motivos pra ter as duas, sendo o primeiro deles querer separar unidades de campos para que aquele que lê o código possa entender mais rapidamente do que se trata aquele código.