如何通过设置时间限制并排除周末来对 DateTime 小时数求和

How to sum DateTime hours by setting limit of time and excluding weekends

我有一个包含如下列的 DataGridView:

持续时间(小时) 日期时间 1 日期时间2
12 2020-8-108:00 ?
28 ? ?

Datetime2 的值是 Duration 和 Datetime1 之间的小时数总和;格式应为 yyyy-mm-dd H:mm.
时间限制是从 8 点到 16 点,因此如果求和结果高于 16 点,日期将从 8 点开始增加。
它还应该不包括周末(周六和周日)

我期望的结果是这样的:

持续时间(小时) 日期时间 1 日期时间2
12 2021-8-108:00 2021-8-1112:00
27 2021-8-11 12.00 2021-8-1615:00

我的代码不考虑时间限制和周末排除:

For i As Integer = 0 To dgv.Rows.Count - 1
    Dim hoursToAdd As Double
    Dim datefrom As DateTime
    If (Double.TryParse(dgv.Rows(i).Cells("Duration").Value, hoursToAdd) AndAlso DateTime.TryParse(dgv.Rows(i).Cells("Datetime1").Value, datefrom)) Then
        Dim dateTo = datefrom.AddHours(hoursToAdd)
        dgv.Rows(i).Cells("Datetime2").Value = dateTo.ToString("yyyy-MM-dd H:mm")

        For j As Integer = 1 To dgv.Rows.Count - 2
            If dgv.Rows(j).Cells("Datetime2").Value IsNot DBNull.Value Then
                dgv.Rows(j).Cells("Datetime1").Value = dgv.Rows(j - 1).Cells("Datetime2").Value
            End If
        Next
    End If
Next

如果没有时间限制,结果如下T.T

持续时间(小时) 日期时间 1 日期时间2
12 2021-8-108:00 2021-8-1020:00
27 2021-8-10 20.00 2021-8-1119:00

排除周末

尝试在 dateFrom 声明后使用 DayOfWeek。喜欢

Dim datefrom As DateTime
If datefrom.DayOfWeek = 0 or datefrom.DayOfWeek = 6 Then
    ' Do nothing
Else
    ' Your If Statement Here
    ' If (Double.TryParse(dgv.Rows(i).Cells("Duration").Value...
End If

Dim datefrom As DateTime
If datefrom.DayOfWeek <> 0 AND datefrom.DayOfWeek <> 6 Then
    ' Your If Statement Here
    ' If (Double.TryParse(dgv.Rows(i).Cells("Duration").Value...
End If

The value of the constants in the DayOfWeek enumeration ranges from DayOfWeek.Sunday to DayOfWeek.Saturday. If cast to an integer, its value ranges from zero (which indicates DayOfWeek.Sunday) to six (which indicates DayOfWeek.Saturday).

DateTime.DayOfWeek Property

这是一个可以满足您要求的函数。它首先创建 DateTime variable for 16:00 on given day. It uses this variable with the given start time to calculate a TimeSpan 剩余小时数,直到 16:00。然后它从所需的总小时数中减去该数字并移动到第二天,检查它是否不是周末。这在循环中重复,直到所需的小时数为 0。

Function AddHours(start As DateTime, hours As Integer) As DateTime

    ' Copy number of hours to add
    Dim hoursLeft As Integer = hours
    Dim currentDate As DateTime = start

    Do While hoursLeft > 0

        ' Create a new variable representing 16:00
        Dim endOfDay As DateTime = New DateTime(currentDate.Year, currentDate.Month, currentDate.Day, 16, 0, 0)

        ' Calculate the number of usable hours
        Dim dayTimeSpan As TimeSpan = endOfDay - currentDate

        If hoursLeft > dayTimeSpan.TotalHours Then
            ' Subtract usable hours from hoursLeft
            hoursLeft -= dayTimeSpan.TotalHours
        Else
            ' End date is found
            Return currentDate.AddHours(hoursLeft)
        End If

        ' Set currentDate to next working day
        Select Case currentDate.DayOfWeek
            Case DayOfWeek.Friday
                currentDate = currentDate.AddDays(3)
            Case Else
                currentDate = currentDate.AddDays(1)
        End Select

        ' Set hour to 8:00
        currentDate = New DateTime(currentDate.Year, currentDate.Month, currentDate.Day, 8, 0, 0)

    Loop

End Function

下面显示了如何在给定持续时间(作为 TimeSpan)、起始 Date/Time 值、工作日开始时间和工作日结束时间的情况下计算结束 Date/Time 值。

注意:下面的代码已经使用“用法”下列出的值进行了测试。

计算结束DT

