Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate only working hours between two dates excluding weekends in Java

I need help please, How can I calculate business hours between two dates? For example we have two dates; 01/01/2017 10:00 and 04/01/2017 15:00. And we have working hours 09:30 to 17:30 (8 hours) on weekdays. How can I calculate working hours and minutes with Java?

Thanks for your help.

like image 913
MIDO LOUAFI Avatar asked May 10 '17 22:05

MIDO LOUAFI


2 Answers

everyone! I faced similar problem (calculate working minutes between to TimeStamps) and finished with this solution. Hope, somebody finds it's useful:

public class WorkingMinutesCalculator {
    private static final int WORK_HOUR_START = 9;
    private static final int WORK_HOUR_END = 17;
    private static final long MINUTES = 60;

    private static final long WORKING_HOURS_PER_DAY = WORK_HOUR_END - WORK_HOUR_START;
    private static final long WORKING_MINUTES_PER_DAY = WORKING_HOURS_PER_DAY * MINUTES;

    public int getWorkingMinutesSince(final Timestamp startTime) {
        Timestamp now = Timestamp.from(Instant.now());
        return getWorkingMinutes(startTime, now);
    }

    public int getWorkingMinutes(final Timestamp startTime, final Timestamp endTime) {
        if (null == startTime || null == endTime) {
            throw new IllegalStateException();
        }
        if (endTime.before(startTime)) {
            return 0;
        }

        LocalDateTime from = startTime.toLocalDateTime();
        LocalDateTime to = endTime.toLocalDateTime();

        LocalDate fromDay = from.toLocalDate();
        LocalDate toDay = to.toLocalDate();

        int allDaysBetween = (int) (ChronoUnit.DAYS.between(fromDay, toDay) + 1);
        long allWorkingMinutes = IntStream.range(0, allDaysBetween)
                .filter(i -> isWorkingDay(from.plusDays(i)))
                .count() * WORKING_MINUTES_PER_DAY ;

        // from - working_day_from_start
        long tailRedundantMinutes = 0;
        if (isWorkingDay(from)) {
            if (isWorkingHours(from)) {
                tailRedundantMinutes = Duration.between(fromDay.atTime(WORK_HOUR_START, 0), from).toMinutes();
            } else if (from.getHour() > WORK_HOUR_START) {
                tailRedundantMinutes = WORKING_MINUTES_PER_DAY;
            }
        }

        // working_day_end - to
        long headRedundanMinutes = 0;
        if (isWorkingDay(to)) {
            if (isWorkingHours(to)) {
                headRedundanMinutes = Duration.between(to, toDay.atTime(WORK_HOUR_END, 0)).toMinutes();
            } else if (from.getHour() < WORK_HOUR_START) {
                headRedundanMinutes = WORKING_MINUTES_PER_DAY;
            }
        }
        return (int) (allWorkingMinutes - tailRedundantMinutes - headRedundanMinutes);
    }

    private boolean isWorkingDay(final LocalDateTime time) {
        return time.getDayOfWeek().getValue() < DayOfWeek.SATURDAY.getValue();
    }

    private boolean isWorkingHours(final LocalDateTime time) {
        int hour = time.getHour();
        return WORK_HOUR_START <= hour && hour <= WORK_HOUR_END;
    }
}
like image 62
Anton Kot Avatar answered Oct 19 '22 04:10

Anton Kot


Off the top of my head.

Let me suggest you begin by studying the java.time package with subpackages, the modern Java date and time API. There’s an Oracle tutorial as well as other good reading material on the net, go search.

Next I would separate model (domain, business layer) from (user) interface. I am assuming that 01/01/2017 10:00 is an input string from somewhere. I would count this as interface. In your model you will want to use datetime classes and objects for your data.

Think about whether you need to take time zone and summer time (DST) into account. Since summer time transitions usually happen in the night and not during working hours, I am thinking not, but make your own decision. If you (may) need this, rely on ZonedDateTime objects, otherwise LocalDateTime.

I would start by writing a good unit test for the working hour calculation. You may also want to write one for the conversion from input strings to either LocalDateTime or ZonedDateTime, but no lot of test will be needed here.

I would use a DateTimeFormatter for parsing your two datetime values into LocalDateTime objects. Then .atZone() if you need to convert to ZonedDateTime.

Model/working hour calculation

Inspired from this question I am thinking that it will be convenient to adjust the start and end datetime if they fall outside working hours. For example if the start time is Saturday at 3 AM, move it to Monday at the beginning of the working day. If the end time is Saturday 3 AM, move it to Friday at the end of the working day. You will need methods like getDayOfWeek, toLocalDate, minusDays, plusDays and atTime depending on the exact way you want to do this. You may also want to skip this step, you will see later whether you find it worthwhile. Or maybe only move the time-of-day but not change the day.

Next you need the ChronoUnit enum. Start for example with ChronoUnit.WEEKS.between(startDateTime, endDateTime) to find the number of full weeks in your interval. Multiply by the number of working hours per week. An option that seems attractive is to store the hours into a Duration object since I think we will need one later anyway. Also add the weeks to your start time so you have an interval for the remaining work time (days and hours).

At this point it is getting more complicated since you need to look at whether the days are working days and at the end and start time of the working day. The Duration class may be convenient for summing up the hours, minutes and seconds of each working day from the new start datetime to the end datetime. You may use its between and plus methods. You will probably need to handle the case where the new start datetime and the end datetime fall on the same date specially.

See if this doesn’t get you started.

like image 1
Ole V.V. Avatar answered Oct 19 '22 04:10

Ole V.V.