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

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

public static final List NON_WORKING_DAYS; static { List nonWorkingDays = new ArrayList(); 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 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个小时

我强烈建议使用Joda-Time来处理Java中的日期操作,因为它提供了许多有用的函数来使代码不那么复杂。

此代码使用JodaTime:

 public static final List NON_WORKING_DAYS; static { List nonWorkingDays = new ArrayList(); 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()); } 

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

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