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:
- Como migrar de Date e Calendar para a nova API de datas no Java 8?
- Por que eu deveria ou não usar a Joda-Time? (Joda-Time é uma API de datas que por muito tempo foi a melhor alternativa para
Date
eCalendar
, e serviu de inspiração para ojava.time
- inclusive, ambas as API's foram feitas pela mesma pessoa) - Livro: Datas e horas - Conceitos fundamentais e as APIs do Java (PITCH: eu que escrevi :-D E até 28 de julho, tem 12% de desconto, usando o cupom
CASADOCODIGO12
)