public Date getStartDate(Date finishDate, Duration duration)
   {
      ProjectProperties properties = getParentFile().getProjectProperties();
      // Note: Using a double allows us to handle date values that are accurate up to seconds.
      //       However, it also means we need to truncate the value to 2 decimals to make the
      //       comparisons work as sometimes the double ends up with some extra e.g. .0000000000003
      //       that wreak havoc on the comparisons.
      double remainingMinutes = NumberHelper.round(duration.convertUnits(TimeUnit.MINUTES, properties).getDuration(), 2);
      Calendar cal = Calendar.getInstance();
      cal.setTime(finishDate);
      Calendar startCal = Calendar.getInstance();

      while (remainingMinutes > 0)
      {
         //
         // Get the current date and time and determine how many
         // working hours remain
         //
         Date currentDate = cal.getTime();
         startCal.setTime(currentDate);
         startCal.add(Calendar.DAY_OF_YEAR, -1);
         Date currentDateEnd = DateHelper.getDayEndDate(startCal.getTime());
         double currentDateWorkingMinutes = getWork(currentDateEnd, currentDate, TimeUnit.MINUTES).getDuration();

         //
         // We have more than enough hours left
         //
         if (remainingMinutes > currentDateWorkingMinutes)
         {
            //
            // Deduct this day's hours from our total
            //
            remainingMinutes = NumberHelper.round(remainingMinutes - currentDateWorkingMinutes, 2);

            //
            // Move the calendar backward to the previous working day
            //
            int count = 0;
            Day day;
            do
            {
               if (count > 7)
               {
                  break; // Protect against a calendar with all days non-working
               }
               count++;
               cal.add(Calendar.DAY_OF_YEAR, -1);
               day = Day.getInstance(cal.get(Calendar.DAY_OF_WEEK));
            }
            while (!isWorkingDate(cal.getTime(), day));

            if (count > 7)
            {
               // We have a calendar with no working days.
               return null;
            }

            //
            // Retrieve the finish time for this day
            //
            Date finishTime = getFinishTime(cal.getTime());
            DateHelper.setTime(cal, finishTime);
         }
         else
         {
            //
            // We have less hours to allocate than there are working hours
            // in this day. We need to calculate the time of day at which
            // our work starts.
            //
            ProjectCalendarDateRanges ranges = getRanges(cal.getTime(), cal, null);

            //
            // Now we have the range of working hours for this day,
            // step through it to work out the start point
            //
            Date startTime = null;
            Date currentDateFinishTime = DateHelper.getCanonicalTime(currentDate);
            boolean firstRange = true;
            // Traverse from end to start
            for (int i = ranges.getRangeCount() - 1; i >= 0; i--)
            {
               DateRange range = ranges.getRange(i);
               //
               // Skip this range if its start is after our end time
               //
               Date rangeStart = range.getStart();
               Date rangeEnd = range.getEnd();

               if (rangeStart == null || rangeEnd == null)
               {
                  continue;
               }

               Date canonicalRangeEnd = DateHelper.getCanonicalTime(rangeEnd);
               Date canonicalRangeStart = DateHelper.getCanonicalTime(rangeStart);

               Date rangeStartDay = DateHelper.getDayStartDate(rangeStart);
               Date rangeEndDay = DateHelper.getDayStartDate(rangeEnd);

               if (rangeStartDay.getTime() != rangeEndDay.getTime())
               {                  
                  canonicalRangeEnd = DateHelper.addDays(canonicalRangeEnd, 1);
               }

               if (firstRange && canonicalRangeStart.getTime() > currentDateFinishTime.getTime())
               {
                  continue;
               }

               //
               // Move the end of the range if our current end is
               // before the range end
               //
               if (firstRange && canonicalRangeEnd.getTime() > currentDateFinishTime.getTime())
               {
                  canonicalRangeEnd = currentDateFinishTime;
               }
               firstRange = false;

               double rangeMinutes;

               rangeMinutes = canonicalRangeEnd.getTime() - canonicalRangeStart.getTime();
               rangeMinutes /= (1000 * 60);

               if (remainingMinutes > rangeMinutes)
               {
                  remainingMinutes = NumberHelper.round(remainingMinutes - rangeMinutes, 2);
               }
               else
               {
                  if (Duration.durationValueEquals(remainingMinutes, rangeMinutes))
                  {
                     startTime = canonicalRangeStart;
                  }
                  else
                  {
                     startTime = new Date((long) (canonicalRangeEnd.getTime() - (remainingMinutes * (60 * 1000))));
                  }
                  remainingMinutes = 0;
                  break;
               }
            }

            DateHelper.setTime(cal, startTime);
         }
      }

      return cal.getTime();
   }
