C# - 两个日期之间的分钟数持续时间

21

我需要确定两个日期时间之间的分钟数。

然而,有一个小问题:

  • 排除周末
  • 只计算在早上7点到晚上7点之间的分钟。 例如:[09/30/2010 6:39:00 PM] - [09/30/2010 7:39:00 PM] = 21 Minutes

我一直很难想到一个合理的方法来实现它,如果有人能提供建议,我将不胜感激。

谢谢。


编辑:

最终我采用了dtb的解决方案。只有一种特殊情况需要处理: 如果结束时间在晚上7点后,则从早上7点到实际结束时间计算分钟。

这是我的修改方式:

var minutes = from day in start.DaysInRangeUntil(end)
                where !day.IsWeekendDay()
                let st = Helpers.Max(day.AddHours(7), start)
                let en = (day.DayOfYear == end.DayOfYear ? 
                            end :
                            Helpers.Min(day.AddHours(19), end)
                            )
                select (en - st).TotalMinutes;

再次感谢您的帮助。


2
@0xA3,这不是那个问题的副本,因为那个问题没有回答这里所问的排除日期和时间。 - Kirk Woll
2
@Kirk - 这是真的。使用自定义排除规则的TimeSpan的无限用法将使SO问题继续存在10年。 - Hans Passant
那么,人们应该展示他们的工作,就像在学校里一样。 :) - Jon Hanna
相关链接:https://connect.microsoft.com/VisualStudio/feedback/details/526951/screen-object-physicalwidthincentimeters-physicalheightincentimeters-displaymode - user1228
9个回答

23

当然,你可以使用LINQ:

DateTime a = new DateTime(2010, 10, 30, 21, 58, 29);
DateTime b = a + new TimeSpan(12, 5, 54, 24, 623);

var minutes = from day in a.DaysInRangeUntil(b)
              where !day.IsWeekendDay()
              let start = Max(day.AddHours( 7), a)
              let end   = Min(day.AddHours(19), b)
              select (end - start).TotalMinutes;

var result = minutes.Sum();

// result == 6292.89

(注意:您可能需要检查我完全忽略的许多边角情况。)

辅助方法:

static IEnumerable<DateTime> DaysInRangeUntil(this DateTime start, DateTime end)
{
    return Enumerable.Range(0, 1 + (int)(end.Date - start.Date).TotalDays)
                     .Select(dt => start.Date.AddDays(dt));
}

static bool IsWeekendDay(this DateTime dt)
{
    return dt.DayOfWeek == DayOfWeek.Saturday
        || dt.DayOfWeek == DayOfWeek.Sunday;
}

static DateTime Max(DateTime a, DateTime b)
{
    return new DateTime(Math.Max(a.Ticks, b.Ticks));
}

static DateTime Min(DateTime a, DateTime b)
{
    return new DateTime(Math.Min(a.Ticks, b.Ticks));
}

3
+1,当我看这段代码时仍然感到有点不舒服,但我对LINQ的创造性运用表示赞赏。 - JaredPar
我可以建议一个IsWeekend扩展吗? - Ron Warholic
首先,我想说这确实是一种非常紧凑的处理问题的方法。LINQ很不错。我的问题在于您正在遍历开始和结束之间的每一天。如果这是一个为期5年的周期,您将执行许多不必要的计算。别误会,今天的计算机非常强大,可以轻松处理这样的事情。但是,在7天的时间内计算总工时很容易=> 5 * 12 = 60。计算剩余部分有些棘手,但永远不需要迭代超过7天。 - dana
2
@dana:优化正确的解决方案总是比纠正已经优化的解决方案更容易。而且我甚至不确定我的相对易读的解决方案在当前形式下是否正确(边角情况、时区、夏令时等)。 - dtb
@dtb,没错,但是从迭代到直接数学优化通常需要进行重写,这样你就会失去沿途所做的任何正确性证明。如果迭代长期来看没问题,那就好了,如果你在处理更大的问题时需要一个“只要能用就行!”的解决方案,那也可以。但是如果你已经认为它可能需要优化,那么这种情况下我会直接考虑替代方案,而不认为这是过早的。 - Jon Hanna
显示剩余2条评论

5

取开始时间,计算到当天结束时间(即晚上7点)的分钟数。

