Как я могу добавить рабочие дни к текущей дате в Java? - PullRequest
13 голосов
/ 08 октября 2009

Как добавить рабочие дни к текущей дате в Java?

public Calendar addBusinessDate(Calendar cal, int days) {
//
// code goes over here
//
}

Примечание:

Следует учитывать и выходные.

Ответы [ 14 ]

31 голосов
/ 08 октября 2009

Возможно, вы захотите использовать ObjectLab Kit , чтобы сделать тяжелую работу за вас.

Предполагается, что требование просто вернуть следующий рабочий день, когда вычисленная дата выпадает на нерабочий день:

package bizdays.example;

import java.time.LocalDate;
import java.util.HashSet;
import net.objectlab.kit.datecalc.common.DateCalculator;
import net.objectlab.kit.datecalc.common.DefaultHolidayCalendar;
import net.objectlab.kit.datecalc.common.HolidayHandlerType;
import net.objectlab.kit.datecalc.jdk8.LocalDateKitCalculatorsFactory;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;

public class BizDayTest {
    private DateCalculator<LocalDate> dateCalculator;
    private final LocalDate startDate = LocalDate.of(2009, 12, 23);

    @Before
    public void setUp() {
        HashSet<LocalDate> holidays = new HashSet<LocalDate>();
        holidays.add(LocalDate.of(2009, 12, 25));  // Friday

        DefaultHolidayCalendar<LocalDate> holidayCalendar =
            new DefaultHolidayCalendar<LocalDate>(holidays);

        LocalDateKitCalculatorsFactory.getDefaultInstance()
                .registerHolidays("example", holidayCalendar);
        dateCalculator = LocalDateKitCalculatorsFactory.getDefaultInstance()
                .getDateCalculator("example", HolidayHandlerType.FORWARD);
        dateCalculator.setStartDate(startDate);
    }

    @Test
    public void should_not_change_calendar_start_date_even_after_moving() {
        assertThat(
            dateCalculator.moveByBusinessDays(6).getStartDate(),
            equalTo(startDate));
    }

    @Test
    public void moveByBusinessDays_will_return_24_dec_2009_as_next_business_day() {
        assertThat(
            dateCalculator.moveByBusinessDays(1).getCurrentBusinessDate(),
            equalTo(LocalDate.of(2009, 12, 24)));
    }

    @Test
    public void moveByBusinessDays_will_return_28_dec_2009_as_two_business_days_later() {
        assertThat(
            dateCalculator.moveByBusinessDays(2).getCurrentBusinessDate(),
            equalTo(LocalDat.of(2009, 12, 28)));

    }

    @Test
    public void moveByDays_will_also_return_28_dec_2009_as_two_business_days_later() {
        assertThat(
            dateCalculator.moveByDays(2).getCurrentBusinessDate(),
            equalTo(LocalDate.of(2009, 12, 28)));
    }

    @Test
    public void moveByBusinessDays_will_exclude_25_26_and_27_dec_when_computing_business_days() {
        assertThat(
            dateCalculator.moveByBusinessDays(5).getCurrentBusinessDate(),
            equalTo(LocalDate.of(2009, 12, 31)));
    }


    @Test
    public void moveByDays_will_include_25_26_and_27_dec_when_computing_business_days() {
        assertThat(
            dateCalculator.moveByDays(5).getCurrentBusinessDate(),
            equalTo(LocalDate.of(2009, 12, 28)));
    }
}

По умолчанию в библиотеке используется рабочая неделя с понедельника по пятницу, но вы можете изменить значения по умолчанию, указав пользовательские значения WorkingWeek на DateCalculator setWorkingWeek().

Как показано в последних двух примерах, moveByDays() включает выходные дни при переносе дней, тогда как moveByBusinessDays() исключает выходные.

Библиотека также позволяет использовать java.util.Calendar или Joda Time '* LocalDate. В примерах используется java.time.LocalDate JDK8, потому что это предпочтительный способ, начиная с JDK8.

Редактировать: обновлены примеры использования java.time.LocalDate

5 голосов
/ 08 октября 2009

Вот модифицированная версия для вычисления даты.

