【问题标题】:Calculating number of nights in overlapping dates计算重叠日期的晚数
【发布时间】:2015-08-25 14:37:04
【问题描述】:

我在寻找解决此问题的合乎逻辑的方法时遇到了问题。 我有一个日期范围列表,例如:

01/01/15 - 11/01/15 
02/01/15 - 04/01/15 
09/01/15 - 13/01/15 
18/01/15 - 20/01/15

我需要做的是计算出所有这些日期范围内涵盖的总晚数。

所以对于这个例子来说,总数应该是 14 晚:

01/01/15 - 11/01/15 // 10 nights
02/01/15 - 04/01/15 // Ignored as nights are covered in 1-11
09/01/15 - 13/01/15 // 2 nights as 11th and 12th nights haven't been covered
18/01/15 - 20/01/15 // 2 nights

我可以使用 min-max 日期轻松计算出总晚数,但忽略了缺失的日期(示例中为 14-17),这是我无法计算的。

有什么方法可以找出失踪的总天数来帮助解决这个问题吗?

【问题讨论】:

  • 最早日期和最晚日期之间的最大可能范围是多少?
  • 您可以将日期转换为DateTimestruct。然后做“laterDate”-“earlierDate”。然后你会得到数据。减去 1,你就有了夜晚(2 天之间有 1 晚......等等)
  • 例如这:var dates = new DateTime("11/01/15") - new DateTime("01/01/15")。 var nights = 日期 - 1
  • “所以不包括结束日期”是什么意思?您能告诉我们示例日期的预期结果是什么吗?
  • 这些日期的输出是什么?

标签: c# date logic


【解决方案1】:

这是一种使用 HashSet 的方法:

public static int CountDays(IEnumerable<TimeRange> periods)
{
    var usedDays = new HashSet<DateTime>();

    foreach (var period in periods)
        for (var day = period.Start; day < period.End; day += TimeSpan.FromDays(1))
            usedDays.Add(day);

    return usedDays.Count;
}

这假设您的日期范围是半开间隔(即开始日期被视为范围的一部分,但结束日期不是)。

这是一个完整的程序来演示。答案是14

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    public sealed class TimeRange
    {
        public DateTime Start { get; private set; }
        public DateTime End   { get; private set; }

        public TimeRange(string start, string end)
        {
            Start = DateTime.Parse(start);
            End   = DateTime.Parse(end);
        }
    }

    internal class Program
    {
        public static void Main()
        {
            var periods = new []
            {
                new TimeRange("01/01/15", "11/01/15"), 
                new TimeRange("02/01/15", "04/01/15"),
                new TimeRange("09/01/15", "13/01/15"),
                new TimeRange("18/01/15", "20/01/15")
            };

            Console.WriteLine(CountDays(periods));
        }

        public static int CountDays(IEnumerable<TimeRange> periods)
        {
            var usedDays = new HashSet<DateTime>();

            foreach (var period in periods)
                for (var day = period.Start; day < period.End; day += TimeSpan.FromDays(1))
                    usedDays.Add(day);

            return usedDays.Count;
        }
    }
}

注意:这对于较大的日期范围不是很有效!如果您需要考虑较大的日期范围,将重叠范围合并为单个范围的方法会更好。

[编辑] 修正代码使用半开区间而不是闭区间。