然后从第二天早上7点开始,计算到最后一天的天数(不包括最后一天的任何时间)。

计算经过了多少个周末(每个周末减少2天的天数)。

从这里进行简单的数学计算,以获取天数的总分钟数。

加上最后一天和开始日的额外时间。


我同意这个观点 - 这不是一个简单的一步计算,但它很干净,代码应该相当易读。 - Joe Enos
我可以写些代码,但是 a) 这样会破坏OP的乐趣,而且 b) 我的VS出了故障。 - Psytronic

4
尝试使用以下DiffRange函数。
public static DateTime DayStart(DateTime date)
{
    return date.Date.AddHours(7);
}

public static DateTime DayEnd(DateTime date)
{
    return date.Date.AddHours(19);
}

public static TimeSpan DiffSingleDay(DateTime start, DateTime end)
{
    if ( start.Date != end.Date ) {
        throw new ArgumentException();
    }

    if (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday )
    {
        return TimeSpan.Zero;
    }

    start = start >= DayStart(start) ? start : DayStart(start);
    end = end <= DayEnd(end) ? end : DayEnd(end);
    return end - start;
}

public static TimeSpan DiffRange(DateTime start, DateTime end)
{
    if (start.Date == end.Date)
    {
        return DiffSingleDay(start, end);
    }

    var firstDay = DiffSingleDay(start, DayEnd(start));
    var lastDay = DiffSingleDay(DayStart(end), end);

    var middle = TimeSpan.Zero;
    var current = start.AddDays(1);
    while (current.Date != end.Date)
    {
        middle = middle + DiffSingleDay(current.Date, DayEnd(current.Date));
        current = current.AddDays(1);
    }

    return firstDay + lastDay + middle;
}

这会产生与我的解决方案截然不同的结果。你能发现我的代码哪里出了问题吗? - dtb
@dtb,看起来是我的问题。我加了14而不是19。我会纠正的。 - JaredPar
哦,太棒了。你的修正版本现在针对我的示例返回相同的结果。 - dtb
1
现在你只需要考虑夏令时的问题 :-D - LBushkin
@JaredPar 我知道你在开玩笑,但是...实际上你不必考虑夏令时,因为所有实行夏令时的司法管辖区都在不计入的某个时期进行切换。由于DateTime的实现没有完整的时区信息,所以本地时间和UTC之间的偏移量在两种情况下被认为是相同的(通常是一个麻烦,但在这里是一个救星)。只要没有人为其中一个输入UTC时间,而另一个输入本地时间(这将是疯狂的),夏令时就不是问题。 - Jon Hanna
显示剩余2条评论

1

我相信我错过了什么。

  TimeSpan CalcBusinessTime(DateTime a, DateTime b)
  {
     if (a > b)
     {
        DateTime tmp = a;
        a = b;
        b = tmp;
     }

     if (a.TimeOfDay < new TimeSpan(7, 0, 0))
        a = new DateTime(a.Year, a.Month, a.Day, 7, 0, 0);
     if (b.TimeOfDay > new TimeSpan(19, 0, 0))
        b = new DateTime(b.Year, b.Month, b.Day, 19, 0, 0);

     TimeSpan sum = new TimeSpan();
     TimeSpan fullDay = new TimeSpan(12, 0, 0);
     while (a < b)
     {
        if (a.DayOfWeek != DayOfWeek.Saturday && a.DayOfWeek != DayOfWeek.Sunday)
        {
           sum += (b - a < fullDay) ? b - a : fullDay;
        }
        a = a.AddDays(1);
     }

     return sum;
  } 

@dtb,谢谢,希望现在没有bug了。也许我会运行一些测试。 - Jeremiah Nunn

1

这是一个相当困难的问题。对于基础知识,我采用了直接的方法,编写了以下代码:

DateTime start = new DateTime(2010, 01, 01, 21, 00, 00);
DateTime end = new DateTime(2010, 10, 01, 14, 00, 00);

// Shift start date's hour to 7 and same for end date
// These will be added after doing calculation:
double startAdjustmentMinutes = (start - start.Date.AddHours(7)).TotalMinutes;
double endAdjustmentMinutes = (end - end.Date.AddHours(7)).TotalMinutes;

