Getting Overlapping Start and End Date - c#

Given a DateRange I need to return a list of Start and EndDates that overlaps the given period.
what is the best way to do it? Thanks for your time in advance.
static void Main(string[] args)
{
//Period 1stMarch to 20th April
var startDate = new DateTime(2011, 03, 1);
var endDate = new DateTime(2011, 4, 20);
List<BookedPeriod> bookedPeriods=new List<BookedPeriod>();
bookedPeriods.Add(new BookedPeriod {StartDate = new DateTime(2011, 02, 5), EndDate = new DateTime(2011, 3, 15)});
bookedPeriods.Add(new BookedPeriod { StartDate = new DateTime(2011, 03, 20), EndDate = new DateTime(2011, 4, 10) });
bookedPeriods.Add(new BookedPeriod { StartDate = new DateTime(2011, 04, 01), EndDate = new DateTime(2011, 4, 15) });
List<OverlappedPeriod> myOverllappedPeriods = GetOverllapedPeriods(startDate, endDate, bookedPeriods);
}
public static List<OverlappedPeriod>GetOverllapedPeriods(DateTime startDate,DateTime endDate,List<BookedPeriod>bookedPeriods)
{
List<OverlappedPeriod>overlappedPeriods=new List<OverlappedPeriod>();
// Given a DateRange I need to return a list of Start and EndDates that overlaps
//??how I do i
return overlappedPeriods;
}
}
public class BookedPeriod
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public class OverlappedPeriod
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
EDITED
I have decided to edit in the hope of clarifing and therefore helping who has to help me.
I get confused by overlapped vs intersect.
A scenario might help
I have booked a subscription to the gym that goes from 01 March 11 to 20 April 11
I go on holiday between 05 Feb 11 to 15 Mar 11
I need to get the start and end date when my subscription is valid that I will NOT BE GOING TO THE GYM
The result I should get back is StartDate=1 March 2011 EndDate =15 March 2011
myAttempt:
static void Main()
{
//Holiday Period
var startDate = new DateTime(2011, 02, 5);
var endDate = new DateTime(2011, 3, 15);
List<BookedPeriod> bookedPeriods = new List<BookedPeriod>();
bookedPeriods.Add(new BookedPeriod { StartDate = new DateTime(2011, 02, 5), EndDate = new DateTime(2011, 4, 20) });
List<OverlappedPeriod> overlappedPeriods=new List<OverlappedPeriod>();
foreach (var bookedPeriod in bookedPeriods)
{
DateTime newStartDate = new DateTime();
DateTime newEndDate = new DateTime();
OverlappedPeriod overlappedPeriod=new OverlappedPeriod();
overlappedPeriod.StartDate = newStartDate;
overlappedPeriod.EndDate = newEndDate;
GetDateRange(bookedPeriod.StartDate, bookedPeriod.EndDate, out newStartDate, out newEndDate);
overlappedPeriods.Add(overlappedPeriod);
}
//do something with it
}
private static void GetDateRange(DateTime startDate,DateTime endDate,out DateTime newStartDate,out DateTime newEndDate)
{
/*
* I need to get the start and end date when my subscription is valid that I will NOT BE GOING TO THE GYM
The result I should get back is StartDate=1 March 2011 EndDate =15 March 2011
*/
}

Are you looking for dates that are completely within the provided period, or only partially?
Completely within the range:
var overlapped =
from period in bookedPeriods
where period.StartDate >= startDate && period.EndDate <= endDate
select new OverlappedPeriod { StartDate = period.StartDate, EndDate = period.EndDate };
overlappedPeriods = overlapped.ToList();
Partially overlapping:
var overlapped =
from period in bookedPeriods
where (period.StartDate >= startDate && period.EndDate <= endDate)
|| (period.EndDate >= startDate && period.EndDate <= endDate)
|| (period.StartDate <= startDate && period.EndDate >= startDate)
|| (period.StartDate <= startDate && period.EndDate >= endDate)
select new OverlappedPeriod
{
StartDate = new DateTime(Math.Max(period.StartDate.Ticks, startDate.Ticks)),
EndDate = new DateTime(Math.Min(period.EndDate.Ticks, endDate.Ticks))
};
overlappedPeriods = overlapped.ToList();

