计算两个日期时间之间的工作小时数

6
给定两个日期时间,如何计算它们之间的工作小时数是最好的方式?考虑到工作时间是周一8点至5:30,周二至周五8:30至5:30,并且任何一天都可能是公共假日。
这是我的尝试,看起来效率极低,因为迭代次数很多,而IsWorkingDay方法会查询数据库以查看该日期时间是否是公共假日。
有人能提出任何优化或替代方案吗?
 public decimal ElapsedWorkingHours(DateTime start, DateTime finish)
        {

            decimal counter = 0;

            while (start.CompareTo(finish) <= 0)
            {   
                if (IsWorkingDay(start) && IsOfficeHours(start))
                {
                    start = start.AddMinutes(1);
                    counter++;
                }
                else
                {
                    start = start.AddMinutes(1);
                }
            }

            decimal hours;

            if (counter != 0)
            {
                hours = counter/60;
            }

            return hours;
        }

你们有多少公共假期啊? ;P - leppie
9个回答

3

在你开始优化之前,先问自己两个问题:

a)它是否有效?

b)它是否太慢?

只有当两个问题的答案都是“是”时,你才可以开始优化。

除此之外

  • 在开始日期和结束日期,你只需要关注分钟和小时。中间的日期显然是完整的9/9.5个小时,除非它们是节假日或周末
  • 不需要检查周末是否为节假日

以下是我的建议:

// Normalise start and end    
while start.day is weekend or holiday, start.day++, start.time = 0.00am
    if start.day is monday,
        start.time = max(start.time, 8am)
    else
        start.time = max(start.time, 8.30am)
while end.day is weekend or holiday, end.day--, end.time = 11.59pm
end.time = min(end.time, 5.30pm)

// Now we've normalised, is there any time left?    
if start > end
   return 0

// Calculate time in first day    
timediff = 5.30pm - start.time
day = start.day + 1
// Add time on all intervening days
while(day < end.day)
   // returns 9 or 9.30hrs or 0 as appropriate, could be optimised to grab all records
   // from the database in 1 or 2 hits, by counting all intervening mondays, and all
   // intervening tue-fris (non-holidays)
   timediff += duration(day) 

// Add time on last day
timediff += end.time - 08.30am
if end.day is Monday then
    timediff += end.time - 08.00am
else
    timediff += end.time - 08.30am

return timediff

你可以这样做: SELECT COUNT(DAY) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End GROUP BY DAY 来计算落在星期一、星期二、星期三等等的假日数量。可能有一种方法让SQL只计算星期一和非星期一,不过目前想不到什么办法。

是的,这有点回避了我的问题。 - Dan
1
这取决于你正在做什么。如果它足够快,则从商业角度来看,进一步优化就没有意义了。 - Airsource Ltd
@CynicalTyler,这就是为什么我在问这个问题,请提供一个更好的方法,否则你会浪费你的时间和我的时间。 - Dan
太多的假设了。我可以轻松地构建一个案例,证明它不需要扩展。你也可以构建一个需要扩展的案例。此外,一个慢速算法仍然可能是在当时使用的正确算法,特别是如果稍后容易替换。正如你所说,这是商业案例的一部分。 - Airsource Ltd

1
特别是考虑到IsWorkingDay方法会访问数据库来查看那一天是否为公共假日,如果问题不在于数据量而在于查询次数,建议在开始时从数据库中查询所需的整个日期范围的工作日数据,而不是在每个循环迭代中进行查询。

我明白你的概念,但是干净地实现它才是问题所在。而且我认为即使不考虑查询,我的方法仍然很差。 - Dan
不要使用只接受一个日期参数的IsWorkingDay()方法,而是实现一个HolidayCount()方法,该方法接受一个日期范围,并返回该范围内的假日数量。 - Joel Coehoorn
如果有任何事情使解决方案在代码方面不够高效,那么我将不得不在更多的天数上进行递增计数。 - Dan

1

看一下TimeSpan类。它可以给你任意两个时间之间的小时数。

一个单独的数据库调用也可以获取你所需的两个时间之间的假期,大致如下:

SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End