public Date getStartDate ( Date finishDate , Duration duration ) { ProjectProperties properties = getParentFile ( ) . getProjectProperties ( ) ; double remainingMinutes = NumberHelper . round ( duration . convertUnits ( TimeUnit . MINUTES , properties ) . getDuration ( ) , 2 ) ; Calendar cal = Calendar . getInstance ( ) ; cal . setTime ( finishDate ) ; Calendar startCal = Calendar . getInstance ( ) ; while ( remainingMinutes > 0 ) { Date currentDate = cal . getTime ( ) ; startCal . setTime ( currentDate ) ; startCal . add ( Calendar . DAY_OF_YEAR , - 1 ) ; Date currentDateEnd = DateHelper . getDayEndDate ( startCal . getTime ( ) ) ; double currentDateWorkingMinutes = getWork ( currentDateEnd , currentDate , TimeUnit . MINUTES ) . getDuration ( ) ; if ( remainingMinutes > currentDateWorkingMinutes ) { remainingMinutes = NumberHelper . round ( remainingMinutes - currentDateWorkingMinutes , 2 ) ; int count = 0 ; Day day ; do { if ( count > 7 ) { break ; } count ++ ; cal . add ( Calendar . DAY_OF_YEAR , - 1 ) ; day = Day . getInstance ( cal . get ( Calendar . DAY_OF_WEEK ) ) ; } while ( ! isWorkingDate ( cal . getTime ( ) , day ) ) ; if ( count > 7 ) { return null ; } Date finishTime = getFinishTime ( cal . getTime ( ) ) ; DateHelper . setTime ( cal , finishTime ) ; } else { ProjectCalendarDateRanges ranges = getRanges ( cal . getTime ( ) , cal , null ) ; Date startTime = null ; Date currentDateFinishTime = DateHelper . getCanonicalTime ( currentDate ) ; boolean firstRange = true ; for ( int i = ranges . getRangeCount ( ) - 1 ; i >= 0 ; i -- ) { DateRange range = ranges . getRange ( i ) ; Date rangeStart = range . getStart ( ) ; Date rangeEnd = range . getEnd ( ) ; if ( rangeStart == null || rangeEnd == null ) { continue ; } Date canonicalRangeEnd = DateHelper . getCanonicalTime ( rangeEnd ) ; Date canonicalRangeStart = DateHelper . getCanonicalTime ( rangeStart ) ; Date rangeStartDay = DateHelper . getDayStartDate ( rangeStart ) ; Date rangeEndDay = DateHelper . getDayStartDate ( rangeEnd ) ; if ( rangeStartDay . getTime ( ) != rangeEndDay . getTime ( ) ) { canonicalRangeEnd = DateHelper . addDays ( canonicalRangeEnd , 1 ) ; } if ( firstRange && canonicalRangeStart . getTime ( ) > currentDateFinishTime . getTime ( ) ) { continue ; } if ( firstRange && canonicalRangeEnd . getTime ( ) > currentDateFinishTime . getTime ( ) ) { canonicalRangeEnd = currentDateFinishTime ; } firstRange = false ; double rangeMinutes ; rangeMinutes = canonicalRangeEnd . getTime ( ) - canonicalRangeStart . getTime ( ) ; rangeMinutes /= ( 1000 * 60 ) ; if ( remainingMinutes > rangeMinutes ) { remainingMinutes = NumberHelper . round ( remainingMinutes - rangeMinutes , 2 ) ; } else { if ( Duration . durationValueEquals ( remainingMinutes , rangeMinutes ) ) { startTime = canonicalRangeStart ; } else { startTime = new Date ( ( long ) ( canonicalRangeEnd . getTime ( ) - ( remainingMinutes * ( 60 * 1000 ) ) ) ) ; } remainingMinutes = 0 ; break ; } } DateHelper . setTime ( cal , startTime ) ; } } return cal . getTime ( ) ; }
Given a finish date and a duration, this method calculates backwards to the
start date. It takes account of working hours in each day, non working
days and calendar exceptions.

@param finishDate finish date
@param duration duration
@return start date