this piece of LINQ might help:
var overlappedPeriods = bookedPeriods.Where(p=>p.EndDate > startDate && p.StartDate < endDate);
then transform the results accordingly to your OverlappedPeriod class

Something like this (not tested sorry):
return bookedPeriods.Where(
b => (b.StartDate > startDate && b.StartDate < endDate) ||
(b.EndDate> startDate && b.EndDate < endDate) ||
(b.StartDate < startDate && b.EndDate > endDate)
).ToList()

One method would be to make use of the Rectangle class to calculate intersects. So the procedure would be to create a rectangle for each date range then use the Rectangle.Intersect( ) method.

Related

iterating between dates, hours and minutes

I am trying to iterate between 2 dates received as inputs and print every 5 minutes (during working hours)
Seems like I am getting to an endless and can get my app stop at endTime
DateTime startDate = new DateTime(2018, 1, 1);
DateTime endDate = new DateTime(2018, 3, 1);
// day in month
for (DateTime date = startDate; date < endDate; date = date.AddDays(1))
{
if (date.DayOfWeek == DayOfWeek.Friday || date.DayOfWeek == DayOfWeek.Saturday)
continue;
//iterate every hour
for (var hour = date; hour < hour.AddDays(1); hour = hour.AddHours(1))
{
if (hour.Hour < 8 || hour.Hour > 17)
continue;
//iterate every minute
for (var min = date; min <= min.AddDays(1); min = min.AddMinutes(5))
{
Console.WriteLine(min);
}
}
}
Maybe you're overcomplicating; take a look into this:
var startDate = new DateTime(2018, 1, 1);
var endDate = new DateTime(2018, 3, 1);
while ((startDate = startDate.AddMinutes(5)) < endDate)
{
if (startDate.Hour < 8 || startDate.Hour > 17 ||
startDate.DayOfWeek == DayOfWeek.Saturday ||
startDate.DayOfWeek == DayOfWeek.Sunday)
continue;
Console.WriteLine("{0:ddd, MMM dd, yyyy HH:mm}", startDate);
}
You just need a loop, incrementing by 5 minutes until the endDate is met; inside the loop you skip all values you don't want (weekends and non-business hours).
In this code I'm reusing the startDate as work variable, but you definitely could create a new one and make things clearer.

Lambda between two dates explicit

