【问题标题】:Time interceptions (employee night working time) - VB.net, C#, Pseudo Code时间截取(员工夜间工作时间) - VB.net, C#, 伪代码
【发布时间】:2017-05-02 17:11:39
【问题描述】:

我并没有真正的技术问题,更多的是我已经绞尽脑汁好几个小时来寻找解决某个问题的方法。我不敢相信我是第一个解决这个问题的人,但是谷歌让我一个人呆着。那么,可能有人以前这样做过。

这就是问题所在:假设在夜间工作的员工会获得一定的工资附加费。根据定义,“夜间时间”从 23:00 到 06:00(晚上 11 点到早上 6 点)。我如何计算某个工作时间段中有多少是夜间时间。对于人脑来说非常简单,但我无法弄清楚逻辑。让我举几个例子:

工作时间:22:00 - 8:00 = 夜间 7 小时,正常 3 小时
工作时间:23:00 - 5:00 = 6 小时夜间,0 小时正常
工作时间:20:00 - 4:00 = 夜间 5 小时,正常 3 小时
工作时间:04:00 - 8:00 = 夜间 2 小时,正常 2 小时
工作时间:16:00 - 22:00 = 夜间 0 小时,正常 6 小时

这是我到目前为止的代码(班次是员工的实际工作时间),但它不起作用。它不适用于午夜之后的工作时间。

DateTime nightTimeStart = new DateTime(1, 1, 1, 23, 0, 0);
DateTime nightTimeEnd = new DateTime(1, 1, 2, 6, 0, 0);

if (shift.UntilTime < nightTimeStart || shift.FromTime > nightTimeEnd)
    return 0;
else if (shift.UntilTime <= nightTimeEnd && shift.FromTime >= nightTimeStart)
    return (decimal)(shift.UntilTime - shift.FromTime).TotalHours;
else if (shift.UntilTime > nightTimeEnd && shift.FromTime < nightTimeStart)
    return (decimal)(nightTimeEnd - nightTimeStart).TotalHours;
else if (shift.UntilTime < nightTimeEnd && shift.FromTime < nightTimeStart)
    return (decimal)(nightTimeEnd - shift.FromTime).TotalHours;
else
    return (decimal)(shift.UntilTime - nightTimeStart).TotalHours;

【问题讨论】:

  • 您使用什么数据结构来存储这些值?
  • 目前我尝试使用 C# 的日期时间来执行此操作,并将日期部分设置为“0001-01-01”。但我对所有可行的事情持开放态度。可以使用任何解决问题的结构。
  • 所以你有这些日期时间,而不是表示为小时的整数?你所有的例子都是:00。另外,04:00 -> 6:00 的例子是不是错了?
  • 我刚刚更正了 4:00 到 6:00 的示例,即 4:00 到 8:00。当然,逻辑也应该适用于不均匀的时间,比如 21:35 到 4:40。
  • 这是为了实际应用吗?我问是因为您需要考虑有人从 05:30 开始工作,一直工作到 23:30。您可能还需要考虑夏令时 - 尽管这也可以通过使用 UTC 来计算(尽管使用 UTC,您的夜班范围会有所不同,可能会导致午夜重叠的问题)。

标签: c# vb.net datetime time


【解决方案1】:

This GitHub Repo 有一个很好的实现,可以满足您的需求

你可以这样使用它(注意从 01 到 02 的日期变化):

var nightTime = new DateTimeInterval()
{
    From = new DateTime(0001, 01, 01, 20, 00, 00),
    To = new DateTime(0001, 01, 02, 6, 00, 00)
};
var shiftTime = new DateTimeInterval()
{
    From = new DateTime(0001, 01, 01, 21, 00, 00),
    To = new DateTime(0001, 01, 01, 8, 00, 00)
};

if(shiftTime.From.Value.Hour > shiftTime.To.Value.Hour)
{
    shiftTime.To = shiftTime.To.Value.AddDays(1);
}

var overlap = DateTimeUtils.GetIntervalIntersection(nightTime, shiftTime);
var duration = (overlap.To.Value - overlap.From.Value).TotalHours;

这是代码的精简版本(以防 repo 被删除):

public class DateTimeInterval
{
    public DateTime? From { get; set; }
    public DateTime? To { get; set; }
}

public static class DateTimeUtils
{
    public static DateTimeInterval GetIntervalIntersection(DateTimeInterval testInterval, DateTimeInterval allowedInterval)
    {
        if (testInterval.From.Value < allowedInterval.From.Value && testInterval.To.Value < allowedInterval.From.Value
            || testInterval.From.Value > allowedInterval.To.Value && testInterval.To.Value > allowedInterval.To.Value)
        {
            return new DateTimeInterval();
        }

        DateTime from = testInterval.From < allowedInterval.From
            ? allowedInterval.From.Value
            : testInterval.From.Value;

        DateTime to = testInterval.To > allowedInterval.To
            ? allowedInterval.To.Value
            : testInterval.To.Value;

        var result = new DateTimeInterval
        {
            From = from,
            To = to
        };

        return result;
    }
}