public  Calendar algorithm2(int businessDays){
    Calendar cal2 = Calendar.getInstance();
    Calendar cal = Calendar.getInstance(); 
    int totalDays= businessDays/5*7;
    int remainder = businessDays % 5;
    cal2.add(cal2.DATE, totalDays); 

    switch(cal.get(Calendar.DAY_OF_WEEK)){
        case 1:
                break;
        case 2: 
                break;
        case 3: 
                if(remainder >3)
                cal2.add(cal2.DATE,2);
                break;
        case 4: 
                if(remainder >2)
                cal2.add(cal2.DATE,2);
                break;
        case 5: 
                if(remainder >1)
                cal2.add(cal2.DATE,2);
                break;
        case 6: 
                if(remainder >1)
                cal2.add(cal2.DATE,2);
                break;
        case 7: 
                if(remainder >1)
                cal2.add(cal2.DATE,1);
                break;
    }

    cal2.add(cal2.DATE, remainder); 
    return cal2;

}
5 голосов
/ 08 октября 2009

Использование:

public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
  int numNonBusinessDays = 0;

  for(int i = 0; i < numBusinessDays; i++) {
    cal.add(Calendar.DATE, 1);

    /*
       It's a Canadian/American custom to get the Monday (sometimes Friday) off
       when a holiday falls on a weekend.
    */
    for(int j = 0; j < holidays; j++) { //holidays is list of dates
      if(cal.getTime() == (Date)holidays.get(j)) {
        numNonBusinessDays++;
      }
    }

    if(cal.get(Calendar.DAY_OF_WEEK) == 1 ||
       cal.get(Calendar.DAY_OF_WEEK) == 7) {
      numNonBusinessDays++;
    }
  }

  if(numNonBusinessDays > 0) {
    cal.add(Calendar.DATE, numNonBusinessDays);
  }

  return cal;
}

Вы должны заполнить список дат, чтобы справиться с праздниками. Есть общие, такие как Новый год, но День благодарения отличается между Канадой и США, например. Также учтите, что выходные могут приходиться на выходные, поэтому выходные становятся 3-дневными выходными.

Справка:

PS: на самом деле нет необходимости возвращать экземпляр Calendar, если вы обновляете значение, как в примере. Но это действительно, если вы хотите создать отдельный экземпляр Calendar, используйте:

public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
  Calendar cal2 = Calendar.getInstance();
  cal2.setTime(cal.getTime());

  int numNonBusinessDays = 0;

  for(int i = 0; i < numBusinessDays; i++) {
    cal2.add(Calendar.DATE, 1);

    /*
       It's a Canadian/American custom to get the Monday (sometimes Friday) off
       when a holiday falls on a weekend.
    */
    for(int j = 0; j < holidays; j++) { //holidays is list of dates
      if(cal2.getTime() == (Date)holidays.get(j)) {
        numNonBusinessDays++;
      }
    }

    if(cal2.get(Calendar.DAY_OF_WEEK) == 1 ||
       cal2.get(Calendar.DAY_OF_WEEK) == 7) {
      numNonBusinessDays++;
    }
  }

  if(numNonBusinessDays > 0) {
    cal2.add(Calendar.DATE, numNonBusinessDays);
  }

  return cal2;
}
3 голосов
/ 06 ноября 2013

// также поддерживает отрицательные числа.

private Calendar addBusinessDay(final Calendar cal, final Integer numBusinessDays)
{
    if (cal == null || numBusinessDays == null || numBusinessDays.intValue() == 0)
    {
        return cal;
    }
    final int numDays = Math.abs(numBusinessDays.intValue());
    final int dateAddition = numBusinessDays.intValue() < 0 ? -1 : 1;//if numBusinessDays is negative
    int businessDayCount = 0;
    while (businessDayCount < numDays)
    {
        cal.add(Calendar.DATE, dateAddition);

        //check weekend
        if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
        {
            continue;//adds another day
        }

        //check holiday
        if (isHoliday(cal))//implement isHoliday yourself
        {
            continue;//adds another day
        }

        businessDayCount++;
    }
    return cal;
}
3 голосов
/ 24 мая 2012
public static Date addBusinessDays(Date date, int days) {

    DateTime result = new DateTime(date);
    result = isWeekEnd(result)
        ? getPreviousBusinessDate(result)
        : result;

    for (int i = 0; i < days; i++) {
        if (isWeekEnd(result)) {
            i--;
        }
        result = result.plusDays(1);
    }
    return result.toDate();
}

