Рассчитать только рабочее время между двумя датами, исключая выходные в Java

Мне нужна помощь, пожалуйста, как я могу рассчитать рабочие часы между двумя датами? Например, у нас есть две даты; 01.01.2017 10:00 и 04.01.2017 15:00. И у нас есть рабочие часы с 09:30 до 17:30 (8 часов) в будние дни. Как я могу рассчитать рабочие часы и минуты с Java?

Спасибо за вашу помощь.

2 ответа

Каждый! Я столкнулся с подобной проблемой (рассчитать рабочие минуты между метками времени) и закончил с этим решением. Надеюсь, кто-нибудь найдет это полезным:

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;
    }
}

С верхней части моей головы.

Позвольте мне предложить вам начать с изучения java.time пакет с подпакетами, современный Java дата и время API. В сети есть учебник по Oracle, а также другие хорошие материалы для чтения.

Далее я бы отделил модель (домен, бизнес-уровень) от (пользовательского) интерфейса. Я предполагаю что 01/01/2017 10:00 входная строка откуда-то Я бы посчитал это интерфейсом. В вашей модели вы захотите использовать классы datetime и объекты для ваших данных.

Подумайте, нужно ли учитывать часовой пояс и летнее время (DST). Поскольку переходы на летнее время обычно происходят ночью, а не в рабочее время, я думаю, что нет, но принимаю ваше собственное решение. Если вам (может) это нужно, положитесь на ZonedDateTime объекты, в противном случае LocalDateTime,

Я бы начал с написания хорошего модульного теста для расчета рабочего времени. Вы также можете написать один для преобразования из входных строк в любой LocalDateTime или же ZonedDateTime, но здесь не нужно много испытаний.

Я бы использовал DateTimeFormatter для анализа ваших двух значений даты и времени в LocalDateTime объекты. затем .atZone() если вам нужно конвертировать в ZonedDateTime,

Модель / расчет рабочего времени

Вдохновленный этим вопросом, я думаю, что будет удобно настроить время начала и окончания, если они выходят за пределы рабочего времени. Например, если время начала субботы в 3 часа ночи, переместите его в понедельник в начале рабочего дня. Если время окончания субботы 3 часа ночи, переместите его в пятницу в конце рабочего дня. Вам понадобятся такие методы, как getDayOfWeek, toLocalDate, minusDays, plusDays а также atTime в зависимости от того, как именно вы хотите это сделать. Вы также можете пропустить этот шаг, позже вы увидите, считаете ли вы его стоящим. Или, может быть, только перемещать время суток, но не менять день.

Далее вам нужно ChronoUnit ENUM. Начните, например, с ChronoUnit.WEEKS.between(startDateTime, endDateTime) чтобы найти количество полных недель в вашем интервале. Умножьте на количество рабочих часов в неделю. Вариант, который кажется привлекательным, состоит в том, чтобы хранить часы в Duration возражать, так как я думаю, что мы все равно понадобимся позже Также добавьте недели к времени начала, чтобы у вас был интервал для оставшегося рабочего времени (дни и часы).

На данный момент все становится сложнее, так как вам нужно посмотреть, являются ли дни рабочими днями, а также в конце и в начале рабочего дня. Duration Класс может быть удобен для суммирования часов, минут и секунд каждого рабочего дня от новой даты начала времени до даты окончания времени. Вы можете использовать его between а также plus методы. Вероятно, вам нужно будет обработать случай, когда новое время начала и окончания даты специально совпадают с одной датой.

Посмотрите, если это не поможет вам начать.

Другие вопросы по тегам