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

Asked

Viewed 253 times

2

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 her to change that value within the 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 25/01/2019.

1 answer

3


You can use the class java.time.temporal.TemporalAdjusters, that has the method lastInMonth, passing as 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 the API classes java.time sane immutable. This means that methods that modify some information (such as the method with above) always return another instance, so you should not forget to store the returned value in some variable. If you want, you can even use the same:

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

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


An alternative is to do the Parsing of its string in "mm/yyyy" format for a java.time.YearMonth, which is a class that has only the month and the year. And to transform a String in 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 String (in the case, "month/year"). Attention to use M uppercase and y because it makes a difference (see in documentation that m tiny and Y capital represent different fields, and if you use them erroneously, won’t work).

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

Then I die an arbitrary day (using the method atDay), to create a LocalDate. In case, I used the day 1, because it is a more guaranteed value that every month have (if I use the day 31, for example, not every month have and that can make a mistake).

Having the LocalDate, I can use with(lastInMonth(FRIDAY)) (notice that I used import static to make 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 for the last Friday of the month, but if I use lastInMonth directly on YearMonth, makes a mistake:

mesAno.with(lastInMonth(FRIDAY));

This code throws an exception:

java.time.temporal.Unsupportedtemporaltypeexception: Unsupported field: Dayofmonth

This is because lastInMonth changes the day of the month, but the class YearMonth has no information about the day (only about the month and year, hence the error of "Unsupported field" - "Unsupported field"). It is therefore necessary to turn it into a LocalDate before.


Alternative

If you don’t want to create a YearMonth, it is possible to 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

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

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

So just use the method LocalDate.parse and directly obtain 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 that is done in the previous examples, with with(lastInMonth(FRIDAY)).

  • 1

    Too complete! Thank you very much!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.