private static boolean isWeekEnd(DateTime dateTime) {
    int dayOfWeek = dateTime.getDayOfWeek();
    return dayOfWeek == DateTimeConstants.SATURDAY || dayOfWeek == DateTimeConstants.SUNDAY;
}

private static DateTime getPreviousBusinessDate(DateTime result) {
    while (isWeekEnd(result)) {
        result = result.minusDays(1);
    }

    return result;
}
2 голосов
/ 15 февраля 2017

ТЛ; др

Идем вперед.

myLocalDate.with( 
    org.threeten.extra.Temporals.nextWorkingDay() 
)

Возвращаясь назад.

myLocalDate.with( 
    org.threeten.extra.Temporals.previousWorkingDay() 
)

Использование java.time

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

Также см. мой ответ на аналогичный вопрос.

TemporalAdjuster

В java.time интерфейс TemporalAdjuster предоставляет классам возможность манипулировать значениями времени и даты. Используя неизменяемые объекты , создается новый экземпляр со значениями, основанными на оригинале.

nextWorkingDay

Проект ThreeTen-Extra расширяет java.time дополнительными функциями. Это включает в себя регулятор nextWorkingDay, который пропускает субботу и воскресенье. Таким образом, мы можем циклически увеличивать дату по одному за раз и пропускать любые выходные дни.

Класс LocalDate представляет значение только для даты без времени суток и без часового пояса.

LocalDate start = LocalDate.now( ZoneId.of( "America/Montreal" ) ) ;
int businessDaysToAdd = 13 ;
// … ensure that: ( businessDaysToAdd >= 0 )

int daysLeft = businessDaysToAdd ;
LocalDate localDate = start ;
while ( daysLeft > 0 ) {
    localDate = localDate.with( Temporals.nextWorkingDay() );
    daysLeft = ( daysLeft - 1 ) ;  // Decrement as we go.
}
return localDate ;

Праздники

Праздники - это совсем другое дело. Очевидно, что нет простого решения. Вы должны либо предоставить список ваших почитаемых праздников, либо получить список, с которым вы согласны.

Если у вас есть такой список, я предлагаю написать собственную реализацию TemporalAdjuster, аналогичную nextWorkingDay.


О java.time

Фреймворк java.time встроен в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует перейти на java.time классы.

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Где получить классы java.time?

  • Java SE 8 и SE 9 и более поздние
    • Встроенный.
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport .
  • Android

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и more .

2 голосов
/ 23 декабря 2009

Будет ли это работать? Конечно, это не обработка праздников.

публичная статическая дата addBusinessDays (Date baseDate, int NumberOfDays) {

    if(baseDate == null){
        baseDate = new Date();
    }

    Calendar baseDateCal = Calendar.getInstance();
    baseDateCal.setTime(baseDate);

    for(int i = 0; i < numberOfDays; i++){

        baseDateCal.add(Calendar.DATE,1);
        if(baseDateCal.get(Calendar.DAY_OF_WEEK)
                         == Calendar.SATURDAY){
           baseDateCal.add(Calendar.DATE,2);
        }
    }
    return baseDateCal.getTime();
}
1 голос
/ 04 июля 2018

Это метод, который я придумал:

    private Date addLaborDays(Integer days, Date date){
        Collection<Date> holidaysList = getHolidays();

        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DATE, 1);
        Date dateTemp = cal.getTime();

        if(days == 1) return dateTemp; 
        if(holidaysList.contains(dateTemp) || DateUtil.isWeekend(dateTemp)){
            return addLaborDays(days, dateTemp);
        } else {
            return addLaborDays(days-1, dateTemp);
        }
    }

Метод getHolidays() запрашивает таблицу базы данных пользовательских праздников, а метод DateUtil.isWeekend(dateTemp) возвращает значение true, если dateTemp - суббота или воскресенье.

1 голос
/ 01 октября 2015

O (1) версия , которая работает и поддерживает различные шаблоны выходных и отрицательных дней:

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

//Weekend patterns
public static  final int WEEKEND_SAT_SUN = 0;
public static  final int WEEKEND_FRI_SAT = 1;
public static  final int WEEKEND_THU_FRI = 2;
public static  final int WEEKEND_FRI_SUN = 3;
public static  final int WEEKEND_FRI = 4;
public static  final int WEEKEND_SAT = 5;
public static  final int WEEKEND_SUN = 6;