【讨论】:

  • 感谢大家发表您的想法。我只评论这个,因为它是第一个。实际上所有想法都会导致相同的结果 - 我的也是如此(见上文)。都没有解决一种情况:午夜之后的工作时间,比如从 4:00 到 8:00。虽然具有时间跨度的解决方案导致零小时,但其他解决方案只有在我在工作时间上增加一天时才有效。这正是问题所在:我如何确定何时将一天添加到工作日,何时不添加。如果有人在 4:00 到 14:00 之间工作,该解决方案也应该奏效……很棘手。
  • @RainerThiele 好吧,如果结束时间小于开始时间,则添加一天。这不是很容易吗?伪代码:If(From.Hour &lt; To.Hour) { To.Day = 1 } else { To.Day = 2 }。当然,这取决于工人不工作 >= 24 小时,我认为这是一个合理的假设。
  • 不。就像我说的,e。 G。从 4:00(上午)到 8:00(上午)。结束时间高于开始时间。但仍然是夜间 2 小时,正常时间 2 小时(假设夜间时间为 6:00)。
【解决方案2】:

您还可以循环时间并检查它是否在夜间范围内

private static DateTime nightTimeStart = new DateTime(1, 1, 1, 23, 0, 0);
private static DateTime nightTimeEnd = new DateTime(1, 1, 2, 6, 0, 0);

private struct WorkHours
{
    public int NightHours;
    public int DayHours;
}

private static WorkHours GetWorkHours(DateTime t1, DateTime t2)
{
    WorkHours result = new WorkHours();
    for (DateTime i = t1; i < t2; i = i.AddHours(1))
    {
        if (i >= nightTimeStart && i < nightTimeEnd)
            result.NightHours++;
        else
            result.DayHours++;
    }
    return result;
}

static void Main(string[] args)
{
    WorkHours result;

    result = GetWorkHours(new DateTime(1, 1, 1, 22, 0, 0), new DateTime(1, 1, 2, 8, 0, 0));
    Console.WriteLine("{0} hours night time, {1} hours normal",
        result.NightHours, result.DayHours);

    result = GetWorkHours(new DateTime(1, 1, 1, 23, 0, 0), new DateTime(1, 1, 2, 5, 0, 0));
    Console.WriteLine("{0} hours night time, {1} hours normal",
        result.NightHours, result.DayHours);

    result = GetWorkHours(new DateTime(1, 1, 1, 20, 0, 0), new DateTime(1, 1, 2, 4, 0, 0));
    Console.WriteLine("{0} hours night time, {1} hours normal",
        result.NightHours, result.DayHours);

    result = GetWorkHours(new DateTime(1, 1, 2, 4, 0, 0), new DateTime(1, 1, 2, 8, 0, 0));
    Console.WriteLine("{0} hours night time, {1} hours normal",
        result.NightHours, result.DayHours);

    result = GetWorkHours(new DateTime(1, 1, 1, 16, 0, 0), new DateTime(1, 1, 1, 22, 0, 0));
    Console.WriteLine("{0} hours night time, {1} hours normal",
        result.NightHours, result.DayHours);

    Console.ReadLine();
}

夜间 7 小时,正常 3 小时
夜间 6 小时,正常 0 小时
夜间 5 小时,正常 3 小时
夜间 2 小时,正常 2 小时
夜间0小时,正常6小时