I am in need of some wizards.
I have a table
Start End PersonID
-----------------------------------------------------
10/07/2017 00:00:00 18/07/2017 00:00:00 1
27/07/2017 00:00:00 27/07/2017 00:00:00 1
28/07/2017 00:00:00 28/07/2017 00:00:00 1
29/07/2017 00:00:00 29/07/2017 00:00:00 1
30/07/2017 00:00:00 30/07/2017 00:00:00 1
If I search for
Date Start = 11/07/2017
Date End = 12/07/2017
Using this query:
DateTime start = new DateTime(2017,07,11,0,0,0,0,0);
DateTime end = start.AddDays(1);
DateTime[] days = new DateTime[end.Subtract(start).Days];
for (int i = 0; i < end.Subtract(start).Days; i++)
{
var d = start.AddDays(i);
days[i] = d;
}
IQueryable block = tmOpen1.Calendar.Where(x => days.All(y => y >= x.start && y <= x.end)).Select(x => new { ID = x.PersonID });`
I get a positive result for ROW 1 (10/07/2017 - 18/07/2017)
However If I apply it against the remaining rows e.g. Filter
Date Start = 28/07/2017
Date End = 29/07/2017
Then obviously this will fail. How Can I get this side of the search to work.
E.g. Either
Take the first row and make it split out into individual rows
Make the Individual rows return true if a Person has several true conditions.
I hope one of the geniuses here can help.
Seems like all you really need is something like this:
DateTime start = new DateTime(2017,07,11,0,0,0,0,0);
DateTime end = start.AddDays(1);
var results = tmOpen1.Calendar
.Where(c => start <= c.end && end >= c.start)
.Select(x => new { ID = x.PersonID });
If your interval starts or ends somewhere between a start and end date from the table, than it means it is overlapping and you should included in your result.
tmOpen1.Calendar.Where(x => (startDate >= x.start && startDate <= x.end) || (endDate >= x.start && endDate <= x.end)).Select(x => new { ID = x.PersonID });
So an interval 10.07 - 27.07 should give you the first 2 rows, right?
Or is the interval supposed to be fully enclosed between 2 dates in the table?
From understanding of your question you want to know when the Date Start or Date End is within a range of dates.
You can check Date Start is within the date range or the Date End is within the date range
Example:
List<DateRange> dates = new List<DateRange>();
dates.Add(new DateRange()
{
StartDate = new DateTime(2017, 07, 10),
EndDate = new DateTime(2017, 07, 18)
});
dates.Add(new DateRange()
{
StartDate = new DateTime(2017, 07, 28),
EndDate = new DateTime(2017, 07, 28)
});
DateRange search1 = new DateRange()
{
StartDate = new DateTime(2017, 07, 11),
EndDate = new DateTime(2017, 07, 12)
};
DateRange search2 = new DateRange()
{
StartDate = new DateTime(2017, 07, 28),
EndDate = new DateTime(2017, 07, 29)
};
var result1 = dates.Where(x => search1.StartDate >= x.StartDate && search1.StartDate <= x.EndDate ||
search1.EndDate <= x.StartDate && search1.EndDate >= x.EndDate);
var result2 = dates.Where(x => search2.StartDate >= x.StartDate && search2.StartDate <= x.EndDate ||
search2.EndDate <= x.StartDate && search2.EndDate >= x.EndDate);
Simplier with the not valid time frame:
DateTime start = new DateTime(2017, 07, 11, 0, 0, 0, 0, 0);
DateTime end = start.AddDays(1);
var results = tmOpen1.Calendar.
.Where( c => ! ( c.Start > end || c.End < start) )
.Select(x => new { ID = x.PersonID } );
For DateTime start = new DateTime(2017, 07, 11, 0, 0, 0, 0, 0);
The result are:
TEST 1: 11/07/2017 00:00:00
Start:10/07/2017 00:00:00 End:18/07/2017 00:00:00 ID:1
For DateTime start = new DateTime(2017, 07, 28, 0, 0, 0, 0, 0);
The result are:
TEST 2: 28/07/2017 00:00:00
Start:28/07/2017 00:00:00 End:28/07/2017 00:00:00 ID:1
Start:29/07/2017 00:00:00 End:29/07/2017 00:00:00 ID:1
modelclassList= modelclassList.Where(x => x.gf_expdate>DateTime.Now).ToList();
to check expiry date and save back list of model class

How to find exact date range from list of date ranges by entering single date

I want to find the date range which falls in input date, following is structure
public class Duration
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
var durations = new List<Duration>();
var duration1 = new Duration()
{
StartDate = new DateTime(2017, 08, 1),
EndDate = new DateTime(2017, 08, 10)
};
durations.Add(duration1);
var duration2 = new Duration()
{
StartDate = new DateTime(2017, 08, 5),
EndDate = new DateTime(2017, 08, 10)
};
durations.Add(duration2);
var duration3 = new Duration()
{
StartDate = new DateTime(2017, 08, 5),
EndDate = new DateTime(2017, 08, 6)
};
durations.Add(duration3);
Now I want to find duration which is closest to the entered date for list of <Durations> with LINQ or for-loop
My expected result for currentDate=new DateTime(2017, 08, 7); is duration2
You first need to check if the currentDate is within the start and end dates of each range. For the ones that meet that condition, you calculate the "closeness" adding both distances. When you find one lapse(gap) smaller tan the previous, you save its index... and voilá
int lapse = Integer.MaxValue;
int counter = 0;
int index = 0;
foreach (d in durations) {
if (((d.StartDate <= currentDate) && (d.EndDate >= currentDate))) {
int newlapse = ((currentDate - d.StartDate).TotalDays + (d.EndDate - currentDate).TotalDays);
if ((newlapse < lapse)) {
lapse = newlapse;
index = counter;
}
}
counter +=1;
}
return durations(index);
If you need the middle of interval to be closest:
durations.OrderBy((d) => Math.Abs(d.EndDate.Ticks + d.StartDate.Ticks) / 2 - currentDate.Ticks).FirstOrDefault();
If you need the start of interval to be closest:
durations.OrderBy((d) => Math.Abs(d.EndDate.Ticks - currentDate.Ticks)).FirstOrDefault();
As D le mentioned above
First check if currentDate is within the start and end dates
Second select the duration with the minimal difference between start end end date
I used a nuget package called morelinq which gives nice extensions methods like MinBy:
var result = (from d in durations
where (d.StartDate <= currentDate && d.EndDate >= currentDate)
select d).MinBy(d => d.EndDate - d.StartDate);

How to calculate list of months in specified date range in UmAlQuraCalendar

I want to calculate a list of months in specified date range.
For instance:
DateTime StartDate = 24 - 11 - 2014;
DateTime EndDate = 24 - 11 - 2016;
I want to calculate all the months between starting and ending date with names of months.
Here you go a static function that do what you need:
public static Dictionary<int, string> MonthsBetween(
DateTime startDate,
DateTime endDate)
{
DateTime iterator;
DateTime limit;
if (endDate > startDate)
{
iterator = new DateTime(startDate.Year, startDate.Month, 1);
limit = endDate;
}
else
{
iterator = new DateTime(endDate.Year, endDate.Month, 1);
limit = startDate;
}
var dateTimeFormat = CultureInfo.CurrentCulture.DateTimeFormat;
var result = new Dictionary<int, string>();
while (iterator <= limit)
{
if (!result.Keys.Contains(iterator.Month))
result.Add(iterator.Month, dateTimeFormat.GetMonthName(iterator.Month));
iterator = iterator.AddMonths(1);
}
return result;
}
you can use it like this:
DateTime startDate = new DateTime(2014, 11, 24);
DateTime endDate = new DateTime(2016, 11, 24);
var list = Program.MonthsBetween(startDate, endDate);
list variable contains dictionary with month int value and name according to CultureInfo.CurrentCulture of your program.
I get this function from this answer and slightly modify it.

Extract sub-periods within a period

Given a period starting from StartingDate to EndingDate.
I want to get the intervals within that period starting given StartingMonth and EndingMonth.
Example :
StartingMonth = april (4)
EndingMonth = november (11)
Periods :
Period A : StartingDate = (2014, 03, 01); EndingDate = (2015, 02, 28);
Period B : StartingDate = (2014, 07, 01); EndingDate = (2015, 06, 30);
Period C : StartingDate = (2014, 01, 01); EndingDate = (2015, 12, 31);
Would return :
Period A : 1 sub-period = (2014, 4, 1) - (2014, 11, 30)
Period B : 2 sub-periods = (2014, 7, 1) - (2014, 11, 30) ; (2015, 4, 1) - (2015, 6, 30)
Period C : 2 sub-periods = (2014, 4, 1) - (2014, 11, 30) ; (2015, 4, 1) - (2015, 11, 30)
I have tried this (seems to be the hard way and does not manage multiple sub-periods):
May be an easier way using LINQ ?
if (StartingDate.Month < startingMonth && EndingDate.Month < endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month > startingMonth && EndingDate.Month > endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month < startingMonth && EndingDate.Month > endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month > startingMonth && EndingDate.Month < endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
The idea is to returns the blue periods within the red period :
class Discount
{
public int DiscountID { get; set; } //You will need some Key field if you are storing these in a database.
public DateTime issueDate { get; set; }
public DateTime expirationDate { get; set; }
public List<PeriodInterval> intervals { get; set; }
public Discount(DateTime IssueDate, DateTime ExpirationDate)
{
issueDate = IssueDate;
expirationDate = ExpirationDate;
intervals = new List<PeriodInterval>();
}
public void AddInterval(DateTime StartDate, DateTime EndDate)
{
intervals.Add(new PeriodInterval() {
StartMonth=StartDate.Month,
StartDay=StartDate.Day,
EndMonth=EndDate.Month,
EndDay=EndDate.Day
});
}
public List<Period> GetPeriods()
{
List<Period> periods=new List<Period>();
int yearCount = expirationDate.Year-issueDate.Year+1; //+1: Run at least one year against the periods.
for (int i = 0; i < yearCount; i++)
{
//Loop through all the years and add 'Periods' from all the PeriodInterval info.
foreach (PeriodInterval pi in intervals)
{
var period = pi.GetPeriod(issueDate, expirationDate, i);
if (period != null)
periods.Add(period);
}
}
return periods;
}
}
class Period
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
class PeriodInterval
{
public int PeriodIntervalID { get; set; } //You will need some Key field if you are storing these in a database.
public int DiscountID { get; set; } //Foreign Key to Discount. This is alsof for database storage.
public int StartMonth { get; set; }
public int StartDay { get; set; }
public int EndMonth { get; set; }
public int EndDay { get; set; }
public Period GetPeriod(DateTime issueDate, DateTime expirationDate, int Year)
{
DateTime PeriodStart = new DateTime(issueDate.AddYears(Year).Year, StartMonth, StartDay);
DateTime PeriodEnd = new DateTime(issueDate.AddYears(Year).Year, EndMonth, EndDay);
PeriodStart=new DateTime(Math.Max(PeriodStart.Ticks, issueDate.Ticks)); //Limit period to the max of the two start dates.
PeriodEnd = new DateTime(Math.Min(PeriodEnd.Ticks, expirationDate.Ticks)); //Limit period to the min of the two end dates.
if(PeriodEnd>PeriodStart) //If a valid period
{
return new Period()
{
StartDate = PeriodStart,
EndDate = PeriodEnd
};
}
//Default Return Null
return null;
}
}
I built a console application to test this out:
static void Main(string[] args)
{
List<Discount> Discounts = new List<Discount>();
Discount d1 = new Discount(new DateTime(2014, 3, 1), new DateTime(2015, 02, 28));
Discount d2 = new Discount(new DateTime(2014, 7, 1), new DateTime(2015, 06, 30));
Discount d3 = new Discount(new DateTime(2014, 01, 1), new DateTime(2015, 12, 31));
Discounts.Add(d1);
Discounts.Add(d2);
Discounts.Add(d3);
foreach (Discount d in Discounts)
{
d.AddInterval(new DateTime(2014, 4, 1), new DateTime(2014, 11, 30));
Console.WriteLine("IssueDate:{0} ExpirationDate:{1}", d.issueDate, d.expirationDate);
foreach (Period p in d.GetPeriods())
{
Console.WriteLine("Start:{0} End:{1}", p.StartDate, p.EndDate);
}
}
Console.ReadLine();
}
Here's what that prints out:
You can use the Time Period Library for .NET:
// ----------------------------------------------------------------------
public void ExtractSubPeriods()
{
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 4, 1 ), new DateTime( 2015, 2, 28 ) ) ) )
{
Console.WriteLine( "SubPeriods 1: {0}", subPeriod );
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 7, 1 ), new DateTime( 2015, 6, 30 ) ) ) )
{
Console.WriteLine( "SubPeriods 2: {0}", subPeriod );
}
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 4, 1 ), new DateTime( 2015, 12, 31 ) ) ) )
{
Console.WriteLine( "SubPeriods 3: {0}", subPeriod );
}
} // ExtractSubPeriods
// ----------------------------------------------------------------------
public ITimePeriodCollection GetSubPeriods( ITimeRange timeRange )
{
ITimePeriodCollection periods = new TimePeriodCollection();
periods.Add( timeRange );
int startYear = periods.Start.Year;
int endYear = periods.End.Year + 1;
for ( int year = startYear; year <= endYear; year++ )
{
periods.Add( new TimeRange( new DateTime( year, 4, 1 ), new DateTime( year, 12, 1 ) ) );
}
TimePeriodIntersector<TimeRange> intersector = new TimePeriodIntersector<TimeRange>();
return intersector.IntersectPeriods( periods );
} // GetSubPeriods
A few things to consider:
As humans, we typically use fully-inclusive ranges for date-only values, while we use half-open intervals for time-only or date+time values. Think: 2 days from Jan 1 to Jan 2, but 1 hour from 1:00 to 2:00, or from Jan 1 Midnight to Jan 2 Midnight.
.Net's built-in DateTime type, is a date+time type. When you omit the time, it uses midnight. You cannot remove the time portion.
If you were to use DateTime with date-at-midnight ranges, the best you could do is choose to ignore the time portion. That makes for some tricky code, as you would have to normalize your inputs to midnight before comparing against the range. I don't recommend this approach, as it is error prone. The edge cases will pile up quickly.
Therefore, I recommend either switching to half-open intervals with DateTime, or if you need to continue to use fully-inclusive ranges then consider using the LocalDate type from Noda Time. I will show you examples of both.
Because you are accepting month numbers as inputs, consider that you should also handle the case of them being out of sequence. That is, a two-month sub-period may range from December of one year, to January of the next.
Unless there are guarantees that the outer period will fall exactly at the start and end points of a whole month, you will need to trim the results. For example, if your Period ran from Jan 3 2014 to March 9 2016, then the subperiod in 2015 would have the whole months, but 2014 would be trimmed at the start and 2016 would be trimmed at the end.
Here is how you can achieve this using DateTime and half-open date-at-midnight intervals:
public class DateTimeInterval
{
/// <summary>
/// The date and time that the interval starts.
/// The interval includes this exact value.
/// </summary>
public DateTime StartDate { get; private set; }
/// <summary>
/// The date and time that the interval is over.
/// The interval excludes this exact value.
/// </summary>
public DateTime EndDate { get; private set; }
public DateTimeInterval(DateTime startDate, DateTime endDate)
{
StartDate = startDate;
EndDate = endDate;
}
public IEnumerable<DateTimeInterval> GetSubIntervals(int startingMonth,
int endingMonth)
{
// Determine the possible ranges based on the year of this interval
// and the months provided
var ranges = Enumerable.Range(StartDate.Year,
EndDate.Year - StartDate.Year + 1)
.Select(year => new DateTimeInterval(
new DateTime(year, startingMonth, 1),
new DateTime(
startingMonth > endingMonth ? year + 1 : year,
endingMonth, 1)
.AddMonths(1)));
// Get the ranges that are overlapping with this interval
var results = ranges.Where(p => p.StartDate < this.EndDate &&
p.EndDate > this.StartDate)
.ToArray();
// Trim the edges to constrain the results to this interval
if (results.Length > 0)
{
if (results[0].StartDate < this.StartDate)
{
results[0] = new DateTimeInterval(
this.StartDate,
results[0].EndDate);
}
if (results[results.Length - 1].EndDate > this.EndDate)
{
results[results.Length - 1] = new DateTimeInterval(
results[results.Length - 1].StartDate,
this.EndDate);
}
}
return results;
}
}
Using the above code:
var interval = new DateTimeInterval(new DateTime(2014, 3, 1), // inclusive
new DateTime(2015, 3, 1)); // exclusive
var subIntervals = interval.GetSubIntervals(4, 11);
And here is how you can achieve the same thing using NodaTime.LocalDate and fully-inclusive date-only intervals:
using NodaTime;
public class LocalDateInterval
{
/// <summary>
/// The date that the interval starts.
/// The interval includes this exact value.
/// </summary>
public LocalDate StartDate { get; private set; }
/// <summary>
/// The date that the interval ends.
/// The interval includes this exact value.
/// </summary>
public LocalDate EndDate { get; private set; }
public LocalDateInterval(LocalDate startDate, LocalDate endDate)
{
StartDate = startDate;
EndDate = endDate;
}
public IEnumerable<LocalDateInterval> GetSubIntervals(int startingMonth,
int endingMonth)
{
// Determine the possible ranges based on the year of this interval
// and the months provided
var ranges = Enumerable.Range(StartDate.Year,
EndDate.Year - StartDate.Year + 1)
.Select(year => new LocalDateInterval(
new LocalDate(year, startingMonth, 1),
new LocalDate(
startingMonth > endingMonth ? year + 1 : year,
endingMonth, 1)
.PlusMonths(1).PlusDays(-1)));
// Get the ranges that are overlapping with this interval
var results = ranges.Where(p => p.StartDate <= this.EndDate &&
p.EndDate >= this.StartDate)
.ToArray();
// Trim the edges to constrain the results to this interval
if (results.Length > 0)
{
if (results[0].StartDate < this.StartDate)
{
results[0] = new LocalDateInterval(
this.StartDate,
results[0].EndDate);
}
if (results[results.Length - 1].EndDate > this.EndDate)
{
results[results.Length - 1] = new LocalDateInterval(
results[results.Length - 1].StartDate,
this.EndDate);
}
}
return results;
}
}
Using the above code:
var interval = new LocalDateInterval(new LocalDate(2014, 3, 1), // inclusive
new LocalDate(2015, 2, 28)); // inclusive
var subIntervals = interval.GetSubIntervals(4, 11);
This shoud work:
var periods = Periods
.Select(p => new {
p = p,
a = p.StartingDate.Year*12 + p.StartingDate.Month - 1,
b = p.EndingDate.Year*12 + p.EndingDate.Month
}
)
.Select(x => new {
period = x.p,
subperiods =
Enumerable
.Range(x.a, x.b - x.a)
.Select(e => new DateTime(e/12, e%12 + 1, 1))
.Where(d => StartingMonth <= d.Month && d.Month <= EndingMonth)
.GroupBy(i => i.Year)
.Where(g => g.Count() > 1)
.Select(g => new Period {
StartingDate = g.Min(),
EndingDate = g.Max()
})
.Select(p => new Period {
StartingDate = p.StartingDate < x.p.StartingDate ? x.p.StartingDate : p.StartingDate,
EndingDate = (p.EndingDate > x.p.EndingDate ? x.p.EndingDate : p.EndingDate)
.AddMonths(1)
.AddDays(-1)
})
});
UPDATE
According to your image, this would do the trick:
var periods = Periods
.Select(p => new {
p = p,
a = p.StartingDate.Year*12 + p.StartingDate.Month - 1,
b = p.EndingDate.Year*12 + p.EndingDate.Month
}
)
.Select(x => new {
period = x.p,
subperiods =
Enumerable
.Range(x.a, x.b - x.a)
.Select(e => new DateTime(e/12, e%12 + 1, 1))
.Where(d => StartingMonth <= d.Month && d.Month <= EndingMonth)
.GroupBy(i => i.Year)
.Where(g => g.Count() > 1)
.Select(g => g.Select(i => i))
});

Categories