//Weekend pattern by country 
//@see https://en.wikipedia.org/wiki/Workweek_and_weekend
public static Map<String,Integer> weekendPatternByCountry = new HashMap<>();
static {
    weekendPatternByCountry.put("CO",WEEKEND_SUN);     //Colombia
    weekendPatternByCountry.put("GQ",WEEKEND_SUN);     //Equatorial Guinea
    weekendPatternByCountry.put("IN",WEEKEND_SUN);     //India
    weekendPatternByCountry.put("MX",WEEKEND_SUN);     //Mexico
    weekendPatternByCountry.put("KP",WEEKEND_SUN);     //North Korea
    weekendPatternByCountry.put("UG",WEEKEND_SUN);     //Uganda
    weekendPatternByCountry.put("BN",WEEKEND_FRI_SUN); //Brunei Darussalam
    weekendPatternByCountry.put("DJ",WEEKEND_FRI);     //Djibouti
    weekendPatternByCountry.put("IR",WEEKEND_FRI);     //Iran
    weekendPatternByCountry.put("AF",WEEKEND_THU_FRI); //Afghanistan
    weekendPatternByCountry.put("NP",WEEKEND_SAT);     //Nepal
    weekendPatternByCountry.put("DZ",WEEKEND_FRI_SAT); //Algeria
    weekendPatternByCountry.put("BH",WEEKEND_FRI_SAT); //Bahrain
    weekendPatternByCountry.put("BD",WEEKEND_FRI_SAT); //Bangladesh
    weekendPatternByCountry.put("EG",WEEKEND_FRI_SAT); //Egypt
    weekendPatternByCountry.put("IQ",WEEKEND_FRI_SAT); //Iraq
    weekendPatternByCountry.put("IL",WEEKEND_FRI_SAT); //Israel
    weekendPatternByCountry.put("JO",WEEKEND_FRI_SAT); //Jordan
    weekendPatternByCountry.put("KW",WEEKEND_FRI_SAT); //Kuwait
    weekendPatternByCountry.put("LY",WEEKEND_FRI_SAT); //Libya
    weekendPatternByCountry.put("MV",WEEKEND_FRI_SAT); //Maldives
    weekendPatternByCountry.put("MR",WEEKEND_FRI_SAT); //Mauritania
    weekendPatternByCountry.put("MY",WEEKEND_FRI_SAT); //Malaysia
    weekendPatternByCountry.put("OM",WEEKEND_FRI_SAT); //Oman
    weekendPatternByCountry.put("PS",WEEKEND_FRI_SAT); //Palestine
    weekendPatternByCountry.put("QA",WEEKEND_FRI_SAT); //Qatar
    weekendPatternByCountry.put("SA",WEEKEND_FRI_SAT); //Saudi Arabia
    weekendPatternByCountry.put("SD",WEEKEND_FRI_SAT); //Sudan
    weekendPatternByCountry.put("SY",WEEKEND_FRI_SAT); //Syria
    weekendPatternByCountry.put("AE",WEEKEND_FRI_SAT); //United Arab Emirates
    weekendPatternByCountry.put("YE",WEEKEND_FRI_SAT); //Yemen
}

