计算2个日期时间之间经过的工作时间
给定两个日期时间。计算两者之间工作时间的最佳方法是什么。考虑到工作时间是星期一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; }
解决方案
especially considering the IsWorkingDay method hits the DB to see if that day is a public holiday
如果问题是查询数量而不是数据量,请从数据库中查询我们在一开始所需的全天范围内的工作日数据,而不是在每次循环迭代中进行查询。
在开始对其进行优化之前,请问自己两个问题。
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
你可以做类似的事情
从@Start和@End GROUP BY DAY之间的假日中选择COUNT(DAY)天
计算星期一,星期二,星期三等的假日数量。可能是使SQL仅在星期一和非星期一进行计数的一种方法,尽管目前无法考虑任何事情。
看一下TimeSpan类。这将为我们提供任意两次之间的时间。
一个数据库调用也可以让我们两次休假。类似于以下内容:
SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End
将该数字乘以8,然后从总工作时间中减去。
-伊恩
编辑:作为对以下内容的回应,如果我们休假的时间不是固定的小时数。我们可以在数据库中保留一个" HolidayStart"和" HolidayEnd"时间,并将它们从调用中也返回给数据库。进行一个小时的计数,类似于我们决定使用主例程的任何方法。
建立在@OregonGhost所说的内容之上,而不是在时使用IsWorkingDay()函数来接受一天并返回一个布尔值,而是要使用一个HolidayCount()函数来接受一个范围并返回一个给出该范围内的Holidays数量的整数。这里的窍门是,如果我们要处理边界日期的开始和结束日期的部分日期,则可能仍需要确定这些日期本身是否就是假期。但是即使那样,我们也可以使用新方法来确保最多需要三个对DB的调用。
尝试以下方法:
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 )
还有递归解决方案。不一定高效,但有很多乐趣:
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); }
最有效的方法是计算总时差,然后减去周末或者假日的时间。有很多边缘情况需要考虑,但是我们可以通过取范围的第一天和最后一天并分别计算它们来简化此情况。
Ian Jacobs建议的COUNT(*)方法似乎是计算假期的好方法。无论我们使用什么,它都将处理整天,我们需要分别涵盖开始日期和结束日期。
周末数很容易。如果我们有一个函数Weekday(date),它从星期一返回0到星期日返回6,则它看起来像这样:
saturdays = ((finish - start) + Weekday(start) + 2) / 7; sundays = ((finish - start) + Weekday(start) + 1) / 7;
注意:(finish start)并非从字面上看,应将其替换为可计算以天为单位的时间跨度的内容。
使用@Ian的查询在日期之间进行检查,以找出哪些日期不是工作日。然后进行一些数学运算,以确定开始时间或者结束时间是否在非工作日,然后减去差值。
因此,如果开始时间是星期六中午,结束时间是星期一中午,则查询应给我们2天的时间,从中计算出48小时(2 x 24)。如果我们在IsWorkingDay(start)上的查询返回false,请从24减去从开始到午夜的时间,这将使我们获得12个小时,或者总计36个小时的非工作时间。
现在,如果我们每天的办公时间相同,那么我们将执行类似的操作。如果办公时间有点分散,我们会遇到更多麻烦。
理想情况下,在数据库上进行一次查询,即可查询两次(甚至日期)之间的所有办公时间。然后从该集合本地进行数学运算。
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
小菜一碟!
干杯!