How to get gap in date ranges from a period of tim

2019-07-17 02:22发布

问题:

I have an initial and a final date range = 1/1/2015 - 1/30/2015

I have these date ranges that represent dates of unavailability.

1/5/2015 - 1/10/2015
1/15/2015 - 1/20/2015
1/22/2015 - 1/28/2015

I want this output, mainly the dates of availability from the main range:

A: 1/1/2015 - 1/4/2015
B: 1/11/2015 - 1/14/2015
C: 1/21/2015 - 1/21/2015
D: 1/29/2015 - 1/30/2015

I tried to generate a sequential date range like this in order to get the exception dates with Except() but I think I'm complicating the thing.

 //dtStartDate = 1/1/2015
 //dtEndDate = 1/30/2015  
 var days = (int)(dtEndDate - dtStartDate).TotalDays + 1;
 var completeSeq = Enumerable.Range(0, days).Select(x => dtStartDate.AddDays(x)).ToArray();

How can I get the gap of date ranges from period of time.

I other words how can I get the A, B, C and D from this picture

http://www.tiikoni.com/tis/view/?id=ebe851c

If these dates overlap, they must not be considered only where is a gap.

----------UPDATE-----------

I think if I do this:

 var range = Enumerable.Range(0, (int)(1/10/2015 - 1/5/2015).TotalDays + 1).Select(i => 1/5/2015.AddDays(i));
 var missing = completeSeq.Except(range).ToArray();

for each date range I will have the exclusion of each date range given but still cannot get the gap!

回答1:

using System;
using System.Collections.Generic;
using System.Linq;
public static class Program {
    public static void Main() {
        Tuple<DateTime,DateTime> range=Tuple.Create(new DateTime(2015,1,1),new DateTime(2015,1,30));
        Tuple<DateTime,DateTime>[] exclude=new[] {
            Tuple.Create(new DateTime(2015,1,5),new DateTime(2015,1,10)),
            Tuple.Create(new DateTime(2015,1,15),new DateTime(2015,1,20)),
            Tuple.Create(new DateTime(2015,1,22),new DateTime(2015,1,28))
        };
        foreach(Tuple<DateTime,DateTime> r in ExcludeIntervals(range,exclude)) {
            Console.WriteLine("{0} - {1}",r.Item1,r.Item2);
        }
    }
    public static IEnumerable<Tuple<DateTime,DateTime>> ExcludeIntervals(Tuple<DateTime,DateTime> range,IEnumerable<Tuple<DateTime,DateTime>> exclude) {
        IEnumerable<Tuple<DateTime,bool>> dates=
            new[] { Tuple.Create(range.Item1.AddDays(-1),true),Tuple.Create(range.Item2.AddDays(1),false) }.
            Concat(exclude.SelectMany(r => new[] { Tuple.Create(r.Item1,false),Tuple.Create(r.Item2,true) })).
            OrderBy(d => d.Item1).ThenBy(d => d.Item2); //Get ordered list of time points where availability can change.
        DateTime firstFreeDate=default(DateTime);
        int count=1; //Count of unavailability intervals what is currently active. Start from 1 to threat as unavailable before range starts.
        foreach(Tuple<DateTime,bool> date in dates) {
            if(date.Item2) { //false - start of unavailability interval. true - end of unavailability interval.
                if(--count==0) { //Become available.
                    firstFreeDate=date.Item1.AddDays(1);
                }
            } else {
                if(++count==1) { //Become unavailable.
                    DateTime lastFreeDate=date.Item1.AddDays(-1);
                    if(lastFreeDate>=firstFreeDate) { //If next unavailability starts right after previous ended, then no gap.
                        yield return Tuple.Create(firstFreeDate,lastFreeDate);
                    }
                }
            }
        }
    }
}

ideone.com



回答2:

I saw your question in my morning today and really liked it, but was busy the whole day. So, got a chance to play with your question and believe me I enjoyed it. Here is my code:-

DateTime startDate = new DateTime(2015, 1, 1);
DateTime endDate = new DateTime(2015, 1, 30);
int totalDays = (int)(endDate - startDate).TotalDays + 1;
availability.Add(new Availability { StartDate = endDate, EndDate = endDate });

var result = from x in Enumerable.Range(0, totalDays)
            let d = startDate.AddDays(x)
            from a in availability.Select((v, i) => new { Value = v, Index = i })
            where (a.Index == availability.Count - 1 ? 
                     d <= a.Value.StartDate : d < a.Value.StartDate)
            && (a.Index != 0 ? d > availability[a.Index - 1].EndDate : true)
            group new { d, a } by a.Value.StartDate into g
            select new
            {
                 AvailableDates = String.Format("{0} - {1}",g.Min(x => x.d),
                                                            g.Max(x => x.d))
            };

This, definitely need explanation so here it is:-

Step 1: Create a range of dates from Jan 01 till Jan 30 using Enumerable.Range
Step 2: Since after the second unavailable date range, we need to limit the dates selected from last endate till current object startdate, I have calculated index so that we can get access to the last enddate.
Step 3: Once we get the index, all we need to do is filter the dates except for first date range since we didn't have last object in this case.
Step 4: For the last item since we don't have the max range I am adding the endDate to our unavailable list (hope this makes sense).

Here is the Working Fiddle, if you get confused just remove group by and other filters and debug and see the resulting output it will look fairly easy :)



回答3:

Got a little oopy...

public class DateRange
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }

        public bool HasStart
        {
            get { return Start != DateTime.MinValue; }
        }
        public bool IsInRange(DateTime date)
        {
            return (date >= this.Start && date <= this.End);
        }

        public List<DateRange> GetAvailableDates(DateRange excludedRange)
        {
            return GetAvailableDates(new List<DateRange>(){excludedRange});
        }

        public List<DateRange> GetAvailableDates(List<DateRange> excludedRanges)
        {
            if (excludedRanges == null)
            {
                return new List<DateRange>() { this };
            }
            var list = new List<DateRange>();
            var aRange = new DateRange();
            var date = this.Start;
            while (date <= this.End)
            {
                bool isInARange = excludedRanges.Any(er => er.HasStart && er.IsInRange(date));
                if (!isInARange)
                {
                    if (!aRange.HasStart)
                    {
                        aRange.Start = date;
                    }
                    aRange.End = date;
                }
                else
                {
                    if (aRange.HasStart)
                    {
                        list.Add(aRange);
                        aRange = new DateRange();
                    }
                }
                date = date.AddDays(1);
            }
            if (aRange.HasStart)
            {
                list.Add(aRange);
            }
            return list;
            }
}


标签: c# linq date range