我在 Java 中做了类似的事情。
我有一个包含日期范围的表格,还有一个日期范围,我必须从已经存在的日期范围插入到覆盖范围的失效中。
我基本上采用了我感兴趣的日期范围,并“减去”了所有现有的日期范围。您将在下面的代码中找到我的减法。
从 DateRange_A 中减去 DateRange_B 会导致 DateRange_A 被原地修改,并且如果 DateRange_A 完全被 DateRange_B 分割,则该方法返回一个新的 DateRange。
当然还有其他方法可以解决这个问题,比如 SQL SERVER 中的迭代,但我已经处于 Java 思维模式,这个解决方案就这样发生了。
/*
* Copyright (c) 2009, Ben Fortuna
* (Modified by Alex Marunowski)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* o Neither the name of Ben Fortuna nor the names of any other contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.Serializable;
import java.util.Date;
/**
* @author fortuna
*
*/
public class DateRange implements Serializable {
private static final long serialVersionUID = -7303846680559287286L;
/**
* A flag indicating whether to include the start of the period in test functions.
*/
public static final int INCLUSIVE_START = 1;
/**
* A flag indicating whether to include the end of the period in test functions.
*/
public static final int INCLUSIVE_END = 2;
private Date rangeStart;
private Date rangeEnd;
/**
* @param start the start of the range
* @param end the end of the range
*/
public DateRange(Date start, Date end) {
if (start == null) {
throw new IllegalArgumentException("Range start is null");
}
if (end == null) {
throw new IllegalArgumentException("Range end is null");
}
if (end.before(start)) {
throw new IllegalArgumentException("Range start must be before range end");
}
this.rangeStart = start;
this.rangeEnd = end;
}
/**
* @return the rangeStart
*/
public Date getStartDate() {
return rangeStart;
}
/**
* @return the rangeEnd
*/
public Date getEndDate() {
return rangeEnd;
}
public void setRangeStart(Date rangeStart) {
if(rangeStart.after(getEndDate()))
throw new IllegalArgumentException("The start of a date range cannot be after its end!");
this.rangeStart = rangeStart;
}
public void setRangeEnd(Date rangeEnd) {
if(rangeStart.after(getEndDate()))
throw new IllegalArgumentException("The end of a date range cannot be after its start!");
this.rangeEnd = rangeEnd;
}
/**
* Determines if the specified date occurs within this period (inclusive of
* period start and end).
* @param date a date to test for inclusion
* @return true if the specified date occurs within the current period
*
*/
public final boolean includes(final Date date) {
return includes(date, INCLUSIVE_START | INCLUSIVE_END);
}
/**
* Decides whether a date falls within this period.
* @param date the date to be tested
* @param inclusiveMask specifies whether period start and end are included
* in the calculation
* @return true if the date is in the period, false otherwise
* @see Period#INCLUSIVE_START
* @see Period#INCLUSIVE_END
*/
public final boolean includes(final Date date, final int inclusiveMask) {
boolean includes = true;
if ((inclusiveMask & INCLUSIVE_START) > 0) {
includes = includes && !rangeStart.after(date);
}
else {
includes = includes && rangeStart.before(date);
}
if ((inclusiveMask & INCLUSIVE_END) > 0) {
includes = includes && !rangeEnd.before(date);
}
else {
includes = includes && rangeEnd.after(date);
}
return includes;
}
/**
* Decides whether this period is completed before the given period starts.
*
* @param range
* a period that may or may not start after this period ends
* @return true if the specified period starts after this periods ends,
* otherwise false
*/
public final boolean before(final DateRange range) {
return (rangeEnd.before(range.getStartDate()));
}
/**
* Decides whether this period starts after the given period ends.
*
* @param range
* a period that may or may not end before this period starts
* @return true if the specified period end before this periods starts,
* otherwise false
*/
public final boolean after(final DateRange range) {
return (rangeStart.after(range.getEndDate()));
}
/**
* Decides whether this period intersects with another one.
*
* @param range
* a possible intersecting period
* @return true if the specified period intersects this one, false
* otherwise.
*/
public final boolean intersects(final DateRange range) {
boolean intersects = false;
// Test for our start date in period
// (Exclude if it is the end date of test range)
if (range.includes(rangeStart) && !range.getEndDate().equals(rangeStart)) {
intersects = true;
}
// Test for test range's start date in our range
// (Exclude if it is the end date of our range)
else if (includes(range.getStartDate())
&& !rangeEnd.equals(range.getStartDate())) {
intersects = true;
}
return intersects;
}
/**
* Decides whether these periods are serial without a gap.
* @param range a period to test for adjacency
* @return true if one period immediately follows the other, false otherwise
*/
public final boolean adjacent(final DateRange range) {
boolean adjacent = false;
if (rangeStart.equals(range.getEndDate())) {
adjacent = true;
} else if (rangeEnd.equals(range.getStartDate())) {
adjacent = true;
}
return adjacent;
}
/**
* Decides whether the given period is completely contained within this one.
*
* @param range
* the period that may be contained by this one
* @return true if this period covers all the dates of the specified period,
* otherwise false
*/
public final boolean contains(final DateRange range) {
// Test for period's start and end dates in our range
return (includes(range.getStartDate()) && includes(range.getEndDate()));
}
/**
* Decides whether the given period is completely contained within this one, taking into consideration whether date ranges with matching start or end dates
* are counted as being contained
*
* @param range
* the period that may be contained by this one
* @param inclusiveMask
* if this is set to 0, the start and end dates cannot be the same date and have it be considered to be contained within this date range.
* this.contains(this, 1) returns true
* this.contains(this, 0) returns false
* @return true if this period covers all the dates of the specified period,
* otherwise false
*/
public final boolean contains(final DateRange range, int inclusiveMask) {
// Test for period's start and end dates in our range
return (includes(range.getStartDate(), inclusiveMask) && includes(range.getEndDate(), inclusiveMask));
}
/**
* Exclude otherRange from the dates covered by this DateRange. Note: This will put the specified buffer around the range being subtracted from this date range.
* @param otherRange
* @return an additional date range if subtracting otherRange from this DateRange results in a part of this objects DateRange being separated from the rest of the range.
* i.e. if this.includes(otherRange, 0), then there will be two remaining portions of this daterange.
* If no dangling date range remains, then the method returns null.
* @author Alex Marunowski. 2012.10.31
*/
public DateRange subtract(DateRange otherRange, long buffer) throws DateRangeObliteratedException{
Date bufferedStart = new Date(otherRange.getStartDate().getTime()-buffer);
Date bufferedEnd= new Date(otherRange.getEndDate().getTime()+buffer);
otherRange = new DateRange(bufferedStart, bufferedEnd);
// If the other range is entirely after this range, nothing happens
if(getEndDate().before(otherRange.getStartDate()))
return null;
// If the other range is entirely before this range, nothing happens
if(getStartDate().after(otherRange.getEndDate()))
return null;
if(otherRange.contains(this))
throw new DateRangeObliteratedException();
DateRange separatedTimeInterval = null;
if(this.contains(otherRange, 0)){
// The trailing daterange is the time between the end date of the inner daterange, and the end date of the outer date range
separatedTimeInterval = new DateRange(otherRange.getEndDate(), getEndDate());
// This date range now ends at the start time of the inner date range
this.setRangeEnd(otherRange.getStartDate());
return separatedTimeInterval;
}
if(otherRange.getEndDate().before(getEndDate())){
// This date range is now the time between the end of the otherRange plus the buffer time, and the end of this date range
long newRangeStart = otherRange.getEndDate().getTime();
this.setRangeStart(new Date(newRangeStart));
return null;
}
if(otherRange.getStartDate().after(getStartDate())){
// This date range is now the time between this date range's start, and the other date ranges start minus the buffer time
long newRangeEnd = otherRange.getStartDate().getTime();
this.setRangeEnd(new Date(newRangeEnd));
return null;
}
// This will never happen, but the compiler doesn't know that
System.out.println("This should never have happened. No comparisons between the date ranges was discovered");
return null;
}
public static class DateRangeObliteratedException extends Exception {
/**
*
*/
private static final long serialVersionUID = -5642891561498447972L;
public DateRangeObliteratedException() {
super("This date range no longer exists. It was entirely contained within the range you subtracted from it.");
}
}
}
for(int rangeIndex = 0; rangeIndex less than rangesBusy.size(); rangeIndex++) {
DateRange busyRange = rangesBusy.get(rangeIndex);
try {
DateRange trailingRange = freeTimeBlock.subtract(busyRange, 0);
if(trailingRange != null) {
freeTimeRanges.add(trailingRange);
}
} catch (DateRangeObliteratedException e) {
freeTimeRanges.remove(rangeIndex);
rangeIndex--;
}
}