// We can do some basic
// mathematical calculation to find weekdays count:
// divide by 7 multiply by 5 gives complete weeks weekdays
// and adding remainder gives the all weekdays:
int weekdaysCount = (((int)((end.Date - start.Date).Days / 7) * 5) 
          + ((end.Date - start.Date).Days % 7));
// so we can multiply it by minutes between 7am to 7 pm
int minutes = weekdaysCount * (12 * 60);

// after adding adjustment we have the result:
int result = minutes + startAdjustmentMinutes + endAdjustmentMinutes;

我知道这看起来不太优美,但我不确定在开始和结束之间迭代天数和小时数是否好。


1
static int WorkPeriodMinuteDifference(DateTime start, DateTime end)
{
    //easier to only have to work in one direction.
    if(start > end)
        return WorkPeriodMinuteDifference(end, start);
    //if weekend, move to start of next Monday.
    while((int)start.DayOfWeek % 6 == 0)
        start = start.Add(new TimeSpan(1, 0, 0, 0)).Date;
    while((int)end.DayOfWeek % 6 == 0)
        end = end.Add(new TimeSpan(1, 0, 0, 0)).Date;
    //Move up to 07:00 or down to 19:00
    if(start.TimeOfDay.Hours < 7)
        start = new DateTime(start.Year, start.Month, start.Day, 7, 0, 0);
    else if(start.TimeOfDay.Hours > 19)
        start = new DateTime(start.Year, start.Month, start.Day, 19, 0, 0);
    if(end.TimeOfDay.Hours < 7)
        end = new DateTime(end.Year, end.Month, end.Day, 7, 0, 0);
    else if(end.TimeOfDay.Hours > 19)
        end = new DateTime(end.Year, end.Month, end.Day, 19, 0, 0);

    TimeSpan difference = end - start;

    int weeks = difference.Days / 7;
    int weekDays = difference.Days % 7;
    if(end.DayOfWeek < start.DayOfWeek)
        weekDays -= 2;

    return (weeks * 5 * 12 * 60) + (weekDays * 12 * 60) + difference.Hours * 60 + difference.Minutes
}

1

我的实现 :) 思路是快速计算总周数,然后逐日遍历剩余的星期几...

public TimeSpan Compute(DateTime start, DateTime end)
{
    // constant start / end times per day
    TimeSpan sevenAM = TimeSpan.FromHours(7);
    TimeSpan sevenPM = TimeSpan.FromHours(19);

    if( start >= end )
    {
        throw new Exception("invalid date range");
    }

    // total # of weeks
    int completeWeeks = ((int)(end - start).TotalDays) / 7;

    // starting total
    TimeSpan total = TimeSpan.FromHours(completeWeeks * 12 * 5);

    // adjust the start date to be exactly "completeWeeks" past its original start
    start = start.AddDays(completeWeeks * 7);

    // walk days from the adjusted start to end (at most 7), accumulating time as we can...
    for(
        // start at midnight
        DateTime dt = start.Date;

        // continue while there is time left
        dt < end;

        // increment 1 day at a time
        dt = dt.AddDays(1)
    )
    {
        // ignore weekend
        if( (dt.DayOfWeek == DayOfWeek.Saturday) ||
             (dt.DayOfWeek == DayOfWeek.Sunday) )
        {
            continue;
        }

        // get the start/end time for each day...
        // typically 7am / 7pm unless we are at the start / end date
        TimeSpan dtStartTime = ((dt == start.Date) && (start.TimeOfDay > sevenAM)) ?
            start.TimeOfDay : sevenAM;
        TimeSpan dtEndTime = ((dt == end.Date) && (end.TimeOfDay < sevenPM)) ?
            end.TimeOfDay : sevenPM;

        if( dtStartTime < dtEndTime )
        {
            total = total.Add(dtEndTime - dtStartTime);
        }
    }

    return total;
}

1
使用TimeSpan.TotalMinutes,减去非工作日,减去多余的小时。

0

我不会写任何代码,但是有了一个DateTime,你可以知道一周的哪一天,因此你知道你的范围内有多少个周末,这样你就可以知道一个周末有多少分钟。

所以这并不难...当然可能有一个最优解决方案...但我认为你可以用这个方法解决问题。

我忘了提到你还知道晚上7点到早上7点之间的分钟数,所以你只需要从时间差中减去正确的分钟数即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接