1

I need to find the next n Fridays but want to exclude 4th and possibly 5th Fridays, i.e I want to get the next n Fridays which are 1st, 2nd or 3rd of each month. I have found a way to stream over the next n Fridays (example next 10 Fridays):

LocalDate now = LocalDate.now();
Stream.iterate(now , localDate -> localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)))
     .skip(1) // skip today 
     .limit(10)
     .forEach(System.out::println);

Output:

2021-10-22
2021-10-29
2021-11-05
2021-11-12
2021-11-19
2021-11-26
2021-12-03
2021-12-10
2021-12-17
2021-12-24

But I need to exclude for example 2021-10-22 & 2021-10-29 because they are 4th and 5th Friday of October. Similarly 2021-11-26 & 2021-12-24 because they are not 1st, 2nd or 3rd Fridays.

I have searched SO and found some ways to calculate nth Friday of a month, for example this one

public static LocalDate nthFridayOfMonth(LocalDate localDate, int week) {
    return localDate.with(TemporalAdjusters.dayOfWeekInMonth(week, DayOfWeek.FRIDAY));
}

which returns for a given date the nth Friday.

How to modify this method so that it returns a boolean if a given LocalDate is a first, second or third Friday of a month? Or use this method to implement another method which does what I need?

Ideally I want to use the stream above and use a method, which returns a boolean as a filter:

LocalDate now = LocalDate.now();
Stream.iterate(now , localDate -> localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)))
     .skip(1) // skip today 
     .filter(d -> is1st2ndOr3rdFriday(d))   
     .limit(10)
     .forEach(System.out::println);

to get

2021-11-05
2021-11-12
2021-11-19
2021-12-03
2021-12-10
2021-12-17
2022-01-07
2022-01-14
2022-01-21
2022-02-04

Appreciate any help on how to implement the filter method boolean is1st2ndOr3rdFriday(LocalDate d) {}

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
wannaBeDev
  • 516
  • 3
  • 14
  • 2
    3×7=21, so if the day of month is over or equal to 22, then it is 4th or 5th Friday. Otherwise, it is 1st, 2nd or 3rd one. Just add a filter before your limit, and you’ll be OK. – Stéphane Veyret Oct 17 '21 at 15:15

3 Answers3

6

You can get the ALIGNED_WEEK_OF_MONTH temporal field of the local date, and check if it is ≤ 3.

Stream.iterate(now , localDate -> localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)))
    .skip(1) // skip today
    .filter(x -> x.get(ChronoField.ALIGNED_WEEK_OF_MONTH) <= 3)
    .limit(10)
    .forEach(System.out::println);
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thank you very much. I have tested your solution and have one problem. It includes the `2021-10-22`. I think I need to configure on which day my week starts and how many days at least a week has. According to the docs the return value of `WeekFields.ISO.weekOfMonth()` depends on `getFirstDayOfWeek()` and `getMinimalDaysInFirstWeek()`. My week starts on Monday not sure about minimal days in a week. Frist Friday should be in first week. – wannaBeDev Oct 17 '21 at 15:30
  • 1
    @wannaBeDev Ah, if that is how you count Fridays, then it might be better to use `ChronoField.ALIGNED_WEEK_OF_MONTH`. rather than `WeekFields.ISO.weekOfMonth()`. – Sweeper Oct 17 '21 at 15:37
  • Perfect. Thank you again. – wannaBeDev Oct 17 '21 at 15:43
2

Another option: iterate months

For the sake of variation here’s a different idea. I am creating a stream of months and explicitly taking the first 3 Fridays from each month.

    LocalDate today = LocalDate.now(ZoneId.systemDefault());
    YearMonth thisMonth = YearMonth.from(today);
    List<LocalDate> fridays = Stream.iterate(thisMonth, ym -> ym.plusMonths(1))
            .flatMap(ym -> Stream.of(fridayOfMonth(ym, 1), fridayOfMonth(ym, 2), fridayOfMonth(ym, 3)))
            .filter(date -> date.isAfter(today))
            .limit(10)
            .collect(Collectors.toList());
    System.out.println(fridays);

Output when run today:

[2021-11-05, 2021-11-12, 2021-11-19, 2021-12-03, 2021-12-10, 2021-12-17, 2022-01-07, 2022-01-14, 2022-01-21, 2022-02-04]

I have used the following auxiliary method to find the nth Friday of a month:

private static LocalDate fridayOfMonth(YearMonth ym, int no) {
    return ym.atDay(15).with(DayOfWeek.FRIDAY).with(ChronoField.ALIGNED_WEEK_OF_MONTH, no);
}

A variation: Still for the sake of variation, here’s different way of creating the stream of the first three Fridays of a month:

    List<LocalDate> fridays = Stream.iterate(thisMonth, ym -> ym.plusMonths(1))
            .flatMap(MyClass::firstThreeFridays)
            .filter(date -> date.isAfter(today))
            .limit(10)
            .collect(Collectors.toList());

This time my auxiliary method goes like this:

private static Stream<LocalDate> firstThreeFridays(YearMonth ym) {
    LocalDate firstFriday = ym.atDay(1).with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY));
    return Stream.of(firstFriday, firstFriday.plusWeeks(1), firstFriday.plusWeeks(2));
}

The result is the same as before.

(The idea of iterating months was also in a now deleted answer by Basil Bourque.)

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

Another solution : you can GroupBy month your dates ( to Map<Month, List< LocalDate > > ) , then for every month key if there are more than 3 date , remove it , and return the result :

public static void main(String[] args) {
    // initialize your date here:
    LocalDate now = LocalDate.now();
    // Grouping Dates By Month 
    Map<Month, List<LocalDate>> map = Stream
            .iterate(LocalDate.of(now.getYear(), now.getMonth(), 1),
                    localDate -> localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)))
            .limit(20).collect(Collectors.groupingBy(element -> element.getMonth()));
    // delete the 4 or 5 fridays
    map.forEach((k, v) -> {
        if (v.size() > 3)
            v.removeIf(e -> e.isBefore(now) || v.indexOf(e) > 2);
    });
    // print results sorted
    Stream.of(map.values()).flatMap(e -> e.stream()).flatMap(List::stream).sorted().limit(10)
            .forEach(System.out::println);
}

Output

2021-11-05

2021-11-12

2021-11-19

2021-12-03

2021-12-10

2021-12-17

2022-01-07

2022-01-14

2022-01-21

2022-02-04

Oussama ZAGHDOUD
  • 1,767
  • 5
  • 15
  • Thank you for your help. But I need to exclude 4th and 5th Fridays of a month. For example in October `2021-10-22` & `2021-10-29` shouldn't be included in my result. That is I need the first three Fridays and not any three Fridays. – wannaBeDev Oct 17 '21 at 15:33
  • @wannaBeDev Sorry , forgot to deal with the current month , code updated – Oussama ZAGHDOUD Oct 17 '21 at 16:03