使用灵活的日期时间范围计算日期时间之间的小时数
Count hours between datetimes with flexible day time range
我正在寻找一些优雅的算法来使用预定义的 "work-hour-day-range".
计算多天的小时数
一个真实世界的例子是:只计算租用对象的工作时间。
DataTime 范围可以在定义的 "work-hour-day-range" 内部或外部开始。
例子
我创建了一个包含 5 个不同场景的小示例。希望这能让它更清楚。
- 第 1 行 = 日期
- 第 2 行 = 白天-时间-小时
- 第 3-7 行 = 5 个不同的日期范围来计算
的小时数
larger image
我想到的唯一方法是在范围内的每一天进行 for 循环,并在其中设置多个复杂的 if-树。但我希望有人比我聪明,可以给我一些更快、更优雅的方式的提示。
非常感谢您的帮助! :)
更新 1
基于 Lashanes answere 我是这样实现的...
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 解释的方式,但没有看到我的错误...:(
算法可以很简单,我假设所有日期范围都四舍五入到小时,即没有minutes/seconds
因此,要计算您需要的工作小时数:
- 日期时间之间的完整天数,例如
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);
常量在这里:
- 14 - 每天工作时数
- 18 - 下班后一天的第一个非工作时间
- 3 - 工作时间前一天的最后一个非工作时间
关于 min/max 的注释:
- max with 0 将所有负值更改为 0,但保留正值,例如:最后一天我们在 2 点完成(即 0 个工作时间),所以 Math.Max(0, 2-3 ==-1) = 0
- 分钟将每天的工作时间限制为 14 小时,例如:最后一天我们完成了 20 小时,Math.min(14, 20-3==17) 只会给我们 14 小时
这是基于您的代码的有效实现:
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
}
}
根据 Lashanes answere,我将代码更改为 return 精确的时间戳值...
代码
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
现在可以正常工作了...:)
我为 previous answer 写了一个 class 定义了一个 Period
class 用来计算空闲时间。我已经扩展 class 来解决这个问题。
首先,这是输入数据:
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
class:
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;
}
}
}
我正在寻找一些优雅的算法来使用预定义的 "work-hour-day-range".
计算多天的小时数一个真实世界的例子是:只计算租用对象的工作时间。
DataTime 范围可以在定义的 "work-hour-day-range" 内部或外部开始。
例子
我创建了一个包含 5 个不同场景的小示例。希望这能让它更清楚。
- 第 1 行 = 日期
- 第 2 行 = 白天-时间-小时
- 第 3-7 行 = 5 个不同的日期范围来计算 的小时数
我想到的唯一方法是在范围内的每一天进行 for 循环,并在其中设置多个复杂的 if-树。但我希望有人比我聪明,可以给我一些更快、更优雅的方式的提示。
非常感谢您的帮助! :)
更新 1
基于 Lashanes answere 我是这样实现的...
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 解释的方式,但没有看到我的错误...:(
算法可以很简单,我假设所有日期范围都四舍五入到小时,即没有minutes/seconds
因此,要计算您需要的工作小时数:
- 日期时间之间的完整天数,例如
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);
常量在这里:
- 14 - 每天工作时数
- 18 - 下班后一天的第一个非工作时间
- 3 - 工作时间前一天的最后一个非工作时间
关于 min/max 的注释:
- max with 0 将所有负值更改为 0,但保留正值,例如:最后一天我们在 2 点完成(即 0 个工作时间),所以 Math.Max(0, 2-3 ==-1) = 0
- 分钟将每天的工作时间限制为 14 小时,例如:最后一天我们完成了 20 小时,Math.min(14, 20-3==17) 只会给我们 14 小时
这是基于您的代码的有效实现:
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
}
}
根据 Lashanes answere,我将代码更改为 return 精确的时间戳值...
代码
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
现在可以正常工作了...:)
我为 previous answer 写了一个 class 定义了一个 Period
class 用来计算空闲时间。我已经扩展 class 来解决这个问题。
首先,这是输入数据:
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
class:
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;
}
}
}