Search code examples
javaloopsjava-streamflux

How to replace a while loop for a stream or a Flux to iterate my elements


I have this code

public String calculateShippingEstimateDate(LocalDateTime initialDate, Integer totalDaysToAdd, SaveOrderSourceData saveOrderSourceData) {

        int workDays = totalDaysToAdd;
        LocalDateTime finalDate = LocalDateTime.of(initialDate.getYear(),
                initialDate.getMonth(),
                initialDate.getDayOfMonth(),
                initialDate.getHour(),
                initialDate.getMinute(),
                initialDate.getSecond());

        while (workDays > 0) {
            finalDate = finalDate.plusDays(1);

            if (!(
                    DateUtility.isWeekend(finalDate.getDayOfWeek())
                            || checkHolidays(saveOrderSourceData, finalDate)
            )) {
                workDays--;
            }
        }
        return finalDate.toString();
    }



    private boolean checkHolidays(SaveOrderSourceData saveOrderSourceData, LocalDateTime finalDate) {
        return saveOrderSourceData.getHolidays()
                .stream().anyMatch(holiday -> getHoliday(holiday).isEqual(finalDate.toLocalDate()));
    }

    private LocalDate getHoliday(Holiday holiday){
        return LocalDate.of(holiday.getYear(),holiday.getMonth(), holiday.getDay());
    }

It's important to know that the estimateDate for this example equals five days after the initialDate.

We have an initialDate(15/02/2022) that equals the finalDate.

We enter into the while loop, it validates if workdays (in this iteration is 5) is mayor to 0, it's false, so finalDate increases its value, and it validates if the date(16/02/2022) is a weekend or a holiday, and so on, when finalDate is equals to (19/02/2022) it doesn't decrease the value of workDays. Finally when the finalDate is (21/02/2022) workDays equals 0, and finalDate is a Monday, so the while loop ends.

Holiday Class

import lombok.Builder;
import lombok.Data;

@Data
@Builder(toBuilder = true)
public class Holiday {
    private final String id;
    private final int year;
    private final int month;
    private final int day;
}

DateUtility Class

import java.time.DayOfWeek;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static java.time.DayOfWeek.SATURDAY;
import static java.time.DayOfWeek.SUNDAY;

public class DateUtility {

    private DateUtility() {
    }

    public static boolean isWeekend(DayOfWeek dayOfWeek) {
        return SATURDAY.equals(dayOfWeek)
                || SUNDAY.equals(dayOfWeek);
    }
}

SaveOrderSourceData class

@Data
@Builder(toBuilder = true)
public class SaveOrderSourceData {

    private  final List<Holiday> holidays;
}

Solution

  • That will be an equivalent of your while loop.

    • iterate() takes initialDate.plusDays(1) as a seed, and spawns new element accordingly to the UnaryOperator date -> date.plusDays(1);
    • filter() - lives only working days in the stream (with assumption that DateUtility and checkHolidays() do there job correctly);
    • limit() - as it's name suggests limit the number of elements in the stream to a given value (note, limit() is applied when weekends or holidays are filtered out);
    • max() - produces the optional result, optional might be empty initialDate and all other days the given range are weekends or holidays and the stream will be empty in this case;
    • map() - is applied on the optional if the value is present;
    • orElse() - provides an alternative value (as well as in your implementation it'll return the initialDate if no further date was found).
        public String calculateShippingEstimateDate(LocalDateTime initialDate, 
                                                    Integer totalDaysToAdd, 
                                                    SaveOrderSourceData saveOrderSourceData) {
    
            return Stream.iterate(initialDate.plusDays(1), date -> date.plusDays(1))
                    .filter(date -> !(DateUtility.isWeekend(date.getDayOfWeek())
                                    || checkHolidays(saveOrderSourceData, date)))
                    .limit(totalDaysToAdd)
                    .max(Comparator.naturalOrder())
                    .map(LocalDateTime::toString)
                    .orElse(initialDate.toString());
        }
    

    I've tested this solution with your utility classes it yields the same result as your imperative implementation

        public static void main(String[] args) {
            System.out.println(new LDTUtil().getShippingDateLoop(LocalDateTime.now(), 5, SaveOrderSourceData.builder().holidays(List.<Holiday>of()).build()));
            System.out.println(new LDTUtil().getShippingDateLoop(LocalDateTime.now(), 5, SaveOrderSourceData.builder().holidays(List.<Holiday>of()).build()) + "\n");
    
            System.out.println(new LDTUtil().getShippingDateLoop(LocalDateTime.now(), 27, SaveOrderSourceData.builder().holidays(List.of(Holiday.builder().id("id1").year(2022).month(3).day(3).build())).build()));
            System.out.println(new LDTUtil().getShippingDateLoop(LocalDateTime.now(), 27, SaveOrderSourceData.builder().holidays(List.of(Holiday.builder().id("id1").year(2022).month(3).day(3).build())).build()));
        }
    

    output

    2022-02-22T21:43:25
    2022-02-22T21:43:25
    
    2022-03-25T21:43:25
    2022-03-25T21:43:25