检查一个日期范围是否在另一个日期范围内。

12

我有以下的类:

public class Membership
{
    public DateTime StartDate { get; set; }
    public DateTime? EndDate { get; set; } // If null then it lasts forever
}

我需要确保在添加以下列表中的新项目时,新项目的日期不会与现有项目重叠:

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 }
};
例如执行以下操作:
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);

我以为这很简单,但是迄今为止,我的尝试都失败了,我开始把自己搞糊涂了,希望有人能分享一些类似的经验。谢谢。

8个回答

17

如果一个日期范围的任何结束日期在另一个范围内,或者反过来,那么这两个日期范围就重叠了。

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

使用方式:

if (AllowedToAdd(membershipList, newItem))
    membershipList.Add(newItem);

1
谢谢大家的回复,但我更喜欢这个,因为它最容易理解了,哈哈。 - nfplee

7

如果我理解正确的话 - 你想确保日期范围2不在日期范围1内?

例如:

startDate1 = 01/01/2011

endDate1 = 01/02/2011

并且

startDate2 = 19/01/2011

endDate2 = 10/02/2011

这应该是一个简单的案例:
if ((startDate2 >= startDate1 &&  startDate2 <= endDate1) || 
    (endDate2   >= startDate1 && endDate2   <= endDate1))

你还应该检查空值。 - jimplode

4
像这样的条件应该可以解决问题:
newItem.StartDate <= range.EndDate && newItem.EndDate.HasValue && newItem.EndDate >= range.StartDate

这是非常合乎逻辑和天才的解决方案。谢谢@Joachim VR。 - Haider Ali Wajihi

2

以下是使用Collection<T>解决方案(包括缺少的null参数验证和在Membership中进行的EndDate > StartDate验证):

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

2

有点晚了,但我在答案/评论中找不到这个模式。

    if (startDate1 <= endDate2 && startDate2 <= endDate1)
    {
     // Overlaps.
    }

0
如果您没有不同的排序标准,那么首先要保持列表有序。由于不允许重叠先前添加的对象,因此一旦确定添加新对象的点,您只需要比较两侧的单个对象,以确保新对象被允许。您只需要考虑“较早”对象的结束日期与“较晚”对象的开始日期是否重叠,因为这种顺序使得另一个重叠可能性变得无关紧要。
因此,除了简化检测重叠的问题外,我们还可以将复杂度从O(n)降低到O(log n),因为我们与通过O(log n)搜索找到的0-2进行比较,而不是与所有现有项进行比较。
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;
}

以上代码返回false,如果无法添加到列表中。如果抛出异常更合适,则可以轻松进行修改。

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

0
我想出了以下方法来检查日期是否重叠,可能不是最有效的方法,但我希望这能有所帮助。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接