我正在寻找一些优雅的算法来计算多天的小时数,预定义的“工时 - 天 - 范围”。
一个真实的例子是:只计算租借物品的工作时间。
DataTime范围可以在定义的“工时 - 日 - 范围”内部或外部开始。
示例
我创建了一个包含5种不同场景的小例子。希望这更清楚。
我想到的唯一方法是在范围内每天进行一次for循环,并且在内部有多个复杂的if-trees。但我希望有人比我更聪明,能给我一些更快更优雅的提示。
非常感谢您的帮助! :)
更新1
基于Lashanes回答我是这样实现的......
public struct DateSpan
{
public DateTime begin, end;
public DateSpan(DateTime begin, DateTime end)
{
if (begin > end || end < begin)
throw new Exception("Not possible");
this.begin = begin;
this.end = end;
}
public DateTime Begin
{
get
{
return this.begin;
}
}
public DateTime End
{
get
{
return this.end;
}
}
public TimeSpan TimeSpan
{
get
{
return this.End - this.Begin;
}
}
public TimeSpan GetWorkTimeSpan(TimeSpan? workTimeBegin, TimeSpan? workTimeEnd)
{
if (this.Begin.Date == this.End.Date)
{
long totalWorkTimeTicks = Math.Min(workTimeEnd.Value.Ticks, this.End.Ticks) - Math.Max(workTimeBegin.Value.Ticks, this.Begin.Ticks);
return TimeSpan.FromTicks(totalWorkTimeTicks);
}
else
{
TimeSpan dailyWorkTime = TimeSpan.FromDays(1);
dailyWorkTime -= workTimeBegin ?? TimeSpan.Zero;
dailyWorkTime -= TimeSpan.FromDays(1) - workTimeEnd ?? TimeSpan.FromDays(1);
long totalDaysWorkTimeTicks = (int)(this.TimeSpan.TotalDays) * dailyWorkTime.Ticks;
long firstDayWorkTimeTicks = Math.Min(dailyWorkTime.Ticks, Math.Max(0, workTimeEnd.Value.Ticks - this.Begin.TimeOfDay.Ticks));
long lastDayWorkTimeTicks = Math.Min(dailyWorkTime.Ticks, Math.Max(0, this.End.TimeOfDay.Ticks - workTimeBegin.Value.Ticks));
return TimeSpan.FromTicks(firstDayWorkTimeTicks + totalDaysWorkTimeTicks + lastDayWorkTimeTicks);
}
}
}
和测试用例......
DateSpan dateRange1 = new DateSpan(new DateTime(2012, 01, 01, 07, 00, 00), new DateTime(2012, 01, 03, 15, 00, 00));
DateSpan dateRange2 = new DateSpan(new DateTime(2012, 01, 01, 02, 00, 00), new DateTime(2012, 01, 03, 20, 00, 00));
DateSpan dateRange3 = new DateSpan(new DateTime(2012, 01, 01, 04, 00, 00), new DateTime(2012, 01, 03, 23, 00, 00));
DateSpan dateRange4 = new DateSpan(new DateTime(2012, 01, 01, 23, 00, 00), new DateTime(2012, 01, 03, 09, 00, 00));
DateSpan dateRange5 = new DateSpan(new DateTime(2012, 01, 02, 12, 00, 00), new DateTime(2012, 01, 02, 20, 00, 00));
Debug.WriteLine(String.Format("dateRange1: {0}", dateRange1.GetWorkTimeSpan(new TimeSpan(04, 00, 00), new TimeSpan(17, 00 ,00)).TotalHours));
Debug.WriteLine(String.Format("dateRange2: {0}", dateRange2.GetWorkTimeSpan(new TimeSpan(04, 00, 00), new TimeSpan(17, 00, 00)).TotalHours));
Debug.WriteLine(String.Format("dateRange3: {0}", dateRange3.GetWorkTimeSpan(new TimeSpan(04, 00, 00), new TimeSpan(17, 00, 00)).TotalHours));
Debug.WriteLine(String.Format("dateRange4: {0}", dateRange4.GetWorkTimeSpan(new TimeSpan(04, 00, 00), new TimeSpan(17, 00, 00)).TotalHours));
Debug.WriteLine(String.Format("dateRange5: {0}", dateRange5.GetWorkTimeSpan(new TimeSpan(04, 00, 00), new TimeSpan(17, 00, 00)).TotalHours));
结果是..
dateRange1: 47 // should be 37h
dateRange2: 52 // should be 42h
dateRange3: 52 // should be 42h
dateRange4: 18 // should be 20h
dateRange5: -17628067 // should be 6h
我做错了什么?我想我理解Lashane解释的方式,但没有看到我的错误...... :(
答案 0 :(得分:1)
算法可能非常简单,我假设所有日期范围都舍入为小时,即没有分钟/秒
所以,要计算你需要的工作小时数:
int totalDaysHours = 14 * (int)((dtTwo - dtOne).TotalDays);
请注意我们将整理为止只有整整一天int firstDayHours = Math.Min(14, Math.Max(0, 18-dtOne.Hour));
int lastDayHours = Math.Min(14, Math.Max(0, dtTwo.Hour - 3));
注意 - 如果开始和结束日期相同,则需要转到另一个分支:
int totalWorkingHours = Math.min(18, dtTwo.Hour) - Math.max(4, dtOne.Hour);
这里的常数:
关于最小/最大的注释:
这是基于您的代码的工作实现:
if (this.Begin.Date == this.End.Date)
{
long totalWorkTimeHours = Math.Min(workTimeEnd.Hours+1, this.End.Hour) - Math.Max(workTimeBegin.Hours, this.Begin.Hour); // note + 1
return TimeSpan.FromHours(totalWorkTimeHours);
}
else
{
TimeSpan dailyWorkTime = TimeSpan.FromDays(1);
dailyWorkTime -= workTimeBegin;
dailyWorkTime -= TimeSpan.FromDays(1) - workTimeEnd;
long totalDaysWorkTimeHours = ((long)this.TimeSpan.TotalDays - 1) * (dailyWorkTime.Hours + 1); // note -1 for days (3rd January - 1st January = 1 whole day, not 2, +1 for hours
long firstDayWorkTimeHours = Math.Min(dailyWorkTime.Hours + 1, Math.Max(0, workTimeEnd.Hours + 1 - this.Begin.Hour)); // +1 hours
long lastDayWorkTimeHours = Math.Min(dailyWorkTime.Hours + 1, Math.Max(0, this.End.Hour + 1 - workTimeBegin.Hours)); // +1 hours
return TimeSpan.FromHours(firstDayWorkTimeHours + totalDaysWorkTimeHours + lastDayWorkTimeHours);
}
为什么我们需要添加1到几小时,因为传递的参数是17(这是最后一个工作小时,如果开始时间也是17 - 我们应该得到1作为结果),相同的dailyWorkTime,17-4给出我们13,但实际上我们有14个工作小时
也注意到小问题:
public TimeSpan TimeSpan
{
get
{
return this.End.Date - this.Begin.Date; // use dates instead of original time stamps
}
}
答案 1 :(得分:1)
我为一个previous answer编写了一个类,它定义了一个用于计算空闲时间的Period
类。我已经扩展了那个类来解决这个问题。
首先,这是输入数据:
var periods = new []
{
new Period(new DateTime(2014, 1, 1, 7, 0, 0), new DateTime(2014, 1, 3, 16, 0, 0)),
new Period(new DateTime(2014, 1, 1, 2, 0, 0), new DateTime(2014, 1, 3, 21, 0, 0)),
new Period(new DateTime(2014, 1, 1, 4, 0, 0), new DateTime(2014, 1, 4, 0, 0, 0)),
new Period(new DateTime(2014, 1, 1, 23, 0, 0), new DateTime(2014, 1, 3, 10, 0, 0)),
new Period(new DateTime(2014, 1, 2, 12, 0, 0), new DateTime(2014, 1, 2, 21, 0, 0)),
};
以下是我编写的用于计算工作期间小时数的查询:
var query =
from period in periods
let workingPeriods =
Enumerable
.Range(0, period.EndTime.Date.Subtract(period.StartTime.Date).Days + 1)
.Select(n => period.StartTime.Date.AddDays((double)n))
.Select(d => new Period(d.AddHours(4.0), d.AddHours(18)))
let remainders = period.Remove(workingPeriods)
let hoursDuringWorkingPeriods = period.TotalHours - remainders.Sum(x => x.TotalHours)
select new { Period = period.ToString(), hoursDuringWorkingPeriods };
这给了我这个结果:
这是更新的Period
类:
private sealed class Period : IEquatable<Period>
{
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public Period(DateTime startTime, DateTime endTime)
{
this.StartTime = startTime;
this.EndTime = endTime;
}
public double TotalHours
{
get
{
return this.EndTime.Subtract(this.StartTime).TotalHours;
}
}
public override bool Equals(object obj)
{
if (obj is Period)
return Equals((Period)obj);
return false;
}
public bool Equals(Period obj)
{
if (obj == null)
return false;
if (!EqualityComparer<DateTime>.Default.Equals(
this.StartTime, obj.StartTime))
return false;
if (!EqualityComparer<DateTime>.Default.Equals(
this.EndTime, obj.EndTime))
return false;
return true;
}
public override int GetHashCode()
{
int hash = 0;
hash ^= EqualityComparer<DateTime>.Default
.GetHashCode(this.StartTime);
hash ^= EqualityComparer<DateTime>.Default
.GetHashCode(this.EndTime);
return hash;
}
public override string ToString()
{
return String.Format("{{ StartTime = {0}, EndTime = {1} }}",
this.StartTime, this.EndTime);
}
public IEnumerable<Period> Remove(Period period)
{
if (period.StartTime <= this.StartTime)
{
if (period.EndTime <= this.StartTime)
yield return this;
else if (period.EndTime >= this.EndTime)
yield break;
else
yield return new Period(period.EndTime, this.EndTime);
}
else if (period.StartTime < this.EndTime)
{
yield return new Period(this.StartTime, period.StartTime);
if (period.EndTime < this.EndTime)
{
yield return new Period(period.EndTime, this.EndTime);
}
}
else
yield return this;
}
public IEnumerable<Period> Remove(IEnumerable<Period> periods)
{
return Remove(new [] { this }, periods);
}
private static IEnumerable<Period> Remove(IEnumerable<Period> selfs, IEnumerable<Period> periods)
{
if (periods == null || periods.IsEmpty())
{
return Enumerable.Empty<Period>();
}
else
{
var period = periods.First();
var nexts =
from s in selfs
from ss in s.Remove(period)
select ss;
return periods.Skip(1).Any() ? Remove(nexts, periods.Skip(1)) : nexts;
}
}
}
答案 2 :(得分:0)
基于Lashanes answere我更改了代码以返回精确的TimeStamp值...
public struct DateSpan
{
public DateTime Begin
{
get
{
return this.begin;
}
}
public DateTime End
{
get
{
return this.end;
}
}
public TimeSpan TimeSpan
{
get
{
return this.End - this.Begin;
}
}
public TimeSpan GetWorkTimeSpan(TimeSpan? workTimeBegin, TimeSpan? workTimeEnd)
{
if (this.Begin.Date == this.End.Date)
{
long totalWorkTimeTicks = Math.Min(workTimeEnd.Value.Ticks, this.End.TimeOfDay.Ticks) - Math.Max(workTimeBegin.Value.Ticks, this.Begin.TimeOfDay.Ticks);
return TimeSpan.FromTicks(totalWorkTimeTicks);
}
else
{
TimeSpan daySpan = this.End.Date - this.Begin.Date;
TimeSpan dailyWorkTime = TimeSpan.FromDays(1);
dailyWorkTime -= workTimeBegin ?? TimeSpan.Zero;
dailyWorkTime -= TimeSpan.FromDays(1) - workTimeEnd ?? TimeSpan.FromDays(1);
long totalDaysWorkTimeTicks = (int)(daySpan.TotalDays - 1) * dailyWorkTime.Ticks;
long firstDayWorkTimeTicks = Math.Min(dailyWorkTime.Ticks, Math.Max(0, (workTimeEnd ?? TimeSpan.FromDays(1)).Ticks - this.Begin.TimeOfDay.Ticks));
long lastDayWorkTimeTicks = Math.Min(dailyWorkTime.Ticks, Math.Max(0, this.End.TimeOfDay.Ticks - (workTimeBegin ?? TimeSpan.Zero).Ticks));
return TimeSpan.FromTicks(firstDayWorkTimeTicks + totalDaysWorkTimeTicks + lastDayWorkTimeTicks);
}
}
}
TimeSpan workTimeBegin = new TimeSpan(04, 00, 00);
TimeSpan workTimeEnd = new TimeSpan(18, 00, 00);
DateSpan dateRange1 = new DateSpan(new DateTime(2012, 01, 01, 07, 00, 00), new DateTime(2012, 01, 03, 15, 00, 00));
DateSpan dateRange2 = new DateSpan(new DateTime(2012, 01, 01, 02, 00, 00), new DateTime(2012, 01, 03, 20, 00, 00));
DateSpan dateRange3 = new DateSpan(new DateTime(2012, 01, 01, 04, 00, 00), new DateTime(2012, 01, 03, 23, 00, 00));
DateSpan dateRange4 = new DateSpan(new DateTime(2012, 01, 01, 23, 00, 00), new DateTime(2012, 01, 03, 09, 00, 00));
DateSpan dateRange5 = new DateSpan(new DateTime(2012, 01, 02, 12, 00, 00), new DateTime(2012, 01, 02, 20, 00, 00));
DateSpan dateRange6 = new DateSpan(new DateTime(2012, 01, 02, 20, 00, 00), new DateTime(2012, 01, 03, 03, 00, 00));
DateSpan dateRange7 = new DateSpan(new DateTime(2012, 01, 02, 15, 00, 00), new DateTime(2012, 01, 03, 00, 00, 00));
Debug.WriteLine(String.Format("dateRange1: {0} ({1})", dateRange1.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange1.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange2: {0} ({1})", dateRange2.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange2.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange3: {0} ({1})", dateRange3.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange3.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange4: {0} ({1})", dateRange4.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange4.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange5: {0} ({1})", dateRange5.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange5.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange6: {0} ({1})", dateRange6.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange6.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
Debug.WriteLine(String.Format("dateRange7: {0} ({1})", dateRange7.GetWorkTimeSpan(workTimeBegin, workTimeEnd), dateRange7.GetWorkTimeSpan(workTimeBegin, workTimeEnd).TotalHours));
dateRange1: 1.12:00:00 (36) // correct
dateRange2: 1.18:00:00 (42) // correct
dateRange3: 1.18:00:00 (42) // correct
dateRange4: 19:00:00 (19) // correct
dateRange5: 06:00:00 (6) // correct
dateRange6: 00:00:00 (0) // correct
dateRange7: 03:00:00 (3) // correct
现在可以使用,因为我希望它可以工作...... :)