LocalDateTime Java - Get same day of week for next month

≯℡__Kan透↙ 提交于 2020-06-01 07:50:06

问题


I have a date and a time of a month, for example 31/01/2020 at 14:00:00, this is the last friday of January. How can I get the date for the last Friday of Feb, March, etc.? It should be dynamic because any date can come in, like the second Tuesday of any month and so on.

I am trying with the following with no luck:

LocalDateTime startTime = LocalDateTime.of(2020, 1, 31, 14, 0, 0);

final Calendar calendar = Calendar.getInstance();
calendar.set(startTime.getYear(), startTime.getMonthValue() - 1, startTime.getDayOfMonth(), startTime.getHour(), startTime.getMinute(), startTime.getSecond());

int ordinal = calendar.get(Calendar.WEEK_OF_MONTH);
startTime = startTime.plusMonths(1).with(TemporalAdjusters.dayOfWeekInMonth(ordinal, startTime.getDayOfWeek();
System.out.println(startTime);

it's printing 06/03/2020 (six of march) at 14:00:00 which is wrong and should be 28/02/2020

What am I missing?

Thanks!


回答1:


As mentioned before, there is some ambiguity in which day of the week of the month you mean, that is, whether you mean the nth day of week or the last nth day of week of the month.

One such example is Monday, February 24th, 2020. It is the fourth and last Monday of February 2020. If you are going to try to determine this for March 2020, which Monday would you pick? The fourth Monday is 23 March, but the last Monday is 30 March.

So apparently, you'll need to distinguish between whether you count forward or backward.

You could, for instance, create a class which represents a certain day of week in a month. This holds three fields: a day-of-week, a position, and whether the position is backwards or not. E.g.

  • "The second Monday of the month" would have

    dayOfWeek = DayOfWeek.MONDAY
    position = 2
    backwards = false
    

    and

  • "The last Thursday of the month" would have

    dayOfWeek = DayOfWeek.THURSDAY
    position = 1
    backwards = true
    
public class WeekdayInMonth {

    private final boolean backwards;

    private final DayOfWeek dayOfWeek;

    private final int position;

    private WeekdayInMonth(DayOfWeek dayOfWeek, int position, boolean backwards) {
        if (position < 1 || position > 5) {
            throw new DateTimeException("Position in month must be between 1 and 5 inclusive");
        }
        this.dayOfWeek = dayOfWeek;
        this.position = position;
        this.backwards = backwards;
    }
}

We could add factory methods to create WeekdayInMonths from LocalDates:

public static WeekdayInMonth of(LocalDate date) {
    int positionInMonth = (date.getDayOfMonth() - 1) / 7 + 1;
    return new WeekdayInMonth(date.getDayOfWeek(), positionInMonth, false);
}

private static WeekdayInMonth ofReversing(LocalDate date) {
    int lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
    int positionInMonth = (lastDayOfMonth - date.getDayOfMonth()) / 7 + 1;
    return new WeekdayInMonth(date.getDayOfWeek(), positionInMonth, true);
}

At last, we add a method to get a LocalDate from a YearMonth adjusted to the WeekdayInMonth.

public LocalDate toLocalDate(YearMonth yearMonth) {
    // Get a temporal adjuster to adjust a LocalDate to match a day-of-the-week
    TemporalAdjuster adjuster = this.backwards ? TemporalAdjusters.lastInMonth(this.dayOfWeek) : TemporalAdjusters.firstInMonth(this.dayOfWeek);

    int weeks = this.position - 1;
    LocalDate date = yearMonth.atDay(1)
            .with(adjuster)
            .plusWeeks(this.backwards ? 0 - weeks : weeks);
    if (!Objects.equals(yearMonth, YearMonth.from(date))) {
        throw new DateTimeException(String.format("%s #%s in %s does not exist", this.dayOfWeek, this.position, yearMonth));
    }
    return date;
}

Working example

Here a working example at Ideone.


Addendum

I am getting errors like this if the initial date is Jan 1 2020: java.time.DateTimeException: FRIDAY #5 in 2020-02 does not exist. How could I get the previous weekday in case this happens? In this case, how would I get the previous Friday?

Well, then you need to adjust your LocalDate so that it falls within the specified yearmonth. Since every month has at least four day-of-the-weeks and no more than five of them, the difference is never more than a week. We could, after removing the throw new DateTimeException line, simply adjust the returned LocalDate using plusWeeks.

I've forked the abovementioned example and added the toAdjustingLocalDate method.




回答2:


This solution is kind of complicated but this is because "last of" or "third in" etc aren't always well defined and might not even exists under some conditions. So here is a solution that looks at the initial date and depending of the day of the month it either performs calculations from the start of the month, calculating forward, or the end of the month, calculating backwards.

From my testing it seems to generate the right results and I am sure some code refactoring could be done as well to improve the code but I leave that for the reader.

public static LocalDateTime nextWithSameDayOfMonth(LocalDateTime indate) {
    if (indate.getDayOfMonth() < 15) {
        return getForStartOfMonth(indate);
    } 

    return getForEndOfMonth(indate);
}

private static LocalDateTime getForEndOfMonth(LocalDateTime indate) {
    DayOfWeek dayOfWeek = indate.getDayOfWeek();
    LocalDateTime workDate = indate.with(TemporalAdjusters.lastDayOfMonth());
    int count = 0;
    while (workDate.isAfter(indate)) {
        count++;
        workDate = workDate.minusWeeks(1);
    }

    LocalDateTime nextDate = indate.plusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
    while (nextDate.getDayOfWeek() != dayOfWeek) {
        nextDate = nextDate.minusDays(1);
    }

    return count == 0 ? nextDate : nextDate.minusWeeks(count - 1);
}

private static LocalDateTime getForStartOfMonth(LocalDateTime indate) {
    DayOfWeek dayOfWeek = indate.getDayOfWeek();
    LocalDateTime workDate = indate.with(TemporalAdjusters.firstDayOfMonth());
    int count = 0;
    while (workDate.isBefore(indate)) {
        count++;
        workDate = workDate.plusWeeks(1);
    }

    LocalDateTime nextDate = indate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
    while (nextDate.getDayOfWeek() != dayOfWeek) {
        nextDate = nextDate.plusDays(1);
    }

    return count == 0 ? nextDate : nextDate.plusWeeks(count - 1);
}



回答3:


Could you check if the function work for you?

public class FindSameDayNextMonth {
    public static void main(String[] args) {
        System.out.println("Next month of 'today' is " + FindSameDayNextMonth.getSameDayNextMonth());
    }

    public static Date getSameDayNextMonth() {
        LocalDateTime dt = LocalDateTime.now();

        Calendar c = Calendar.getInstance();
        c.set(Calendar.MONTH, dt.getMonthValue()-1);
        c.set(Calendar.DAY_OF_MONTH, dt.getDayOfMonth());
        c.add(Calendar.MONTH, 1);

        return c.getTime();
    }
}

The output is Next month of today is Mon Sep 23 07:18:09 CDT 2019



来源:https://stackoverflow.com/questions/57625854/localdatetime-java-get-same-day-of-week-for-next-month

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!