【讨论】:

    【解决方案2】:

    你可以这样计算:

    var laterDateTime = Convert.ToDateTime("11/01/15 ");
    var earlierDateTime = Convert.ToDateTime("01/01/15");
    
    TimeSpan dates = laterDateTime - earlierDateTime;
    
    int nights = dates.Days - 1;
    

    您可以将您拥有的任何内容转换为 DateTime。 然后,您可以使用 - 运算符减去两个日期时间。 您的结果将是一种 struct TimeSpan。

    TimeSpan 具有 Days 属性。从中减去 1,您就得到了夜晚。

    2 天之间是 1 晚
    3 天之间是 2 晚
    4天之间是3晚

    我相信你可以做剩下的。

    【讨论】:

      【解决方案3】:

      假设之间有两个晚上,例如1 月 1 日和 1 月 3 日,这应该有效。如果您已经有 DateTime 值而不是字符串,则可以摆脱解析位。基本上,我使用DateTime.Subtract() 来计算两个日期之间的天数(即夜数)。

      namespace DateTest1
      {
          using System;
          using System.Collections.Generic;
          using System.Globalization;
      
          class Program
          {
              static void Main(string[] args)
              {
                  var intervals = new List<Tuple<string, string>>
                                      {
                                          new Tuple<string, string>("01/01/15", "11/01/15"),
                                          new Tuple<string, string>("02/01/15", "04/01/15"),
                                          new Tuple<string, string>("09/01/15", "13/01/15"),
                                          new Tuple<string, string>("18/01/15", "20/01/15")
                                      };
      
                  var totalNights = 0;
      
                  foreach (var interval in intervals)
                  {
                      var dateFrom = DateTime.ParseExact(interval.Item1, "dd/MM/yy", CultureInfo.InvariantCulture);
                      var dateTo = DateTime.ParseExact(interval.Item2, "dd/MM/yy", CultureInfo.InvariantCulture);
      
                      var nights = dateTo.Subtract(dateFrom).Days;
      
                      Console.WriteLine("{0} - {1}: {2} nights", interval.Item1, interval.Item2, nights);
      
                      totalNights += nights;
                  }
      
                  Console.WriteLine("Total nights: {0}", totalNights);
              }
          }
      }
      

      01/01/15 - 11/01/15: 10 nights
      02/01/15 - 04/01/15: 2 nights
      09/01/15 - 13/01/15: 4 nights
      18/01/15 - 20/01/15: 2 nights
      Total nights: 18
      Press any key to continue . . .
      

      【讨论】:

        【解决方案4】:

        这样的事情应该可以工作:

        internal class Range
        {
            internal DateTime From, To;
        
            public Range(string aFrom, string aTo)
            {
                From = DateTime.ParseExact(aFrom, "dd/mm/yy", CultureInfo.InvariantCulture);
                To = DateTime.ParseExact(aTo, "dd/mm/yy", CultureInfo.InvariantCulture);
            }
        }
        
            public static int ComputeNights(IEnumerable<Range> ranges)
            {
                var vSet = new HashSet<DateTime>();
                foreach (var range in ranges)
                    for (var i = range.From; i < range.To; i = i.AddDays(1)) vSet.Add(i)
                return vSet.Count;
            }
        

        运行示例的代码:

                var vRanges = new List<Range>
                {
                    new Range("01/01/15", "11/01/15"),
                    new Range("02/01/15", "04/01/15"),
                    new Range("09/01/15", "13/01/15"),
                    new Range("18/01/15", "20/01/15"),
                };
                var v = ComputeNights(vRanges);
        

        v 计算结果为 14

        【讨论】:

          【解决方案5】:

          我认为这个解决方案会比使用内循环遍历范围以在列表中插入天数更快。此解决方案不需要额外的空间。它是 O(1),它只在范围内循环一次,所以它的复杂性是 O(n)。但它假定您的范围按startdate 排序。如果没有,您可以随时轻松订购:

          var p = new[]
          {
              new Tuple<DateTime, DateTime>(DateTime.ParseExact("01/01/15", "dd/MM/yy", CultureInfo.InvariantCulture), DateTime.ParseExact("11/01/15", "dd/MM/yy", CultureInfo.InvariantCulture)),
              new Tuple<DateTime, DateTime>(DateTime.ParseExact("02/01/15", "dd/MM/yy", CultureInfo.InvariantCulture), DateTime.ParseExact("04/01/15", "dd/MM/yy", CultureInfo.InvariantCulture)),
              new Tuple<DateTime, DateTime>(DateTime.ParseExact("09/01/15", "dd/MM/yy", CultureInfo.InvariantCulture), DateTime.ParseExact("13/01/15", "dd/MM/yy", CultureInfo.InvariantCulture)),
              new Tuple<DateTime, DateTime>(DateTime.ParseExact("18/01/15", "dd/MM/yy", CultureInfo.InvariantCulture), DateTime.ParseExact("20/01/15", "dd/MM/yy", CultureInfo.InvariantCulture))
          };
          
          int days = (p[0].Item2 - p[0].Item1).Days;
          var endDate = p[0].Item2;
          
          for(int i = 1; i < p.Length; i++)
          {
              if(p[i].Item2 > endDate)
              {
                  days += (p[i].Item2 - (p[i].Item1 > endDate ? p[i].Item1 : endDate)).Days;
                  endDate = p[i].Item2;
              }
          }
          

          【讨论】:

          • 这个解决方案结合 Matthew Watson 的 TimeRange 类将是理想的 imo
          【解决方案6】:

          如果您在这里没有足够的答案,请使用 Linq 和 Aggregate 再做一个。返回 14 晚。

          List<Tuple<DateTime, DateTime>> dates = new List<Tuple<DateTime, DateTime>>
              {
                  Tuple.Create(new DateTime(2015, 1,1), new DateTime(2015, 1,11)),
                  Tuple.Create(new DateTime(2015, 1,2), new DateTime(2015, 1,4)),
                  Tuple.Create(new DateTime(2015, 1,9), new DateTime(2015, 1,13)),
                  Tuple.Create(new DateTime(2015, 1,18), new DateTime(2015, 1,20))
              };
          
              var availableDates = 
                    dates.Aggregate<Tuple<DateTime, DateTime>, 
                                    IEnumerable<DateTime>, 
                                    IEnumerable<DateTime>>
                          (new List<DateTime>(),
                          (allDates, nextRange) => allDates.Concat(Enumerable.Range(0, (nextRange.Item2 - nextRange.Item1).Days)
                                                           .Select(e => nextRange.Item1.AddDays(e))),
                           allDates => allDates);
          
          
              var numDays = 
                    availableDates.Aggregate<DateTime, 
                                            Tuple<DateTime, int>, 
                                            int> 
                                   (Tuple.Create(DateTime.MinValue, 0),                                                                                               
                                   (acc, nextDate) =>
                                   {
                                       int daysSoFar = acc.Item2;                                                                                    
                                       if ((nextDate - acc.Item1).Days == 1)                                                                                                
                                       {                                                                                      
                                          daysSoFar++;                                                                                                                                                                
                                       }                                                                                                                                                                                                                                                                                                                        
          
                                       return Tuple.Create(nextDate, daysSoFar);                                                                          
                                    },
                                    acc => acc.Item2);
          

          【讨论】:

            猜你喜欢
            • 2020-02-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-26
            • 1970-01-01
            • 2019-08-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多