【讨论】:

    【解决方案3】:

    我找到了一个解决方案,到目前为止,它适用于我所有的测试案例,包括我最初提到的工作时间以及任何夜间时间(如 20:00-0:00、23:00-6:00、1: 00-5:00,...)。它基于 MrZander 发布的“DateTimeUtils”。

    我不知道这是唯一还是最好的解决方案。实际上我必须说代码有点奇怪,但它确实有效。如果我以后找到更好的解决方案,我会重新发布。这是我所做的:

    首先我修改了 DateTimeInterval 类以使处理更容易。那应该是自我解释:

        private class DateTimeInterval
        {
            public DateTime From { get; set; }
            public DateTime To { get; set; }
            public TimeSpan Duration
            {
                get { return (To - From); }
            }
    
            public DateTimeInterval() { }
    
            public DateTimeInterval(DateTime from, DateTime to)
            {
                From = from;
                To = to;
            }
        }
    

    然后我创建了一个 nightTimeIntervals 列表。这似乎很奇怪,但这是因为夜间时间不超过午夜(实际上在我的项目中,夜间时间来自 web.config):

        private void SetNightTime()
        {
            DateTime nightTimeStart;
            DateTime nightTimeEnd;
            try
            {
                nightTimeStart = new DateTime(1, 1, 1, 23, 0, 0);
                nightTimeEnd = new DateTime(1, 1, 1, 6, 0, 0);
    
                if (nightTimeEnd < nightTimeStart)
                    nightTimeEnd = nightTimeEnd.AddDays(1);
    
                nightTimeIntervals.Add(new DateTimeInterval(nightTimeStart, nightTimeEnd));
    
                if (nightTimeEnd.Date == nightTimeStart.Date)
                    nightTimeIntervals.Add(new DateTimeInterval(nightTimeStart.AddDays(1), nightTimeEnd.AddDays(1)));
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    

    为了计算实际的夜间工作时间,我还创建了一个时间跨度列表。我将每个 nightTimeSpan 与工作时间和工作时间加一天(示例中的 4:00-8:00)进行比较,并将结果添加到时间跨度列表中。从该列表中只需选择最大值 - 就是这样。

        private decimal CalculateNightWorkingHours(Shift shift)
        {
           List<TimeSpan> nightTimeSpans = new List<TimeSpan>();
    
            if (shift.UntilTime < shift.FromTime)
                shift.UntilTime = shift.UntilTime.AddDays(1);
    
            foreach (DateTimeInterval n in nightTimeIntervals)
            {
                nightTimeSpans.Add(GetIntervalIntersection(new DateTimeInterval(shift.FromTime, shift.UntilTime), n).Duration);
                nightTimeSpans.Add(GetIntervalIntersection(new DateTimeInterval(shift.FromTime.AddDays(1), shift.UntilTime.AddDays(1)), n).Duration);
            }
    
            return (decimal)nightTimeSpans.Max(h => h.TotalHours);
        }
    

    如果有更好的解决方案,我不会感到惊讶,但到目前为止,这是我能想到的最好的解决方案。

    【讨论】:

      【解决方案4】:

      我宁愿在[00:00:00 - 24:00:00) 范围内将包含开始时间和独占结束时间编码为TimeSpans。

      那么这两个范围的基本交集

      [startA, endA), startA [startB, endB), startB

      Max(Min(endA, endB) - Max(startA, startB), TimeSpan.Zero)

      问题是[start, end) 范围包含午夜班 (end &lt; start)。在这种情况下,它可以被视为两个不重叠的范围[start, 24:00:00)[00:00:00, end)。因此,两个范围的交集将是一个或两个源范围与一个或两个目标范围的交集之和,根据参数导致总共 4 个组合。

      这是上述的一种可能的实现方式:

      public static class TimeUtils
      {
          static readonly TimeSpan DayStart = new TimeSpan(0, 0, 0);
          static readonly TimeSpan DayEnd = new TimeSpan(24, 0, 0);
          public static TimeSpan Intersect(TimeSpan startA, TimeSpan endA, TimeSpan startB, TimeSpan endB)
          {
              if (startA < endA)
              {
                  if (startB < endB)
                      return IntersectCore(startA, endA, startB, endB);
                  else
                      return IntersectCore(startA, endA, startB, DayEnd)
                          + IntersectCore(startA, endA, DayStart, endB);
              }
              else
              {
                  if (startB < endB)
                      return IntersectCore(startA, DayEnd, startB, endB)
                          + IntersectCore(DayStart, endA, startB, endB);
                  else
                      return IntersectCore(startA, DayEnd, startB, DayEnd)
                          + IntersectCore(startA, DayEnd, DayStart, endB)
                          + IntersectCore(DayStart, endA, startB, DayEnd)
                          + IntersectCore(DayStart, endA, DayStart, endB);
              }
          }
          private static TimeSpan IntersectCore(TimeSpan startA, TimeSpan endA, TimeSpan startB, TimeSpan endB)
          {
              return Max(Min(endA, endB) - Max(startA, startB), TimeSpan.Zero);
          }
          public static TimeSpan Max(TimeSpan a, TimeSpan b)
          {
              return a > b ? a : b;
          }
          public static TimeSpan Min(TimeSpan a, TimeSpan b)
          {
              return a < b ? a : b;
          }
      }
      

      示例用法:

      var nightWorkTime = TimeUtils.Intersect(
          new TimeSpan(22, 00, 00), new TimeSpan(08, 00, 00), // Working time: 22:00 - 8:00,
          new TimeSpan(23, 00, 00), new TimeSpan(06, 00, 00) // Night time: 23:00 - 6:00
      );
      // 07:00:00
      

      【讨论】:

      • 事实证明这个问题比我最初的复杂。希望更新后的代码能处理所有情况。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-23
      • 2023-02-26
      • 2012-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-14
      相关资源
      最近更新 更多