如何使用Java日历忽略周末?

时间:2013-01-03 08:31:16

标签: java date calendar

我试图通过忽略周末来获得两个特定时间实例之间的分钟数。这就是我所做的。

public static final List<Integer> NON_WORKING_DAYS;
static {
    List<Integer> nonWorkingDays = new ArrayList<Integer>();
    nonWorkingDays.add(Calendar.SATURDAY);
    nonWorkingDays.add(Calendar.SUNDAY);
    NON_WORKING_DAYS = Collections.unmodifiableList(nonWorkingDays);
}

public static int getMinsBetween(Date d1, Date d2, boolean onlyBusinessDays)
{
    int minsBetween = (int)((d2.getTime() - d1.getTime()) / (1000 * 60));
    int minsToSubtract = 0;
    if(onlyBusinessDays){
        Calendar dateToCheck = Calendar.getInstance();
        dateToCheck.setTime(d1);
        Calendar dateToCompare = Calendar.getInstance();
        dateToCompare.setTime(d2);


        //moving the first day of the week to Tues so that a Sat, sun and mon fall in the same week, easy to adjust dates
        dateToCheck.setFirstDayOfWeek(Calendar.TUESDAY);
        dateToCompare.setFirstDayOfWeek(Calendar.TUESDAY);

        //moving the dates out of weekends
        if(!isBusinessDay(dateToCheck, NON_WORKING_DAYS)){
            dateToCheck.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
            dateToCheck.set(Calendar.HOUR, 0);
            dateToCheck.set(Calendar.MINUTE, 0);
            dateToCheck.set(Calendar.SECOND, 0);
            dateToCheck.set(Calendar.MILLISECOND, 0);
        }

        if(!isBusinessDay(dateToCompare, NON_WORKING_DAYS)){
            dateToCompare.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
            dateToCompare.set(Calendar.HOUR, 0);
            dateToCompare.set(Calendar.MINUTE, 0);
            dateToCompare.set(Calendar.SECOND, 0);
            dateToCompare.set(Calendar.MILLISECOND, 0);
        }

        for(; dateToCheck.getTimeInMillis() < dateToCompare.getTimeInMillis() ; dateToCheck.add(Calendar.DAY_OF_MONTH, 1)){
            if(isBusinessDay(dateToCheck, NON_WORKING_DAYS)){
                minsToSubtract = minsToSubtract + 1440;
            }
        }
    minsBetween = minsBetween - minsToSubtract; 
    }
    return minsBetween;
}


private static boolean isBusinessDay(Calendar dateToCheck, List<Integer> daysToExclude){
    for(Integer dayToExclude : daysToExclude){
        if(dayToExclude != null && dayToExclude == dateToCheck.get(Calendar.DAY_OF_WEEK)) {
            return true; 
        }
        else continue;
    }
    return false;
}

有人可以告诉我,如果我的逻辑是正确的,如果不是,如何做到这一点?我不太确定这个代码在月末周末变化时的行为方式。

某些测试用例的预期输出:

  1. 周五下午6点,周一早上6点 - 应该返回12小时
  2. 星期六中午12点,星期日中午12点 - 应该返回0小时
  3. 星期六中午12点,星期一上午6点 - 应该返回6小时

2 个答案:

答案 0 :(得分:2)

我强烈建议使用Joda-Time来处理有关Java中日期操作的任何事情,因为它提供了许多有用的函数来使代码更简单。

此代码使用JodaTime:

public static final List<Integer> NON_WORKING_DAYS;
static {
    List<Integer> nonWorkingDays = new ArrayList<Integer>();
    nonWorkingDays.add(DateTimeConstants.SATURDAY);
    nonWorkingDays.add(DateTimeConstants.SUNDAY);
    NON_WORKING_DAYS = Collections.unmodifiableList(nonWorkingDays);
}

public static Minutes getMinsBetween(DateTime d1, DateTime d2,
        boolean onlyBusinessDays) {

    BaseDateTime startDate = onlyBusinessDays && !isBusinessDay(d1) ?
                new DateMidnight(d1) : d1;
    BaseDateTime endDate = onlyBusinessDays && !isBusinessDay(d2) ?
                new DateMidnight(d2) : d2;

    Minutes minutes = Minutes.minutesBetween(startDate, endDate);

    if (onlyBusinessDays) {
        DateTime d = new DateTime(startDate);

        while (d.isBefore(endDate)) {
            if (!isBusinessDay(d)) {
                Duration dayDuration = new Duration(d, d.plusDays(1));
                minutes = minutes.minus(int) dayDuration.getStandardMinutes());
            }
            d = d.plusDays(1);
        }
    }
    return minutes;
}

private static boolean isBusinessDay(DateTime dateToCheck) {
    return !NON_WORKING_DAYS.contains(dateToCheck.dayOfWeek().get());
}

