How to add Business hours to Date considering not adding weekends ? - Java

前端 未结 7 945
盖世英雄少女心
盖世英雄少女心 2020-12-12 04:47

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)

相关标签:
7条回答
  • 2020-12-12 05:34

    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 Duration to be added to the starting date-time
    • An EnumSet of DayOfWeek items.

    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]

    0 讨论(0)
提交回复
热议问题