//Adjustment vectors - precomputed adjustment
static int[][][] adjVector = new int[][][]{
    {//WEEKEND_SAT_SUN
        //Positive number of days
        {1,0,-1,-2,-3,1,1},
        {0,0},
        {0,0,0,0,0,2,1},
        //Negative number of days
        {-1,3,2,1,0,-1,-1},
        {0,0},
        {-1,1,1,1,1,1,0}
    },
    {//WEEKEND_FRI_SAT
        //Positive number of days
        {0,-1,-2,-3,1,1,1},
        {0,0},
        {0,0,0,0,2,1,0},
        //Negative number of days
        {3,2,1,0,-1,-1,-1},
        {0,0},
        {1,1,1,1,1,0,-1}
    },
    {//WEEKEND_THU_FRI
        //Positive number of days
        {-1,-2,-3,1,1,1,0},
        {0,0},
        {0,0,0,2,1,0,0},
        //Negative number of days
        {2,1,0,-1,-1,-1,3},
        {0,0},
        {1,1,1,1,0,-1,1}
    },
    {//WEEKEND_FRI_SUN
        //Positive number of days
        {0,-1,-2,-3,-4,-4,0},
        {1,0},
        {0,0,0,0,0,-1,1},
        //Negative number of days
        {4,3,2,1,0,0,4},
        {0,-1},
        {1,1,1,1,1,0,2}
    },
    {//WEEKEND_FRI
        //Positive number of days
        {-1,-2,-3,-4,1,1,0},
        {0},
        {0,0,0,0,1,0,0},
        //Negative number of days
        {3,2,1,0,-1,-1,4},
        {0},
        {1,1,1,1,1,0,1}
    },
    {//WEEKEND_SAT
        //Positive number of days
        {0,-1,-2,-3,-4,1,1},
        {0},
        {0,0,0,0,0,1,0},
        //Negative number of days
        {4,3,2,1,0,-1,-1},
        {0},
        {1,1,1,1,1,1,0}
    },
    {//WEEKEND_SUN
        //Positive number of days
        {1,0,-1,-2,-3,-4,1},
        {0},
        {0,0,0,0,0,0,1},
        //Negative number of days
        {-1,4,3,2,1,0,-1},
        {0},
        {0,1,1,1,1,1,1}
    }
};

//O(1) algorithm to add business days.
public static Date addBusinessDays(Date day, int days,int weekendPattern){
    Calendar ret = Calendar.getInstance();
    if(day != null) {
        ret.setTime(day);
    }
    if(days != 0) {
        int startDayofWeek  = ret.get(Calendar.DAY_OF_WEEK)-1; //Zero based to use the vectors bellow.
        int idx = days > 0 ? 0 : 3;
        int howManyWeekendDays = 0;
        int[][] adjV = adjVector[weekendPattern];
        int numWeekendDaysInOneWeek = adjV[idx+1].length;

        for(int i  = 0; i < numWeekendDaysInOneWeek;i++){
            int adjustmentA = adjV[idx][startDayofWeek];  //pattern shift
            int adjustmentB = adjV[idx+1][i];             //day shift

            howManyWeekendDays += (days-adjustmentA-adjustmentB)/(7-numWeekendDaysInOneWeek); 
        }

        int adjustmentC = adjV[idx+2][startDayofWeek]; //f(0) adjustment
        howManyWeekendDays += adjustmentC;

        ret.add(Calendar.DATE,days + howManyWeekendDays);

        //TODO: Extend to support holidays using recursion
        // int numHolidays = getNumHolidaysInInterval(day,ret.getTime());
        // if(numHolidays > 0) return addBusinessDays(ret.getTime,numHolidays);
    }
    return ret.getTime();
}

public static Date addBusinessDays(Date day, int days,String country){
    Integer weekpat = weekendPatternByCountry.get(country);     
    return weekpat != null ? addBusinessDays(day,days,weekpat) : addBusinessDays(day,days,WEEKEND_SAT_SUN);
}
}
1 голос
/ 23 декабря 2009

Этот алгоритм вычисляет следующую рабочую дату для данной даты (рабочие дни с понедельника по пятницу в моей стране) , вы можете адаптировать его для повторения количества дней, которое нужно добавить.

public Calendar nextBusinessDate(Calendar cal) {

    List<Calendar> holidays = ********
    // Here get list of holidays from DB or some other service...

    GregorianCalendar calCp = new GregorianCalendar();
    calCp.setTime(cal.getTime());
    calCp.add(Calendar.DAY_OF_MONTH, 1);

    boolean isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
    boolean isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
    boolean isHoliday = holidays.contains(calCp);

    while (isSaturday || isSunday || isHoliday) {
      if (isSaturday) {
          calCp.add(Calendar.DAY_OF_MONTH, +2); // is saturday, make it monday
        } else {
        if (isSunday) {
            calCp.add(Calendar.DAY_OF_MONTH, +1); // is sunday, make it monday
        } else {
            if (isHoliday) {
                 calCp.add(Calendar.DAY_OF_MONTH, +1); // is holiday, make it next day
               }
            }
        }
      calCp = new GregorianCalendar();
      calCp.setTime(cal.getTime());
      isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
      isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
    isHoliday = holidays.contains(calCp);
    } // end while

    return calCp;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...