测试此代码时,会产生以下结果:

DateTime d1 = new DateTime(2013, 1, 4, 18, 0); // a Friday, 6 pm
DateTime d2 = new DateTime(2013, 1, 7, 6, 0);  // the following Monday, 6 am

Minutes minutes = getMinsBetween(d1, d2, true);
System.out.println(minutes.toStandardHours().getHours()); // outputs "12" (in hours)

d1 = new DateTime(2013, 1, 5, 12, 0); // a Saturday, 12 pm
d2 = new DateTime(2013, 1, 6, 12, 0); // the following Sunday, 12 pm

minutes = getMinsBetween(d1, d2, true);
System.out.println(minutes.toStandardHours().getHours()); // outputs "0" (in hours)

d1 = new DateTime(2013, 1, 5, 12, 0); // a Saturday, 12 pm
d2 = new DateTime(2013, 1, 7, 6, 0);  // the following Monday, 6 am

minutes = getMinsBetween(d1, d2, true);
System.out.println(minutes.toStandardHours().getHours()); // outputs "6" (in hours)

我刚刚测试了一个月份周末变化的案例:从3月29日星期五(下午6点)到4月1日星期一(早上6点):

d1 = new DateTime(2013, 3, 29, 18, 0);
d2 = new DateTime(2013, 4, 1, 6, 0);

minutes = getMinsBetween(d1, d2, true);
System.out.println(minutes.toStandardHours().getHours());

结果是12小时,因此适用于月份更改。


我的第一个解决方案是没有正确处理夏令时。我们必须在减去分钟数时确定每个实际日期的持续时间,因为夏令时变化的天数不会是24小时:

if (!isBusinessDay(d)) {
    Duration dayDuration = new Duration(d, d.plusDays(1));
    minutes = minutes.minus(int) dayDuration.getStandardMinutes());
}

答案 1 :(得分:1)

JodaTime是要走的路,所以@KatjaChristiansen走在正确的轨道上。 如果您需要使用Java日历,我的解决方案将如下所示:

private static final long MILLIS_OF_WEEK = TimeUnit.DAYS.toMillis(7);
private static final long MILLIS_OF_WORKWEEK = TimeUnit.DAYS.toMillis(5);

public static int getMinsBetween(Date d1, Date d2, boolean onlyBusinessDays) {
    long duration = d2.getTime() - d1.getTime();
    if (onlyBusinessDays) {
        Date sat = toSaturdayMidnight(d1);
        long timeBeforeWeekend = Math.max(sat.getTime() - d1.getTime(), 0);
        if (duration > timeBeforeWeekend) {
            Date mon = toMondayMidnight(d2);
            long timeAfterWeekend = Math.max(d2.getTime() - mon.getTime(), 0);
            long numberOfWeekends = Math.max((duration / MILLIS_OF_WEEK) - 1, 0);
            duration = numberOfWeekends * MILLIS_OF_WORKWEEK + timeBeforeWeekend + timeAfterWeekend;
        }
    }
    return (int) TimeUnit.MILLISECONDS.toMinutes(duration);
}

private static Date toMondayMidnight(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    switch (cal.get(Calendar.DAY_OF_WEEK)) {
        case Calendar.SATURDAY:
        case Calendar.SUNDAY:
            cal.add(Calendar.DAY_OF_MONTH, 7);
    }
    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
    toMidnight(cal);
    return cal.getTime();
}

private static Date toSaturdayMidnight(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
    toMidnight(cal);
    return cal.getTime();
}

private static void toMidnight(Calendar cal) {
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
}

这些测试通过:

@Test
public void testWithinSameDay() {
    assertMinsBetween(30, "2013-01-03 9:00", "2013-01-03 9:30");
}
@Test
public void testOverWeekend() {
    assertMinsBetween(60, "2013-01-04 23:30", "2013-01-07 0:30");
}
@Test
public void testWeekendStart() {
    assertMinsBetween(30, "2013-01-05 23:30", "2013-01-07 0:30");
}
@Test
public void testTwoWeeks() {
    assertMinsBetween((int) TimeUnit.DAYS.toMinutes(10), "2013-01-08 23:30", "2013-01-22 23:30");
}
@Test
public void testTwoWeeksAndOneDay() {
    assertMinsBetween((int) TimeUnit.DAYS.toMinutes(11), "2013-01-08 23:30", "2013-01-23 23:30");
}
@Test
public void testOneWeekMinusOneDay() {
    assertMinsBetween((int) TimeUnit.DAYS.toMinutes(4), "2013-01-09 23:30", "2013-01-15 23:30");
}
private void assertMinsBetween(int expected, String start, String end) {
    try {
        assertEquals(expected, getMinsBetween(FORMAT.parse(start), FORMAT.parse(end), true));
    }
    catch (ParseException e) {
        throw new IllegalStateException(e);
    }
}