'How to add Business hours to Date considering not adding weekends ? - Java
I want to add certain number of hours to date, ignoring the weekends
For example,
(Friday 18:00) + 48 = (Tuseday 18:00) (Saturday and Sunday are ignored)
since the company works 24 hours, business hours are 24. But still i could not get how to add hours only on business days
function can be something like:
public Date getTaskEndTime(Calendar startDate, int hours){
// calculate the end time by adding the hours ignoring the weekends
}
Solution 1:[1]
I would strongly recommend using JodaTime (or DateTimeof Java8) for this, since the old Date/Calendar API is pretty useless.
public DateTime getEndtime(final DateTime startdate, final int hours) {
final DateTime endOfWeek = endOfWeek(startdate);
final Duration restOfWeek = new Duration(startdate, endOfWeek);
final Duration hoursDuration = toDuration(hours);
if (restOfWeek.isLongerThan(hoursDuration)) {
return startdate.plus(hoursDuration);
} else {
final Duration durationForNextWeek = hoursDuration.minus(restOfWeek);
return startOfWeek(startdate).plus(durationForNextWeek);
}
}
//Converts number of hours as int to Duration
private Duration toDuration(final int hours) {
return new Duration(hours * 60 * 60 * 1000);
}
//Returns coming friday, 1 millisecond to midnight
private DateTime endOfWeek(final DateTime dateTime) {
DateTime copy = dateTime;
while (copy.getDayOfWeek() != 6) {
copy = copy.plusDays(1);
}
return copy.toDateMidnight().toDateTime().minusMillis(1);
}
//Returns the following monday at midnight
//If dateTime is on a monday, the next monday will be chosen
private DateTime startOfWeek(final DateTime dateTime) {
DateTime copy = dateTime.plusDays(1);
while (copy.getDayOfWeek() != 1) {
copy = copy.plusDays(1);
}
return copy.toDateMidnight().toDateTime();
}
Explanation of the code:
- Check whether the number of hours can be added without crossing into a weekend
- If no, just add the hours to the startdate
- If yes, find the duration to be transferred to the next week, and add it to the start of the week
This code NOT support tasks stretching over multiple weeks, but it's a start that you can modify to support this.. Might be some edge cases that aren't handled as well, I'll leave it to you to test it thoroughly.
Solution 2:[2]
Avoid old date-time classes
You are using the old date-time classes that have proven to be poorly designed, confusing, and troublesome. Avoid them.
java.time
Use the java.time framework built into Java 8 and later. See Tutorial. For Java 6 & 7, use the ThreeTen-Backport project. For Android, the adaptation of that back-port, ThreeTenABP.
Here is some example code to get you going. I only whipped this up impromptu, so it may not be robust. This code does seem to work for your one example usage:
(Friday 18:00) + 48 = (Tuseday 18:00) (Saturday and Sunday are ignored)
I generalized this code a bit. Rather than a number of hours, it takes a Duration for any span of time (internally represented as a total number of seconds plus a fraction of second in nanoseconds). You will notice the output of the toString method uses standard ISO 8601 notation such as PT48H for your example amount of 48 hours.
Assuming you want real moments on the time line, we need to use a time zone to account for anomalies such as Daylight Saving Time (DST). For that we need to start with a ZonedDateTime which combines a moment on the timeline in UTC (Instant) with a time zone (ZoneId).
We also pass a duration such as 48 hours, and a set of day-of-week values such as Saturday & Sunday. Skip past this method for a discussion of these types below.
The strategy in this method is to take our duration such as 48 hours and chip away at it one day at a time by an amount necessary to get to the beginning of the following day. If that next day happens to be a prohibited day-of-week, we slide to the following day after that, and keep sliding until we reach an allowed (not prohibited) day-of-week date. We continue to nibble away at our remaining amount of time until it reaches zero. Read comments in the code for more discussion.
When calculating the start of the next day, do not assume that time-of-day will be 00:00:00.0. The day may start at another time because of Daylight Saving Time (DST) and perhaps other anomalies in certain time zones. We call atStartOfDay to let java.time determine the first moment of the day.
public ZonedDateTime addDurationSkippingDaysOfWeek ( ZonedDateTime zdt , Duration duration , EnumSet<DayOfWeek> daysOfWeek ) {
// Purpose: Start at zdt, add duration but skip over entire dates where day-of-week is contained in EnumSet of prohibited days-of-week. For example, skip over United States weekend of Saturday-Sunday.
// Verify inputs.
if ( ( null == zdt ) || ( null == zdt ) || ( null == zdt ) ) {
throw new IllegalArgumentException ( "Passed null argument. Message # bf186439-c4b2-423a-b5c9-76edebd87cf0." );
}
if ( daysOfWeek.size () == DayOfWeek.values ().length ) { // We must receive 6 or less days. If passed all 7 days of the week, no days left to use for calculation.
throw new IllegalArgumentException ( "The EnumSet argument specified all days of the week. Count: " + daysOfWeek.size () + ". So, impossible to calculate if we skip over all days. Message # 103a3088-5600-4d4e-a1e0-54410afa14f8." );
}
// Move through time, day-to-day, allocating remaining duration.
ZoneId zoneId = zdt.getZone (); // Passed as argument in code below.
ZonedDateTime moment = zdt; // This var is reassigned in loop below to fresh value, later and later, as we allocate the Duration to determine our target date-time.
Duration toAllocate = duration; // Loop below chips away at this Duration until none left.
while ( ! toAllocate.isZero () ) { // Loop while some duration remains to be allocated.
if ( toAllocate.isNegative () ) { // Bad - Going negative should be impossible. Means our logic is flawed.
throw new RuntimeException ( "The duration to allocate ran into a negative amount. Should not be possible. Message # 15a4267d-c16a-417e-a815-3c8f87af0232." );
}
ZonedDateTime nextDayStart = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId );
Duration untilTomorrow = Duration.between ( moment , nextDayStart );
// ZonedDateTime oldMoment = moment; // Debugging.
Duration allocation = null;
if ( untilTomorrow.compareTo ( toAllocate ) >= 0 ) { // If getting to tomorrow exceeds our remaining duration to allocate, we are done.
// Done -- we can allocate the last of the duration. Remaining to allocate is logically zero after this step.
moment = moment.plus ( toAllocate );
allocation = toAllocate; // Allocating all of our remaining duration.
// Do not exit here; do not call "return". Code below checks to see if the day-of-week of this date is prohibited.
} else { // Else more duration to allocate, so increment to next day.
moment = nextDayStart;
allocation = untilTomorrow; // Allocating the amount of time to take us to the start of tomorrow.
}
toAllocate = toAllocate.minus ( allocation ); // Subtract the amount of time allocated to get a fresh amount remaining to be
// Check to see if the moment has a date which happens to be a prohibited day-of-week.
while ( daysOfWeek.contains ( moment.getDayOfWeek () ) ) { // If 'moment' is a date which is a day-of-week on oun prohibited list, move on to the next date.
moment = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId ); // Move to start of the next day after. Continue loop to check this next day.
}
}
return moment;
}
To invoke that code, we will use your example data values. As one way of getting the raw data input, we parse a string into LocalDateTime.
String input = "2016-01-01T18:00:00";
LocalDateTime ldt = LocalDateTime.parse ( input );
That LocalDateTime object does not represent an actual moment on the timeline. So we apply a time zone to get an actual moment, a ZonedDateTime.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone ( zoneId );
Duration duration = Duration.ofHours ( 48 );
I also generalized from "the weekend" to any set of day-of-week values rather than hard-coding for Saturday & Sunday. The java.time classes include a handy enum, DayOfWeek -- much better than using strings or numbers in our code.
The enum facility in Java is vastly more flexible and useful than the simple number-masked-as-constant in most languages. Among its feature is a special Set implementation EnumSet for when you want to collect a subset of the possible items defined by your enum. For us here, we want to collect a pair of items, for the Saturday item and the Sunday item.
EnumSet<DayOfWeek> daysOfWeek = EnumSet.of ( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
Perhaps the working days are a four-day week such as Monday-Tuesday plus Thursday-Friday, then use EnumSet.of ( DayOfWeek.WEDNESDAY , DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ).
Now we are ready to call our calculation method. Pass three arguments:
- The starting
ZonedDateTime - A
Durationto be added to the starting date-time - An
EnumSetofDayOfWeekitems.
We get back our calculated future date-time.
ZonedDateTime zdtLater = this.addDurationSkippingDaysOfWeek ( zdt , duration , daysOfWeek );
Dump to console.
System.out.println ( "zdt: " + zdt + " plus duration: " + duration + " while skipping daysOfWeek: " + daysOfWeek + " is zdtLater: " + zdtLater );
zdt: 2016-01-01T18:00-05:00[America/Montreal] plus duration: PT48H while skipping daysOfWeek: [SATURDAY, SUNDAY] is zdtLater: 2016-01-05T18:00-05:00[America/Montreal]
Solution 3:[3]
Below code does solve the purpose.
public static Date addBusinessHours(Calendar startDate, int hours, int workingHourStart, int workingHourEnd){
System.out.println("Entering: Date Time " + startDate.getTime() + " | Remaining Hours: "+ hours + " | Working hours ("+workingHourStart+"-"+workingHourEnd+")");
if(hours == 0){
return startDate.getTime();
}
int hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
if(startDate.get(Calendar.MINUTE) > 0){
hourOfDay = hourOfDay +1;
}
int dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
if(dayOfWeek == Calendar.SATURDAY){
startDate.add(Calendar.DATE, 2);
startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
}
if(dayOfWeek == Calendar.SUNDAY){
startDate.add(Calendar.DATE, 1);
startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
}
if(dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY){
if(hourOfDay < workingHourStart){
startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
}
else if(hourOfDay >= workingHourEnd){
startDate.add(Calendar.DATE, 1);
startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
hourOfDay = startDate.get(Calendar.HOUR_OF_DAY);
dayOfWeek = startDate.get(Calendar.DAY_OF_WEEK);
addBusinessHours(startDate, hours, workingHourStart, workingHourEnd);
}
else if(hourOfDay >= workingHourStart && hourOfDay < workingHourEnd){
if(hours+hourOfDay <= workingHourEnd){
startDate.add(Calendar.HOUR_OF_DAY, hours);
return startDate.getTime();
}else{
//System.out.println("¤¤" + startDate.getTime() );
startDate.add(Calendar.DATE, 1);
//System.out.println("¤¤" + startDate.getTime() );
startDate.set(Calendar.HOUR_OF_DAY, workingHourStart);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
//System.out.println("¤¤" + startDate.getTime() );
System.out.println("##"+hours+ "##"+ workingHourEnd + "##" + hourOfDay);
int remaining_hours = hours - (workingHourEnd - hourOfDay);
addBusinessHours(startDate, remaining_hours, workingHourStart, workingHourEnd);
}
}
}
return startDate.getTime();
}
Solution 4:[4]
Basically what you need to do is to first calculate how many hours that remain of the week (this is the difference between current time and 24:00@Fri). If the number of hours are less than that you just add.
Otherwise you subtract that amount from the hours and then if the remainder is more than 120h (one week) you take the integer quotient and skips that many weeks. Finally you add the remainder to 00:00@Mon that week.
In your example you have between your start and 24:00@Fri 6h, that's less than 48h so you subtract 6h from it and get 42h. Now 42h is less than 120h so you don't skip weeks then you add 42h to 00:00@Mon by which you arrive at 18:00@Tue.
Solution 5:[5]
Please refer the below code, see if that helps you.
public static Date getTaskEndTime(Date startDate, int hours) {
// calculate the end time by adding the hours ignoring the weekends
Calendar endCal = Calendar.getInstance();
endCal.setTime(startDate);
for (int i = 0; i < hours; hours = hours - 8) {
if (!(endCal.get(Calendar.DAY_OF_WEEK) == 1 || endCal.get(Calendar.DAY_OF_WEEK) == 7)) {
endCal.add(Calendar.DAY_OF_MONTH, 1);
} else {
endCal.add(Calendar.DAY_OF_MONTH, 1);
hours = hours + 8;
}
}
return endCal.getTime();
}
Solution 6:[6]
you need to handle it in custom way:
public Date getTaskEndTime(Calendar startDate, int hours) {
Calendar endTime = Calendar.getInstance();
endTime.setTime(startDate.getTime());
endTime.add(Calendar.HOUR, hours); // add 2 for saturday and sunday
int dayOfWeek = endTime.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek == Calendar.SATURDAY) {
endTime.add(Calendar.DATE, 2); // add 2 for saturday and sunday
} else if (dayOfWeek == Calendar.SATURDAY) {
endTime.add(Calendar.DATE, 1); // add 1 for sunday
}
return endTime.getTime();
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Tobb |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | skyking |
| Solution 5 | Mukul Kumar Chaundhyan |
| Solution 6 |
