How to generate a value containing the" last Friday of the month " using Java LocalDate?

I am receiving data from a CSV file with some data, and one of these data contains random dates in the following format: mm/YYYY.

I have a parameter private LocalDate date. And from the generated date, I would like to create one with the last Friday of the month.

I would generate it change that value within setDate():

public void setDate(LocalDate date) {
    this.date = date; // Aqui seria alterado para a ultima sexta-feira do mês.
}

For example: 01/2019.

The last Friday of this month was Day 25/01/2019.

Author: hkotsubo, 2019-02-16

1 answers

You can use the class java.time.temporal.TemporalAdjusters, who owns the method lastInMonth, passing as a parameter a java.time.DayOfWeek corresponding to Friday. Ex:

// alguma data em janeiro (1 de janeiro de 2019)
LocalDate data = LocalDate.of(2019, 1, 1);

// ajustar para a última sexta-feira do mês
LocalDate ultimaSexta = data.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println(ultimaSexta); // 2019-01-25

An important point is that API classes java.time are immutable . This means that methods that modify some information (such as the with above) always return another instance, so do not forget to save the returned value in some variable. Sixth want, you can use even the same:

// data passa a ser a última sexta-feira do mês
data = data.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));

But notice that LocalDate it requires one to have the day, month and year. Since in your CSV there is only month and year, it is not possible to create a LocalDate directly (unless you artificially put some value for the day).


An alternative is to do parsing of your string in the format "mm / yyyy" for a java.time.YearMonth, which is a class that has only the month and year. And to turn a String into YearMonth, we use a java.time.format.DateTimeFormatter:

import static java.time.DayOfWeek.FRIDAY;
import static java.time.temporal.TemporalAdjusters.lastInMonth;

// String no formato mm/yyyy
String s = "01/2019"; // janeiro de 2019
// definir o formato para mês/ano (MM/yyyy)
DateTimeFormatter parser = DateTimeFormatter.ofPattern("MM/yyyy");
YearMonth mesAno = YearMonth.parse(s, parser);

// setar um dia arbitrário (1) e depois ajustar para a última sexta-feira do mês
LocalDate ultimaSexta = mesAno.atDay(1).with(lastInMonth(FRIDAY));
System.out.println(ultimaSexta); // 2019-01-25

First I create the DateTimeFormatter indicating the format of the String (in the case, "month/year"). Be careful to use uppercase and lowercase M, as this makes a difference (see in the documentation that lowercase and uppercase m represent different fields, and if you use them incorrectly, will not work ).

Then I use the method YearMonth.parse to create the corresponding YearMonth. This class only has the month and year, without any information about the day. In the case, the obtained instance corresponds to January 2019.

Then I set an arbitrary day (using the atDay), to create a LocalDate. In the case, I used the day 1, since it is a more guaranteed value that all months have (if I use the day 31, for example, not all months have and this can give error ).

Having the LocalDate, I can use with(lastInMonth(FRIDAY)) (notice that I used import static to leave the code a little more succinct and readable). The result is the last Friday of the month.


It may seem redundant to set the day to 1 and only then adjust to the last Friday of the month, but if I use lastInMonth directly on YearMonth, it gives error:

mesAno.with(lastInMonth(FRIDAY));

This code throws an exception:

Java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: dayofmonth

This happens because lastInMonth modifies the day of the month, but the class YearMonth does not have no information about the day (only about the month and year, hence the error of "Unsupported field" - "unsupported field"). So it is necessary to turn it into a LocalDate before.


Alternative

If you do not want to create a YearMonth, you can create a LocalDate directly. For this, just set an arbitrary day (as we did above with Day 1) and use a java.time.format.DateTimeFormatterBuilder:

DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // formato "mês/ano"
    .appendPattern("MM/yyyy")
    // definir o valor default para o dia do mês = 1
    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
    // cria o DateTimeFormatter
    .toFormatter();
// faz o parse diretamente para LocalDate (o dia é setado para 1)
LocalDate data = LocalDate.parse("01/2019", parser);

// ajusta para a última sexta-feira do mês
LocalDate ultimaSexta = data.with(lastInMonth(FRIDAY));
System.out.println(ultimaSexta); // 2019-01-25

We first define the format (the same used in the previous example: month / year - > MM/yyyy). Then we use the method parseDefaulting, which serves to set the value that a field will have, if it is not present.

To set which field should have the value default , I used a java.time.temporal.ChronoField, which is a enum that has several predefined constants for the most common date and time fields. Since I want to set a value default for the day of the month , I used the field DAY_OF_MONTH. Then I I set the value that this field will have: in the case, it is 1.

So just use the method LocalDate.parse and directly get a LocalDate. The month and year will have the values that are in String, while the day will be 1. After that, just adjust to the last Friday of the month, in the same way as is done in the previous examples, with with(lastInMonth(FRIDAY)).

 3
Author: hkotsubo, 2019-02-17 13:23:57