Avoid old date-time classes
The old date-time classes bundled with the earliest versions of Java are poorly designed, confusing, and troublesome. Avoid java.util.Date/.Calendar and so on.
java.time
Use the java.time framework built into Java 8 and later. Or its backport for Java 7 & 6. See Oracle Tutorial.
I have not tried running this code; just off the top of my head. Never run, never tested. Might give you a start.
The Question seems to say that we want to assume generic 24-hour days rather than actual days (which can vary in length due to anomalies such as Daylight Saving Time). In the case of generic days, we do not care about time zone. We can use the LocalDateTime
class.
If the start happens to be on a weekend, move it to the first moment of the following Monday.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHH" );
LocalDateTime start = LocalDateTime.parse( "2016010123" , formatter );
// If on a weekend, move to first moment of Monday.
if( start.getDayOfWeek().equals( DayOfWeek.SATURDAY ) {
start = start.plusDays( 2 ).toLocalDate().atStartOfDay();
}
if( start.getDayOfWeek().equals( DayOfWeek.SUNDAY ) {
start = start.plusDays( 1 ).toLocalDate().atStartOfDay();
}
Ditto for the stop. If on weekend, move to first moment of the following Monday.
LocalDateTime stop = LocalDateTime.parse( "2016010723" , formatter );
// If on a weekend, move to first moment of Monday.
if( stop.getDayOfWeek().equals( DayOfWeek.SATURDAY ) {
stop = stop.plusDays( 2 ).toLocalDate().atStartOfDay();
}
if( stop.getDayOfWeek().equals( DayOfWeek.SUNDAY ) {
stop = stop.plusDays( 1 ).toLocalDate().atStartOfDay();
}
If the start is the same as stop, we are done: report zero.
if( start.isEqual( stop ) ) {
return 0;
}
If the start is after the stop, we have an error condition. Throw an exception, try to correct the problem, or report zero, whatever makes sense in your app.
if( start.isAfter( stop ) ) {
return 0; // Or consider it a problem, throw exception.
}
Let's divide this problem into three parts:
- First partial day,
- Last partial day,
- Number of whole days in between.
To represent the partial days, we use the Duration
class. A Duration
is a span of time tracked as a total number of seconds plus nanoseconds.
First part runs from start
to the first moment of the following day.
LocalDateTime firstMomentOfDayAfterStart = start.toLocalDate().plusDays(1).atStartOfDay();
Duration firstDayDuration = Duration.between( start , firstMomentOfDayAfterStart );
The last partial day runs from first moment of the same day as stop
.
LocalDateTime firstMomentOfDayOfStop = stop.toLocalDate().atStartOfDay();
Duration lastDayDuration = Duration.between( firstMomentOfDayOfStop , stop );
We can convert each Duration
to a number of hours.
int hoursFirstDay = firstDayDuration.toHours();
int hoursLastDay = lastDayDuration.toHours();
So we have variables defining these spans of time as shown in this text-as-diagram below. Remember these are Half-Open, where the ending of each span is exclusive.
[ start > firstMomentOfDayAfterStart ][ firstMomentOfDayAfterStart > firstMomentOfDayOfStop ][ firstMomentOfDayOfStop > stop ]
Now loop through the whole days in between that pair of partial days. Increment day-by-day, testing each successive day for being a weekday or weekend. Keep count of the weekdays we encounter. Use the handy DayOfWeek
enum to compare.
int countWeekdays = 0;
LocalDateTime firstMomentOfSomeDay = firstMomentOfDayAfterStart
while( firstMomentOfSomeDay.isBefore( firstMomentOfDayOfStop ) ) {
DayOfWeek dayOfWeek = firstMomentOfSomeDay.getDayOfWeek();
if( dayOfWeek.equals( DayOfWeek.SATURDAY ) || dayOfWeek.equals( DayOfWeek.SUNDAY ) ) {
// ignore this day.
} else {
countWeekdays ++ ; // Tally another weekday.
}
// Set up the next loop.
firstMomentOfSomeDay = firstMomentOfSomeDay.plusDays( 1 );
}
We can use the TimeUnit
enum to convert the count of whole days to a count of hours. This conversion assumes generic 24-hour days.
int hoursOfWholeDays = TimeUnit.DAYS.toHours( countWeekDays ) ;
Add those all up.
int totalHours = ( hoursFirstDay + hoursOfWholeDays + hoursLastDay ) ;
Done. Return the total hours.
return totalHours ;
If your data often includes long periods of time then you might consider optimizing performance by detecting a Monday and calculating number of whole weeks. I assume for tracking orders in a business scenario, the intervals are for a small number of days. So this optimization is not likely to be significant.
EnumSet
You can replace the line:
if( dayOfWeek.equals( DayOfWeek.SATURDAY ) || dayOfWeek.equals( DayOfWeek.SUNDAY ) )…
…with an EnumSet
.
Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;
…
if( weekend.contains( dayOfWeek ) ) …
Temporal Adjustor
See my answer to another Question for more info on detecting weekends using the nextWorkingDay
and previousWorkingDay
methods of the Temporals
class. This class is found in the ThreeTen-Extra project that extends the java.time framework.
Furthermore, that class docs suggests it is relatively easy to write your own temporal adjustor to suit your particular business rules.