将该计数乘以8并从总工时中减去。
-Ian
编辑:回应下面的情况,如果你的假期不是一个固定的小时数。你可以在DB中保留HolidayStart和HolidayEnd时间,并在调用DB时将它们返回。使用与主程序相似的方法进行小时计算。

  1. 那个查询对实现没有帮助。
  2. 并非所有的日子都是8小时长,开始和结束日期时间可能在一天中间。
- Dan
在Ian的辩护中,关于2,你的问题说“给定两个日期”,而不是两个日期时间。 - Powerlord

1

还有递归解决方案。不一定高效,但很有趣:

public decimal ElapseddWorkingHours(DateTime start, DateTime finish)
{
    if (start.Date == finish.Date)
        return (finish - start).TotalHours;

    if (IsWorkingDay(start.Date))
        return ElapsedWorkingHours(start, new DateTime(start.Year, start.Month, start.Day, 17, 30, 0))
            + ElapsedWorkingHours(start.Date.AddDays(1).AddHours(DateStartTime(start.Date.AddDays(1)), finish);
    else
        return ElapsedWorkingHours(start.Date.AddDays(1), finish);
}

0

基于@OregonGhost的说法,不要使用一个接受日期并返回布尔值的IsWorkingDay()函数,而是使用一个HolidayCount()函数,它接受一个范围并返回表示该范围内假日数量的整数。 这里的诀窍是如果你处理边界开始和结束日期的部分日期,你可能仍然需要确定那些日期是否是假日。但即便如此,你也可以使用新方法确保你最多只需要三次DB调用。


0

使用@Ian的查询来检查日期之间的非工作日,找出哪些天不是工作日。然后进行一些数学计算,找出您的开始时间或结束时间是否落在非工作日,并减去差异。

因此,如果开始时间是周六中午,结束时间是周一中午,则查询应返回2天,从中计算出48小时(2 x 24)。如果您对IsWorkingDay(start)的查询返回false,则从24中减去从开始到午夜的时间,这将给您12个小时,或36个小时的总非工作时间。

现在,如果您的办公时间每天都相同,您可以做类似的事情。如果您的办公时间有点分散,您会遇到更多麻烦。

理想情况下,在数据库上进行单个查询,以获取两个时间(甚至日期)之间的所有办公时间。然后从该集合本地进行数学计算。


0
尝试按照以下方式进行编程:
TimeSpan = TimeSpan Between Date1 And Date2
cntDays = TimeSpan.Days 
cntNumberMondays = Iterate Between Date1 And Date2 Counting Mondays 
cntdays = cntdays - cntnumbermondays
NumHolidays = DBCall To Get # Holidays BETWEEN Date1 AND Date2
Cntdays = cntdays - numholidays 
numberhours = ((decimal)cntdays * NumberHoursInWorkingDay )+((decimal)cntNumberMondays * NumberHoursInMondayWorkDay )

这并没有考虑到星期一的假期。 - Airsource Ltd
或者是天的分数。或者如果日期超出办公时间。 - Dan

0

最有效的方法是计算总时间差,然后减去周末或节假日的时间。有很多边缘情况需要考虑,但可以通过分别计算范围的第一天和最后一天来简化。

Ian Jacobs 建议的 COUNT(*) 方法似乎是计算假期的好方法。无论使用什么方法,它都只能处理整天,您需要单独处理开始和结束日期。

计算周末天数很容易;如果您有一个返回星期一到星期日的数字(0到6)的 Weekday(date) 函数,则如下所示:

saturdays = ((finish - start) + Weekday(start) + 2) / 7;
sundays = ((finish - start) + Weekday(start) + 1) / 7;

注意:(finish - start) 不是字面意思,应该替换为计算天数的内容。

-1
Dim totalMinutes As Integer = 0

For minute As Integer = 0 To DateDiff(DateInterval.Minute, contextInParameter1, contextInParameter2)
    Dim d As Date = contextInParameter1.AddMinutes(minute)
    If d.DayOfWeek <= DayOfWeek.Friday AndAlso _
       d.DayOfWeek >= DayOfWeek.Monday AndAlso _
       d.Hour >= 8 AndAlso _
       d.Hour <= 17 Then
        totalMinutes += 1
    Else
        Dim test = ""
    End If
Next minute

Dim totalHours = totalMinutes / 60

小菜一碟!

干杯!


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