Private Function CalculateEndDT(duration As TimeSpan, dateTime1 As DateTime, workdayStart As DateTime, workDayEnd As DateTime) As DateTime
    Dim endDT As DateTime = DateTime.MinValue

    'since workday starts and ends on same day
    'use the year, month, and day of dateTime1
    Dim wdStart As DateTime = New DateTime(dateTime1.Year, dateTime1.Month, dateTime1.Day, workdayStart.Hour, workdayStart.Minute, workdayStart.Second)
    Dim wdEnd As DateTime = New DateTime(dateTime1.Year, dateTime1.Month, dateTime1.Day, workDayEnd.Hour, workDayEnd.Minute, workDayEnd.Second)

    'calculate hours in a workday
    Dim tsWorkday As TimeSpan = wdEnd - wdStart

    Dim numDays As Integer = 0

    Dim timeRemaining As TimeSpan = duration - (wdEnd - dateTime1)
    'Debug.WriteLine("timeRemaining - " & timeRemaining.Hours & ":" & timeRemaining.Minutes & ":" & timeRemaining.Seconds)

    If timeRemaining.TotalMinutes > 0 Then

        Do
            numDays += 1 'increment

            'calculate
            timeRemaining = timeRemaining - tsWorkday

            If timeRemaining.TotalMinutes <= 0 Then
                Dim tempTs As TimeSpan = New TimeSpan(24 * numDays, 0, 0)
                endDT = wdEnd.Add(tempTs)
                endDT = endDT.Add(timeRemaining)
            End If
        Loop While timeRemaining.TotalMinutes > 0

    Else
        endDT = wdEnd.Subtract(timeRemaining)
    End If

    'add day for each weekend day
    If endDT.DayOfWeek = 0 Then
        endDT = endDT.AddDays(1)
    ElseIf endDT.DayOfWeek = 6 Then
        endDT = endDT.AddDays(2)
    End If

    Return endDT
End Function

给定:

  • 持续时间:12 小时
  • dateTime1: 2021/08/10 08:00:00(您要从中开始计算的 Date/Time)
  • 工作日开始: 08:00
  • 工作日结束: 16:00

这转化为以下内容(见下文):

  • 持续时间:新时间跨度(0, 12, 0, 0) - 这是 12 小时
  • dateTime1: New DateTime(2021, 8, 10, 8, 0, 0) - 这是 2021/08/10 08:00:00
  • workdayStart: New DateTime(2021, 8, 10, 8, 0, 0) - 未使用年、月和日的值
  • workdayEnd: New DateTime(2021, 8, 10, 16, 0, 0) - 未使用年、月和日的值

注意workdayStartworkdayEnd不使用年、月、日的值,但小时、分钟、第二个被使用。

用法:

Dim endDT1 As DateTime = CalculateEndDT(New TimeSpan(0, 12, 0, 0), New DateTime(2021, 8, 10, 8, 0, 0), New DateTime(2021, 8, 10, 8, 0, 0), New DateTime(2021, 8, 10, 16, 0, 0))
Dim endDT2 As DateTime = CalculateEndDT(New TimeSpan(0, 27, 0, 0), New DateTime(2021, 8, 11, 12, 0, 0), New DateTime(2021, 8, 11, 8, 0, 0), New DateTime(2021, 8, 11, 16, 0, 0))

Debug.WriteLine(endDT1.ToString("yyyy-M-d HH:mm"))
Debug.WriteLine(endDT2.ToString("yyyy-M-d HH:mm"))

结果:

2021-8-11 12:00
2021-8-16 15:00

我相信有很多方法可以做到这一点。以下是一种可能的解决方案。

下面的代码做出了一些假设……

  1. Datetime1 可以是任何日期。这个日期可以是周末约会。这个日期可以是具有小时时间值的日期,该时间值可以是……8 点之前(上午 8 点)……或者……16 点之后(下午 4 点)。
  2. Datetime2 只能是不在周末(周六或周日)的日期,并且小时时间值将始终为……大于或等于 8(dt2.Hour >= 8)……并且……小于或等于 16 (dt.Hour <= 16)。基本上 Datetime2 将始终是一个工作日(周一至周五),时间为上午 8 点至下午 4 点。

如果 Datetime1 始终是工作日,并且在上午 8 点到下午 4 点之间……那么下面的代码可以简化。但是下面的代码支持第一个假设。

为了解决这个问题,一种特殊的方法可能会派上用场。此方法将采用 DateTime 对象和 return 从给定日期开始的下一个工作日(周一至周五)。注意……如果给定日期已经是工作日,则原始给定日期为 returned。这有什么帮助,因为我们可能会在日期中添加“天数”……我们可能需要多次检查周末。在这一点上这有什么帮助可能并不明显;但是,当您看到它在 main 方法中的使用方式时,它应该会变得更加清晰。基本上,这种方法只是用来减少代码重复。

Private Function GetNextWorkday(Date1 As DateTime) As DateTime
  Dim ReturnDate = Date1
  While ReturnDate.DayOfWeek.Equals(DayOfWeek.Saturday) Or
        ReturnDate.DayOfWeek.Equals(DayOfWeek.Sunday)
    ReturnDate = ReturnDate.AddDays(1)
  End While
  Return ReturnDate
