计算VB.NET中重叠日期范围内的天数

时间:2017-06-15 13:47:57

标签: vb.net date

我有大量的日期FROM和TO对,我需要计算其中的所有日期。但如果两个范围重叠,那么重叠天数不应计算两次。

这里是我所有日子的代码:

Dim total_days_used = 0
For Each row As DataRow In MY_DATA.Tables(0).Rows
    Dim total_days As Double = 0

    Dim date_from = MY_FROM_DATE_FROM_DATA
    Dim date_to = MY_TO_DATE_FROM_DATA

    Dim span = date_to - date_from

    total_days = span.TotalDays '=4
    total_days_used += total_days
Next

我无法找到一种简单的方法来减去重叠的天数,甚至可以跟踪它们。我想这将是一种方法,另一种方法是组合重叠范围,直到我最终得到一组没有重叠的范围,但这似乎也太复杂了。应该有一个简单的方法来做到这一点?

3 个答案:

答案 0 :(得分:1)

这样的事情应该有效。 我们首先命令范围以了解最后一个是否与当前重叠。 然后计算重叠的天数并从总数中减去。 如果您希望范围包含,则+1天,否则将其删除。

Private Sub Main()
    Dim ranges = New List(Of Range)() From { _
        {New Range(New DateTime(2000, 1, 1), New DateTime(2000, 1, 30))}, _
        {New Range(New DateTime(2000, 1, 28), New DateTime(2000, 2, 3))} _
    }
    CountNonOverlappingsDays(ranges).Dump() '34 days
End Sub

Private Function CountNonOverlappingsDays(ranges As IEnumerable(Of Range)) As Integer    
    Dim isFirst = True
    Dim last As Range = Nothing
    Dim overlapping As Integer = 0
    Dim total As Integer = 0
    For Each current In ranges.OrderBy(Function(r) r.[To])
        total += CInt((current.[To] - current.From).TotalDays) + 1 '+1 if we want Inclusive count

        If isFirst Then
            isFirst = False
            last = current
            Continue For
        End If
        If (last.From <= current.[To]) AndAlso (last.[To] >= current.From) Then
            Dim start = current.From
            Dim [end] = last.[To]
            overlapping += CInt(([end] - start).TotalDays) + 1 '+1 if we want Inclusive count
        End If
        last = current
    Next
    Return total - overlapping
End Function

   Public Class Range
    Public Sub New([from] As DateTime, [to] As DateTime)
        [From] = [from]
        [To] = [to]
    End Sub
    Public Property [From]() As DateTime
        Get
            Return m_From
        End Get
        Set
            m_From = Value
        End Set
    End Property
    Private m_From As DateTime
    Public Property [To]() As DateTime
        Get
            Return m_To
        End Get
        Set
            m_To = Value
        End Set
    End Property
    Private m_To As DateTime
End Class

答案 1 :(得分:0)

使用以下(或类似的DateRange类)。

Class DateRange
    Implements IEnumerable(Of DateTime)
    Public Sub New(startDate As DateTime, endDate As DateTime)
        me.StartDate = startDate
        me.EndDate = endDate
    End Sub

    Public ReadOnly Property StartDate() As DateTime
    Public ReadOnly Property EndDate() As DateTime

    Public Function GetEnumerator() As IEnumerator(Of DateTime) Implements IEnumerable(of DateTime).GetEnumerator
        Return Enumerable.Range(0, 1 + EndDate.Subtract(StartDate).Days).[Select](Function(offset) StartDate.AddDays(offset)).GetEnumerator()
    End Function

    Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return GetEnumerator()
    End Function
End Class

这个类的重要部分是它是可枚举的。也就是说,它在for each循环(以及其他内容)中使用时,返回开始日期和结束日期(包括)之间的日期序列。

然后你可以使用这样的代码来得到你想要的东西:

Dim ranges = New List(Of DateRange)()
ranges.Add(New DateRange(#2017/1/1#,#2017/1/10#))
ranges.Add(New DateRange(#2017/1/8#,#2017/1/20#))

Dim merged = ranges.SelectMany(Function(r) r.AsEnumerable()).Distinct().OrderBy(Function(dt) dt)
Console.WriteLine($"{merged.Count()} days: ")
For Each [date] As DateTime In merged
    Console.WriteLine([date].ToShortDateString())
Next
Console.ReadLine()

这使用LINQ SelectMany函数将列表中所有IEnuemrable个实例的日期列表(由DateRange DateRange创建)展平为单个列表DateTime。然后它获取不同的(唯一)值,并对列表进行排序。

输出显示包含2个DateRange个实例的列表的输出。第一个是2017年1月1日至2017年1月10日,第二个是2017年1月8日至2017年1月20日。这些范围在8日,9日和10日重叠。如您所见,这些重叠日期仅包含一次。

生成以下输出:

  

20天:
  2017年1月1日
  2017年1月2日
  2017年1月3日
  2017年1月4日
  2017年1月5日
  2017年1月6日
  2017年1月7日
  2017年1月8日
  2017年1月9日
  2017年1月10日
  2017年1月11日
  2017年1月12日
  2017年1月13日
  2017年1月14日
  2017年1月15日
  2017年1月16日
  2017年1月17日
  2017年1月18日
  2017年1月19日
  2017年1月20日

答案 2 :(得分:0)

试试这个:

Dim range As New List(Of DateTime)()

For Each row As DataRow In MY_DATA.Tables(0).Rows
    Dim date_from = MY_FROM_DATE_FROM_DATA
    Dim date_to = MY_TO_DATE_FROM_DATA
    range.AddRange(Enumerable.Range(0, (date_from-date_to).TotalDays).Select(d => date_from.AddDays(d))
Next
Dim total_days_used As Integer = range.Distinct().Count()

魔术分为两部分。第一部分使用Enumerable.Range()来实际投影每个范围中的所有日期,因此我们可以将它们添加到列表中。然后第二部分只取该列表中不同的成员并计算它们。