计算重叠日期的夜晚数

时间:2015-06-10 11:16:55

标签: c# date logic

我遇到了解决这个问题的逻辑方法的问题。 我有一个日期范围列表,比如说:

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

我可以很容易地计算出使用最小 - 最大日期的总夜数,但忽略了缺少的日期(示例中为14-17),这是我无法弄清楚的。

有没有办法找到失踪的总天数来帮助解决这个问题?

6 个答案:

答案 0 :(得分:3)

以下是使用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;
        }
    }
}

注意:对于大日期范围,这不是非常有效!如果要考虑大的日期范围,将重叠范围组合到单个范围的方法会更好。

[编辑]修复了使用半开间隔而非封闭间隔的代码。

答案 1 :(得分:1)

这样的事情应该有效:

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

答案 2 :(得分:1)

你可以像这样计算:

var laterDateTime = Convert.ToDateTime("11/01/15 ");
var earlierDateTime = Convert.ToDateTime("01/01/15");

TimeSpan dates = laterDateTime - earlierDateTime;

int nights = dates.Days - 1;

您可以将所有内容转换为DateTime。 然后,您可以使用 - 运算符减去两个DateTimes。 您的结果将是一种结构TimeSpan。

TimeSpan hat a Days属性。从中减去1并且你收到了夜晚。

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

我相信你可以做其余的事。

答案 3 :(得分:1)

假设有两个晚上,例如两晚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 :(得分:1)

我认为这个解决方案会更快,然后循环使用内部循环的范围来插入列表中的天数。该解决方案不需要额外的空间。它是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;
    }
}

答案 5 :(得分:0)

如果您没有足够的答案,可以使用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);