End Function

接下来我们有一个方法,它接受一个 DateTime 对象 Date1 和一个 int32 Duration。此方法将 return 我们正在寻找的 Datetime2 值。

遍历下面的代码……首先我们假设给定日期 Date1 可能是周末(周六或周日)日期。所以第 1 步是使用我们上面的方法来获取从给定日期开始的下一个工作日日期。我们知道给定日期是否已经是工作日日期,那么我们上面的方法将简单地 return 相同的日期。这是通过以下行完成的...

Date1 = GetNextWorkday(Date1)

接下来我们创建一个名为 HoursLeftInt32 变量并将其设置为给定的 Duration 值。我们将使用此变量“倒计时”到 8 或更小的值。这就是 While 循环正在做的事情。需要注意的是,由于我们可能会添加多天,所以我们需要调用上面的方法来忽略周末。

while 循环结束后,HoursLeft 将是一个等于或小于 8 的值。这是我们需要添加 Date1 日期的小时数。添加这些最后几个小时后,我们需要进行最后一次检查以确保添加这些小时不会发送日期 HOUR 值 PAST 16(下午 4 点)。

如果在添加最后几个小时后,小时值大于 16,那么,我们将知道我们需要再添加一天并将剩余小时数添加到新日期,其中 HOUR 为 8(上午 8 点) ) 加上剩余时间。

示例:如果在添加最后几个小时后日期的 HOUR 值为 18,那么我们就会知道我们需要在日期上添加一 (1) 天,然后将小时设置为 (18-16=2) + 8 = 10。换句话说,18 的值是 16 点后 2 小时,因此我们想在下一个工作日的 8(上午 8 点)开始增加两 (2) 小时。同样,因为我们可能会在日期中添加一天,所以我们需要第三次调用上面的方法。我希望这是有道理的。

Private Function ComputeDate2(Date1 As DateTime, Duration As Int32) As DateTime
  Date1 = GetNextWorkday(Date1)
  If (Duration < 0) Then
    Duration = Math.Abs(Duration)
  End If
  Dim HoursLeft As Int32 = Duration
  While HoursLeft > 8
    HoursLeft -= 8
    Date1 = Date1.AddDays(1)
    Date1 = GetNextWorkday(Date1)
  End While
  Date1 = Date1.AddHours(HoursLeft)
  If Date1.Hour > 16 Then
    Dim dif As Int32 = Date1.Hour - 16
    Date1 = Date1.AddDays(1)
    Date1 = GetNextWorkday(Date1)
    Date1 = New DateTime(Date1.Year, Date1.Month, Date1.Day, 8 + dif, 0, 0)
  End If
  Return Date1
End Function

要对此进行测试并完成示例,请创建一个新的 VB winforms 解决方案,将 DataGridViewButton 拖放到表单上。该按钮用于遍历网格中的所有行并设置 Datetime2 单元格值。

Dim GridTable As DataTable

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  GridTable = GetData()
  DataGridView1.DataSource = GridTable
  DataGridView1.Columns("Date1").DefaultCellStyle.Format = "yyyy-MM-dd HH:mm"
  DataGridView1.Columns("Date2").DefaultCellStyle.Format = "yyyy-MM-dd HH:mm"
  DataGridView1.Columns("Date1").Width = 150
  DataGridView1.Columns("Date2").Width = 150
End Sub

Function GetData() As DataTable
  Dim dt = New DataTable
  dt.Columns.Add("Duration", GetType(Int32))
  dt.Columns.Add("Date1", GetType(DateTime))
  dt.Columns.Add("Date2", GetType(DateTime))
  dt.Rows.Add(12, New DateTime(2021, 8, 10, 8, 0, 0))
  dt.Rows.Add(27, New DateTime(2021, 8, 11, 12, 0, 0))
  dt.Rows.Add(28, New DateTime(2021, 8, 11, 12, 0, 0))
  dt.Rows.Add(29, New DateTime(2021, 8, 11, 12, 0, 0))
  dt.Rows.Add(40, New DateTime(2021, 8, 1, 8, 0, 0))
  dt.Rows.Add(8, New DateTime(2021, 8, 7, 8, 0, 0))
  Return dt
End Function

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  Dim drv As DataRowView
  Dim Dur As Int32
  Dim date1 As DateTime
  For Each row As DataGridViewRow In DataGridView1.Rows
    If Not row.IsNewRow Then
      drv = CType(row.DataBoundItem, DataRowView)
      Dur = CType(drv("Duration"), Int32)
      date1 = CType(drv("Date1"), DateTime)
      If (date1.Hour > 16 Or date1.Hour < 8) Then
        date1 = New DateTime(date1.Year, date1.Month, date1.Day, 8, 0, 0)
      End If
      drv("Date2") = ComputeDate2(date1, Dur)
    End If
  Next
  DataGridView1.Refresh()
End Sub

我希望这是有道理的。