简体   繁体   中英

Check if a date range is within a date range

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

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);

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))

像这样的条件应该可以解决问题:

newItem.StartDate <= range.EndDate && newItem.EndDate.HasValue && newItem.EndDate >= range.StartDate

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);
   }
}

A bit late but I couldn't find this pattern anywhere in the answers/comments.

    if (startDate1 <= endDate2 && startDate2 <= endDate1)
    {
     // Overlaps.
    }
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;
        }
    }

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.

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM