2

I have some kind of an event, that is characterized by the start date and duration.

I need to identify is event already finished for current time or not and how many hours are remained or passed after finish.

And I have a condition to count work hours only between 10AM and 6PM (int work_start = 10, int work_end = 18). If now is 9AM it should calculate only yesterday hours as last working hours, and if today is 01PM it should calculate for today that 3 hours are already passed

I've created two methods but the calculation doesn't take into account working hours. How to calculate working time only?

The condition is NOT to use Joda Time. Is it possible?

My two methods are:

public String getProgramEndDate(Date dateStart, int totalDuration){
        long durationInMillis = totalDuration * 3600000;
        long end = dateStart.getTime() + durationInMillis;
        Date date=new Date(end);
        SimpleDateFormat df2 = new SimpleDateFormat("dd/MM/yyyy");
        String endD = df2.format(date);
        return endD;
    }

public StringBuilder getDaysEndOfTheProgram(Long howMuchTime) {
    SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH");
    long diffHours = howMuchTime / (60 * 60 * 1000) % 8;
    long diffDays = howMuchTime / (24 * 60 * 60 * 1000);
    StringBuilder sb = new StringBuilder();
    sb.append(diffDays + " days, ");
    sb.append(diffHours + " hours. ");
    return sb;
javagirl
  • 21
  • 4

2 Answers2

1

First of all, you are using troublesome old date-classes that are now legacy, supplanted by the java.time classes.

The equivalent to Date is Instant, a moment on the timeline in UTC, but with a finer resolution of nanoseconds. Pass object of this class as your first argument. If you are given a Date, convert using new methods added to the old class, Date::toInstant.

Instant = myUtilDate.toInstant() ;

Use a class for you duration rather than a mere integer number. This makes your code more self-documenting and gives you type-safety.

Duration d = Duration.ofHours( … ) ;

Since you want certain hours of the day and current date, we require a time zone. We must adjust out UTC value into that time zone.

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdtInitial = instant.atZone( z ) ;

Do you understand that Date is actually a date-time? If you intended to pass a date-only value without a time-of-day, pass a LocalDate object instead. Your code suggests you did mean an actual time-of-day is included.

First test if your start time is in the work hours.

LocalTime ltStart = LocalTime.of( 10 , 0 ) ;
LocalTime ltStart = LocalTime.of( 18 , 0 ) ;

LocalTime ltInitial = zdtInitial.toLocalTime() ;
if( ( ! ltInitial.isBefore( ltStart ) ) && ltInitial.isBefore( ltStop ) ) { … }

Next, test if your start time has yet to occur.

ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
if( zdtNow.isBefore( zdtInitial ) ) { … }

No magic answer to the issue of working hours-of-day. Just start counting, whittling away at the Duration.

Duration dRemaining = d ; 

ZonedDateTime zdtInitialEndOfDay = ZonedDateTime.of( zdtInitial.toLocalDate() , ltStop ) ; 
Duration dBetween = Duration.between( zdt.Initial , zdtInitialEndOfDay ) ; 

Test to see if the between amount is equal to or exceeds to remaining amount.

if( dBetween.compareTo( dRemaining ) >= 0 ) {
    // Add remaining duration to the zdt to get the ending moment. End looping. 
} else {  // between is less than remaining. So subtract, and move on to next day.
    dRemaining = dRemaining.minus( dBetween ); 
    // get `LocalDate as seen above, call `plusDays` and create another `ZonedDateTime` as seen above.
}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

You can use the new Date and Time API.

If you're using Java 8, it comes natively in java.time package.

If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

I'm assuming that a full day counts as 8 hours, so a case like this:

  • start at July 10th, at 3PM
  • end at July 12th, at 1PM

will result in 14 hours (3 hours in the first day, 8 hours in July 11th and 3 hours in the last day). The algorithm is as follows:

// get system's default timezone
ZoneId zone = ZoneId.systemDefault();

// or even better, get a specific timezone (more details on that below)
ZoneId zone = ZoneId.of("Europe/Kiev");


// start and end working hours
LocalTime workStart = LocalTime.of(10, 0);
LocalTime workEnd = LocalTime.of(18, 0);

// start and end dates
ZonedDateTime start = ZonedDateTime.of(2017, 7, 10, 15, 0, 0, 0, zone);
ZonedDateTime end = ZonedDateTime.of(2017, 7, 12, 13, 0, 0, 0, zone);

long totalHours = 0;
ZonedDateTime startHour = start;
// if start is before 10AM or 6PM, adjust it
if (start.toLocalTime().isBefore(workStart)) { // before 10 AM
    startHour = start.with(workStart); // set time to 10 AM
} else if (start.toLocalTime().isAfter(workEnd)) { // after 6 PM
    startHour = start.with(workEnd); // set time to 6 PM
}
ZonedDateTime endHour = end;
// if end is before 10AM or 6PM, adjust it
if (end.toLocalTime().isAfter(workEnd)) { // after 6 PM
    endHour = end.with(workEnd); // set time to 6 PM
} else if (end.toLocalTime().isBefore(workStart)) { // before 10 AM
    endHour = end.with(workStart); // set time to 10 AM
}

while(startHour.isBefore(endHour)) {
    if (startHour.toLocalDate().equals(endHour.toLocalDate())) { // same day
        totalHours += ChronoUnit.HOURS.between(startHour, endHour);
        break;
    } else {
        ZonedDateTime endOfDay = startHour.with(workEnd); // 6PM of the day
        totalHours += ChronoUnit.HOURS.between(startHour, endOfDay);
        startHour = startHour.plusDays(1).with(workStart); // go to next day
    }
}

System.out.println(totalHours); // 14

The result of totalHours will be 14. If you want to break this value in days and hours, you can do the following (assuming that each day has 8 working hours):

long days = totalHours / 8;
long hours = totalHours % 8;

System.out.println(days + " days, " + hours + " hours");

The output will be:

1 days, 6 hours

Or even better (to get correct singular/plural):

System.out.println(days + " day" + (days > 1 ? "s" : "") + ", " + hours + " hour" + (hours > 1 ? "s" : ""));

1 day, 6 hours


Notes:

  • I'm using ZonedDateTime (as @BasilBourque reminded me in the comments) because it can take care of DST changes and another timezone issues.
  • I've used ZoneId.systemDefault() to get the system's default timezone, but it's better to use a specific zone (because the system's default can be changed in runtime, leading to unexpected results), using ZoneId.of(zoneName). A zoneName can be any of the IANA timezones names (always in the format Region/City, like Europe/Kiev or Europe/Berlin). Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
    You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
  • if you need to convert your java.util.Date to the new API, it's straightforward:

    // Java 8
    ZonedDateTime dt = date.toInstant().atZone(zone);
    
    // Java 7 ThreeTen Backport
    ZonedDateTime dt = DateTimeUtils.toInstant(date).atZone(zone);
    
  • `LocalDateTime` is inappropriate here. It lacks any concept of time zone. So this code will be ignoring any real-world anomalies such as a Daylight Saving Time (DST) cutover. `ZonedDateTime` should be used. – Basil Bourque Jul 13 '17 at 16:13
  • @BasilBourque I've first thought of that, but if OP is considering only working hours, will it make a difference? I'm not 100% sure, but anyway, I've changed my answer to use `ZonedDateTime` just in case. Thanks! –  Jul 13 '17 at 16:21
  • 1
    An anomaly can happen during working hours. You don't know what politicians will do next with zones and DST. You don't know what time-of-day may be used in the final code or in the future. Depending on luck or hope-and-prayer for date-time work will eventually leave you unhappy, usually after a very long drawn-out debugging struggle. – Basil Bourque Jul 13 '17 at 20:11
  • @BasilBourque You're right. I usually think that DST changes don't occur during working hours, but you never know what politicians can do. I'll leave my answer with `ZonedDateTime`, it's more guaranteed than `LocalDateTime`. Thank you! –  Jul 13 '17 at 21:05
  • You should **cache your `ZoneId`**. Two calls to `systemDefault` could return two different zones. How? Any code in any thread in any app in the JVM at any moment can instantly change the JVM’s current default time zone. Even better: **specify your desired/expected zone** rather than relying on default. – Basil Bourque Jul 14 '17 at 06:45
  • @BasilBourque Yes, I always miss those details. I've updated the answer, thanks! –  Jul 14 '17 at 11:47