Reputation: 7977
I have the following class:
public class Membership
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; } // If null then it lasts forever
}
I need to make sure when adding to the following list that the new item doesn't overlap the dates from existing item:
var membership = new List<Membership>
{
new Membership { StartDate = DateTime.UtcNow.AddDays(-10), EndDate = DateTime.UtcNow.AddDays(-5) },
new Membership { StartDate = DateTime.UtcNow.AddDays(-5), EndDate = null }
};
For example doing:
var newItem = new Membership { StartDate = DateTime.UtcNow.AddDays(-15), EndDate = DateTime.UtcNow.AddDays(-10) }; // Allowed
var newItem2 = new Membership { StartDate = DateTime.UtcNow.AddDays(-15), EndDate = null }; // Not Allowed
if (AllowededToAdd(newItem))
membership.Add(newItem);
if (AllowededToAdd(newItem2))
membership.Add(newItem2);
I thought this would be simple but so far my attempts have all been wrong and i'm starting to confuse myself and was hoping someone had done something similar they could share. Thanks
Upvotes: 13
Views: 35570
Reputation: 1
I came up with the following method to check if dates overlap, it may not be the most effecient way but i hope this helps..
public static bool DateRangesOverlap(DateTime startDateA, DateTime endDateA, DateTime startDateB, DateTime endDateB)
{
var allDatesA = new List<DateTime>();
var allDatesB = new List<DateTime>();
for (DateTime date = startDateA; date <= endDateA; date = date.AddDays(1))
{
allDatesA.Add(date);
}
for (DateTime date = startDateB; date <= endDateB; date = date.AddDays(1))
{
allDatesB.Add(date);
}
var isInRange = false;
foreach (var date in allDatesA)
{
var existsInAllDatesB = allDatesB.Any(x => x == date);
if (existsInAllDatesB)
{
isInRange = true;
break;
}
}
return isInRange;
}
Upvotes: 0
Reputation: 31
A bit late but I couldn't find this pattern anywhere in the answers/comments.
if (startDate1 <= endDate2 && startDate2 <= endDate1)
{
// Overlaps.
}
Upvotes: 2
Reputation: 3502
So if I understand this correctly - you want to make sure date range 2 is not within date range 1?
For example:
startDate1 = 01/01/2011
endDate1 = 01/02/2011
and
startDate2 = 19/01/2011
endDate2 = 10/02/2011
This should be a simple case of:
if ((startDate2 >= startDate1 && startDate2 <= endDate1) ||
(endDate2 >= startDate1 && endDate2 <= endDate1))
Upvotes: 7
Reputation: 1673
public bool DoesAnOfferAlreadyExistWithinTheTimeframeProvided(int RetailerId, DateTime ValidFrom, DateTime ValidTo)
{
bool result = true;
try
{
// Obtain the current list of coupons associated to the retailer.
List<RetailerCoupon> retailerCoupons = PayPalInStore.Data.RetailerCoupon.Find(x => x.RetailerId == RetailerId).ToList();
// Loop through each coupon and see if the timeframe provided in the NEW coupon doesnt fall between any EZISTING coupon.
if (retailerCoupons != null)
{
foreach (RetailerCoupon coupon in retailerCoupons)
{
DateTime retailerCouponValidFrom = coupon.DateValidFrom;
DateTime retailerCouponValidTo = coupon.DateExpires;
if ((ValidFrom <= retailerCouponValidFrom && ValidTo <= retailerCouponValidFrom) || (ValidFrom >= retailerCouponValidTo && ValidTo >= retailerCouponValidTo))
{
return false;
}
}
}
return result;
}
catch (Exception ex)
{
this.errorManager.LogError("DoesAnOfferAlreadyExistWithinTheTimeframeProvided failed", ex);
return result;
}
}
Upvotes: 0
Reputation: 113242
If you don't have different criteria for sorting, then start by maintaining your list in order. Since no previously-added object is allowed to overlap, then once you know the point where you would add a new object you need only compare the single objects at either side to be sure the new object is allowed. You also only need to consider whether the end date of the "earlier" object is overlaps with the start date of the "later" object, as this ordering makes the other possibility for an overlap irrelevant.
Hence as well as simplifying the question of detecting overlaps, we can reduce the complexity from O(n) of to O(log n), as rather than compare with all existing items, we compare with 0-2 we've found through an O(log n) search.
private class MembershipComparer : IComparer<Membership>
{
public int Compare(Membership x, Membership y)
{
return x.StartDate.CompareTo(y.StartDate);
}
}
private static bool AddMembership(List<Membership> lst, Membership ms)
{
int bsr = lst.BinarySearch(ms, new MembershipComparer());
if(bsr >= 0) //existing object has precisely the same StartDate and hence overlaps
//(you may or may not want to consider the case of a zero-second date range)
return false;
int idx = ~bsr; //index to insert at if doesn't match already.
if(idx != 0)
{
Membership prev = lst[idx - 1];
// if inclusive ranges is allowed (previous end precisely the same
// as next start, change this line to:
// if(!prev.EndDate.HasValue || prev.EndDate > ms.StartDate)
if(prev.EndDate ?? DateTime.MaxValue >= ms.StartDate)
return false;
}
if(idx != lst.Count)
{
Membership next = lst[idx];
// if inclusive range is allowed, change to:
// if(!ms.EndDate.HasValue || ms.EndDate > next.StartDate)
if(ms.EndDate ?? DateTime.MaxValue >= next.StartDate)
return false;
}
lst.Insert(idx, ms);
return true;
}
The above returns false
if it was unable to add to the list. If it would be more appropriate to throw an exception, this is an easy modification.
Upvotes: 0
Reputation: 5423
Basically, a date range overlaps another if any of its endings are within the other range, or vice versa.
static bool AllowedToAdd(List<Membership> membershipList, Membership newItem)
{
return !membershipList.Any(m =>
(m.StartDate < newItem.StartDate &&
newItem.StartDate < (m.EndDate ?? DateTime.MaxValue))
||
(m.StartDate < (newItem.EndDate ?? DateTime.MaxValue) &&
(newItem.EndDate ?? DateTime.MaxValue) <= (m.EndDate ?? DateTime.MaxValue))
||
(newItem.StartDate < m.StartDate &&
m.StartDate < (newItem.EndDate ?? DateTime.MaxValue))
||
(newItem.StartDate < (m.EndDate ?? DateTime.MaxValue) &&
(m.EndDate ?? DateTime.MaxValue) <= (newItem.EndDate ?? DateTime.MaxValue))
);
}
With the usage:
if (AllowedToAdd(membershipList, newItem))
membershipList.Add(newItem);
Upvotes: 18
Reputation: 113402
Here's a solution (missing null
argument-validation, and validation within Membership
that EndDate > StartDate
) using Collection<T>
:
public class Membership
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; } // If null then it lasts forever
private DateTime NullSafeEndDate { get { return EndDate ?? DateTime.MaxValue; } }
private bool IsFullyAfter(Membership other)
{
return StartDate > other.NullSafeEndDate;
}
public bool Overlaps(Membership other)
{
return !IsFullyAfter(other) && !other.IsFullyAfter(this);
}
}
public class MembershipCollection : Collection<Membership>
{
protected override void InsertItem(int index, Membership member)
{
if(CanAdd(member))
base.InsertItem(index, member);
else throw new ArgumentException("Ranges cannot overlap.");
}
public bool CanAdd(Membership member)
{
return !this.Any(member.Overlaps);
}
}
Upvotes: 2
Reputation: 2340
A condition like this should do the trick:
newItem.StartDate <= range.EndDate && newItem.EndDate.HasValue && newItem.EndDate >= range.StartDate
Upvotes: 4