Confused over some date/time calculations
So, I am trying to calculate the time between two dates that fits certain criteria (here: work / non-work) and I'm confused about the results as I can't find out why it's wrong.
But first, some code;
**Input Date A:** 2009-01-01 2:00 pm
**Input Date B:** 2009-01-02 9:00 am
So, as you can see, the total timespan (calculated e.g. by DateB.Substract(DateA)
) is 19 hours.
I now want to calculate how many hours in this timespan are "non-work" hours, based on an average work day from 8am to 5pm - Result should be 15 hours (So, 19 - 15 = 4 hours total work time) (2:00 pm to 5:00 pm plus 8:00 am to 9:00 am)
But, following my code
DateTime _Temp = new DateTime(2009, 1, 1, 14, 0, 0);
DateTime _End = new DateTime(2009, 1, 2, 9, 0, 0);
int _WorkDayStart = 8;
int _WorkDayEnd = 17;
int _Return = 0;
while (_End > _Temp)开发者_如何学编程
{
if (_Temp.Hour <= _WorkDayStart || _Temp.Hour >= _WorkDayEnd)
_Return++;
_Temp = _Temp.AddHours(1);
}
the result is 16 hours (19 - 16 = 3 hours total work time) - I don't see where the mistake is, so there is one hour missing?! I refactored it on paper and it should work as intended... but doesn't :-/
Anyone sees the mistake?
You're counting both ends as non-work, instead of just one. This line:
if (_Temp.Hour <= _WorkDayStart || _Temp.Hour >= _WorkDayEnd)
should probably be:
if (_Temp.Hour < _WorkDayStart || _Temp.Hour >= _WorkDayEnd)
You're effectively stepping through "start hours". So 8am itself should count as a work hour, because it's the start of a work hour, but 5pm won't because it's the start of a non-work hour.
I would also strongly advise you to either put braces around the body of the if
statement or at least indent the following line. (I'd further advise you to use camelCase
for local variables, but that's just a convention thing - I've never seen C# code written with that convention for local variables before now. You may want to read Microsoft's naming conventions document - it doesn't specify local variables, but they;re generally in camel case.)
Finally, I personally find it easier to read conditions where the "thing that's changing" is on the left - so I'd change your loop condition to:
while (_Temp < _End)
An alternative is to change it into a for loop. With all of these changes, the code would be:
DateTime start = new DateTime(2009, 1, 1, 14, 0, 0);
DateTime end = new DateTime(2009, 1, 2, 9, 0, 0);
int workDayStart = 8;
int workDayEnd = 17;
int nonWorkHours = 0;
for (DateTime time = start; time < end; time = time.AddHours(1))
{
if (time.Hour < workDayStart || time.Hour >= workDayEnd)
{
nonWorkHours++;
}
}
Finally, extract this into a method:
public static int CountNonWorkHours(DateTime start, DateTime end,
int workDayStart, int workDayEnd)
{
int nonWorkHours = 0;
for (DateTime time = start; time < end; time = time.AddHours(1))
{
if (time.Hour < workDayStart || time.Hour >= workDayEnd)
{
nonWorkHours++;
}
}
return nonWorkHours;
}
EDIT: Regarding konamiman's suggestion... yes, looping over each hour is very inefficient. However, it's relatively easy to get right. Unless you're going to be doing this a lot with long time periods, I'd use this fairly simple code. It would be very easy to end up with off-by-one errors in various situations if you tried to do a per-day version. While I don't like inefficient code, I don't mind it if it's not hurting me :)
If you plan to reuse this code, I would refactor it to avoid the loop. You could just multiply the number of whole days by the labour hours per day, then treat the first and the last day of the interval as special cases.
You could also use this to avoid a loop
DateTime startDate = new DateTime(2009, 1, 1, 14, 0, 0);
DateTime endDate = new DateTime(2009, 1, 2, 9, 0, 0);
int startTime = 8;
int endTime = 17;
int ret = ((endDate.Subtract(startDate).Days - 1) * 8)
+ (startDate.Hour >= startTime && startDate.Hour < endTime ? endTime - startDate.Hour : 0)
+ (endDate.Hour > startTime && endDate.Hour <= endTime ? endDate.Hour - startTime : 0);
精彩评论