How to ignore weekends using Java calendar?

I am trying to get the number of minutes between two specific instances of time, ignoring the weekend. This is what I did.

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

Can someone tell me if my logic is correct, and if not, how to do it? I'm not too sure how this code will behave when the month changes over the weekend.

Expected Result for Some Test Cases:

  • Friday, 18:00, Monday 6 am - should be back 12 hours.
  • Saturday 12 pm, Sunday 12 pm - should return 0 hours
  • Saturday 12:00, Monday 6 am - should be back 6 hours.
+4
source share
2 answers

I highly recommend using Joda-Time for anything related to date processing in Java, because it has many useful features to make the code less complicated.

This code uses 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()); } 

When this code is verified, it gives the following results:

 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) 

I just checked the case where the month changes on weekends: from Friday, March 29 (6 hours) to Monday, April 1 (6 hours):

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

The result is 12 hours, so it works to change the month.


My first solution did not handle daylight saving time correctly. We must determine the duration of each actual day when subtracting minutes, because days with a change in daylight saving time will not be equal to 24h:

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

JodaTime is the way to go, so @KatjaChristiansen is on the right track. If you need to use Java Calendar, my solution would look like this:

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

These tests pass:

 @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); } } 
+1
source

All Articles