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

9 回答 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 BETWEEN @Start AND @End GROUP BY DAY 之类的操作

计算周一、周二、周三等节假日的数量。可能是一种让 SQL 只计算星期一和非星期一的方法,尽管目前想不出任何东西。

于 2008-09-26T19:20:17.150 回答
1

看看 TimeSpan 类。这将为您提供任意 2 次之间的时间。

一个 DB 调用也可以获得您两次之间的假期;类似于:

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

将该计数乘以 8,然后从总小时数中减去。

-伊恩

编辑:回应下面,如果你是假期不是固定的小时数。你可以在你的数据库中保留一个HolidayStart和一个HolidayEnd时间,并且也可以将它们从调用中返回到数据库。做一个小时计数,类似于您为主要例程选择的任何方法。

于 2008-09-26T19:23:19.090 回答
1

特别是考虑到 IsWorkingDay 方法会访问数据库以查看那天是否是公共假期

如果问题是查询次数而不是数据量,请在开始时从数据库中查询您需要的整个日期范围的工作日数据,而不是在每个循环迭代中查询。

于 2008-09-26T19:18:55.547 回答
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);
}
于 2008-09-26T19:42:45.410 回答
0

基于@OregonGhost 所说的,而不是在接受一天并返回布尔值时使用 IsWorkingDay() 函数,而是使用一个接受范围并返回一个整数的 HolidayCount() 函数,该整数给出该范围内的假期数。这里的诀窍是,如果您要处理边界开始和结束日期的部分日期,您可能仍需要确定这些日期本身是否是假期。但即便如此,您也可以使用新方法来确保您最多需要对 DB 的三个调用。

于 2008-09-26T19:26:41.563 回答
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 )
于 2008-09-26T19:37:31.757 回答
0

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

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

计算周末天数很容易;如果你有一个函数 Weekday(date),它从星期一返回 0 到星期日返回 6,它看起来像这样:

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

注意: (finish - start) 不是字面意思,而是用计算时间跨度的东西来代替它。

于 2008-09-26T19:47:38.410 回答
0

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

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

现在,如果你每天的办公时间都一样,你就会做类似的事情。如果你的办公时间有点分散,你会遇到更多的麻烦。

理想情况下,对数据库进行一次查询,即可为您提供两次(或什至日期)之间的所有办公时间。然后从该集合中本地进行数学运算。

于 2008-09-26T19:51:33.750 回答
-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

小菜一碟!

干杯!

于 2010-08-30T18:38:18.350 回答