Find Gaps Between date ranges - c#

I need your help. I'm trying to figure out how I can get find Gaps between EndDate and the next StartDate. If there is a Gap I need to return the values where the gap is. Can you please help me?
I have a list with StartDate and Enddate.
Example:
Startdate: 2/01/2021 Enddate: 2/10/2021
Startdate: 2/11/2021 Enddate: 2/15/2021
Startdate: 2/20/2021 Enddate: 2/25/2021
Between 1 and 2 is no Gap. Between 2 (Enddate) and 3 (Stardate) is a Gap and needs to return a result like this: Gap found between 2/16/2021 and 2/19/2021.
I already tried this one but it didn't work out for me well, may someone of you can help me here?
static void Main(string[] args)
{
List<DateRanges> DateRanges = new List<DateRanges>();
DateRanges.Add(new DateRanges() { StartDate = new DateTime(2021, 01, 01), EndDate = new DateTime(2021, 01, 31) });
DateRanges.Add(new DateRanges() { StartDate = new DateTime(2021, 02, 10), EndDate = new DateTime(2021, 02, 25) });
var missing = DateRangeEnumerable.GetDates(DateRanges);
var ranges = missing.GetRanges();
}
public class DateRanges
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
//public DateRanges(DateTime Start, DateTime End)
//{
// StartDate = Start;
// EndDate = End;
//}
}
public static class DateRangeEnumerable
{
public static IEnumerable<DateTime> GetDates(this IEnumerable<DateRanges> source)
{
var sortedSource = source.OrderBy(r => r.StartDate);
foreach (var range in sortedSource)
{
var d = range.StartDate;
while (d < range.EndDate)
{
yield return d;
d = d.AddDays(1);
}
}
}
public static IEnumerable<DateRanges> GetRanges(this IEnumerable<DateTime> source)
{
var sortedSource = source.OrderBy(d => d);
var enumerator = sortedSource.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
DateTime from = enumerator.Current;
DateTime prev = from;
while (true)
{
while (true)
{
if (enumerator.MoveNext())
{
if (enumerator.Current == prev.AddDays(1))
prev = enumerator.Current;
else
break;
}
else
{
yield return new DateRanges() { StartDate = from, EndDate = prev.AddDays(1) };
yield break;
}
}
yield return new DateRanges() { StartDate = from, EndDate = prev.AddDays(1) };
from = enumerator.Current;
prev = enumerator.Current;
}
}
}

Exmaple
var dateRanges = new List<DateRanges>
{
new() {StartDate = new DateTime(2021, 2, 01), EndDate = new DateTime(2021, 2, 10)},
new() {StartDate = new DateTime(2021, 02, 11), EndDate = new DateTime(2021, 02, 15)},
new() {StartDate = new DateTime(2021, 02, 20), EndDate = new DateTime(2021, 02, 25)}
};
var end = dateRanges.First().EndDate;
foreach (var range in dateRanges.Skip(1))
{
if(end.AddDays(1) < range.StartDate )
Console.WriteLine($"{end.AddDays(1):d} - {range.StartDate.AddDays(-1):d}");
end = range.EndDate;
}
Output
16/02/2021 - 19/02/2021
Disclaimer : This contains no error checking, no sorting, nothing implied from your code, and assumes that dates have no time value. It merely just replicates your description

What about using a linked list ?
Code:
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
internal static class Program
{
private static void Main(string[] args)
{
var ranges = new List<DateRange>();
ranges.Add(
new DateRange
{
StartDate = new DateTime(2021, 01, 01),
EndDate = new DateTime(2021, 01, 31)
}
);
ranges.Add(
new DateRange
{
StartDate = new DateTime(2021, 02, 10),
EndDate = new DateTime(2021, 02, 25)
}
);
var list = new LinkedList<DateRange>(ranges);
var gaps = new List<LinkedListNode<DateRange>>();
var current = list.First;
while (current != null)
{
var next = current.Next;
if (next != null && next.Value.StartDate > current.Value.EndDate)
gaps.Add(current);
current = next;
}
Console.WriteLine("Gaps found:");
foreach (var gap in gaps)
{
Console.WriteLine("---------------------------");
Console.WriteLine("Date Range 1:");
Console.WriteLine(gap.Value);
Console.WriteLine("Date Range 2:");
Console.WriteLine(gap.Next!.Value);
Console.WriteLine(
$"Gap length in minutes: {(gap.Next.Value.StartDate - gap.Value.EndDate).TotalMinutes}");
Console.WriteLine("---------------------------");
}
}
}
public class DateRange
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public override string ToString()
{
return $"{nameof(StartDate)}: {StartDate}, {nameof(EndDate)}: {EndDate}";
}
}
}
Result:
Gaps found:
---------------------------
Date Range 1:
StartDate: 1/1/2021 12:00:00 AM, EndDate: 1/31/2021 12:00:00 AM
Date Range 2:
StartDate: 2/10/2021 12:00:00 AM, EndDate: 2/25/2021 12:00:00 AM
Gap length in minutes: 14400
---------------------------

I like to use a generic extension method that returns subsequent item-pairs:
public static IEnumerable<(T Previous, T Next)> PreviousAndNext<T>(this IEnumerable<T> self)
{
using (var iter = self.GetEnumerator())
{
if (!iter.MoveNext())
yield break;
var previous = iter.Current;
while (iter.MoveNext())
{
var next = iter.Current;
yield return (previous, next);
previous = next;
}
}
}
This allow for a very compact linq query
DateRanges.PreviousAndNext()
.Where(p => p.Previous.EndDate.AddDays(1) < p.Next.StartDate)
.Select(p => (p.Previous.EndDate, p.Next.StartDate));

Well, you can enumerate the gaps like this:
private static IEnumerable<DateRanges> Gaps(IEnumerable<DateRanges> dates) {
var source = dates
.OrderBy(range => range.StartDate);
DateTime last = DateTime.MinValue;
bool first = true;
foreach (var range in source) {
if (!first && range.StartDate.AddDays(1) > last)
yield return new DateRanges() {
StartDate = last.AddDays(1),
EndDate = range.StartDate.AddDays(-1))
};
// In case of overlapping periods we have to pick the latest date
last = first || range.EndDate > last
? range.EndDate
: last;
first = false;
}
}
If you want to print them all you have to do is to loop:
foreach (var gap in Gaps(dateRanges))
Console.WriteLine($"Gap found between {gap.StartDate} and {gap.EndDate}");

Related

Reversing a list of objects with a timespan property, while maintaining the time difference between the objects

I'd like to reverse a list of objects with a TimeSpan property, which should maintain it's TimeSpan difference when reversing.
To give an example, consider a route from A to D with the following TimeSpans:
(A 12:00), (B 12:15), (C 12:40), (D 13:40).
Between A and B there is a 15 minute difference, between B and C there is a 25 minute difference and so on. I'd like to reverse this list in an efficient manner, where the result list would look like:
(D: 12:00), (C 13:00), (B 13:25), (A 13:40).
My first idea was creating a list of time differences and using that and the start time to create the new objects with the correct times, however I feel like the solution could be better.
Edit: Added my (working) sample solution. Any feedback is appreciated.
private IList<Activity> ReverseActivities(IList<Activity> activities)
{
IList<TimeSpan> timeDifferences = GetTimeDifferences(activities);
IList<Activity> resultList = new List<Activity>();
TimeSpan timeOfDay = activities.First().TimeOfDay;
for (int i = activities.Count - 1; i >= 0; i--)
{
resultList.Add(new Activity(activities[i].Name, timeOfDay));
timeOfDay = timeOfDay.Add(timeDifferences[i]);
}
return resultList;
}
private IList<TimeSpan> GetTimeDifferences(IList<Activity> activities)
{
IList<TimeSpan> timeDifferences = new List<TimeSpan>();
Activity prev = activities.First();
if (activities.Count > 1)
{
foreach (var curr in activities)
{
timeDifferences.Add(curr.TimeOfDay - prev.TimeOfDay);
prev = curr;
}
}
return timeDifferences;
}
Activity looks as follows:
public class Activity
{
public Activity(string name, TimeSpan timeOfDay)
{
this.Name = name;
this.TimeOfDay = timeOfDay;
}
public string Name { get; }
public TimeSpan TimeOfDay { get; }
}
One trick we can use is to have a single loop that finds the corresponding item from the end of the list based on the current index. We can do this like:
for (int i = 0; i < activities.Count; i++)
var correspondingIndex = activities.Count - i - 1;
Notice that:
When i is 0, correspondingIndex is the last index in the array.
When i is 1, correspondingIndex is the second-to-last index in the array.
When i is activities.Count - 1 (the last index), correspondingIndex is 0
Using this trick, we can get the corresponding time differences at the same time as we populate a new list of Activity objects.
Hopefully this code makes it a little clearer:
public static IList<Activity> ReverseActivities(IList<Activity> activities)
{
// If activities is null or contains less than 2 items, return it
if ((activities?.Count ?? 0) < 2) return activities;
// This will contain the reversed list
var reversed = new List<Activity>();
for (int i = 0; i < activities.Count; i++)
{
// Get the corresponding index from the end of the list
var correspondingIndex = activities.Count - i - 1;
// Get the timespan from the corresponding items from the end of the list
var timeSpan = i == 0
? TimeSpan.Zero
: activities[correspondingIndex + 1].TimeOfDay -
activities[correspondingIndex].TimeOfDay;
// The new TimeOfDay will be the previous item's TimeOfDay plus the TimeSpan above
var timeOfDay = i == 0
? activities[i].TimeOfDay
: reversed[i - 1].TimeOfDay + timeSpan;
reversed.Add(new Activity(activities[correspondingIndex].Name, timeOfDay));
}
return reversed;
}
In use, this would look like:
var original = new List<Activity>
{
new Activity("A", new TimeSpan(0, 12, 0)),
new Activity("B", new TimeSpan(0, 12, 15)),
new Activity("C", new TimeSpan(0, 12, 40)),
new Activity("D", new TimeSpan(0, 13, 40))
};
var reversed = ReverseActivities(original);
Here's the output in the debug window (compare original and reversed):
This is quite simple using a bit of TimeSpan maths.
IList<Activity> input = new List<Activity>()
{
new Activity("A", TimeSpan.Parse("12:00")),
new Activity("B", TimeSpan.Parse("12:15")),
new Activity("C", TimeSpan.Parse("12:40")),
new Activity("D", TimeSpan.Parse("13:40")),
};
TimeSpan min = input.Min(x => x.TimeOfDay);
TimeSpan max = input.Max(x => x.TimeOfDay);
IList<Activity> output =
input
.Select(x => new Activity(
x.Name,
x.TimeOfDay.Subtract(max).Duration().Add(min)))
.OrderBy(x => x.TimeOfDay)
.ToList();
That gives me:
I tested this and it works:
DateTime[] times = { new DateTime(2020, 06, 17, 12, 00, 00),
new DateTime(2020, 06, 17, 12, 15, 00), new DateTime(2020, 06, 17, 12, 40, 00),
new DateTime(2020, 06, 17, 13, 40, 00) };
List<DateTime> newTimes = new List<DateTime>();
newTimes.Add(times[0]);
for (int i = 1; i < times.Length; i++) {
DateTime d = newTimes[i - 1].Add(times[times.Length - i] - times[times.Length - i - 1]);
newTimes.Add(d);
}
Using LinkedList:
static void Main(string[] args)
{
var list = new List<Location>
{
new Location{Name = "A", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 0, 0)) },
new Location{Name = "B", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 15, 0)) },
new Location{Name = "C", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(12, 40, 0)) },
new Location{Name = "D", TimeOffset = DateTimeOffset.MinValue.Add(new TimeSpan(13, 40, 0)) },
};
var route = new LinkedList<Location>(list);
WriteToConsole("Before: ", route);
var reversedRoute = Reverse(route);
Console.WriteLine();
WriteToConsole("After: ", reversedRoute);
Console.WriteLine(); Console.ReadKey();
}
public static LinkedList<Location> Reverse(LinkedList<Location> route)
{
LinkedList<Location> retVal = new LinkedList<Location>();
DateTimeOffset timeOffset = DateTimeOffset.MinValue;
var currentNode = route.Last;
while (currentNode != null)
{
var next = currentNode.Next;
if (next == null)
{
// last node, use the first node offset
timeOffset = DateTimeOffset.MinValue.Add(route.First.Value.TimeOffset - timeOffset);
}
else
{
timeOffset = timeOffset.Add(next.Value.TimeOffset - currentNode.Value.TimeOffset);
}
retVal.AddLast(new Location { Name = currentNode.Value.Name, TimeOffset = timeOffset });
currentNode = currentNode.Previous;
}
return retVal;
}
public static void WriteToConsole(string title, LinkedList<Location> route)
{
Console.Write($"{title}: ");
foreach (var i in route)
{
Console.Write($"\t({i.Name}, {i.TimeOffset.Hour:D2}:{i.TimeOffset.Minute:D2})");
}
}

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))
});

ASP.NET/C#: Get nearest future date based from list of days

I would like to get the nearest based from a list of days:
Scenario 1:
Date: July 22, 2013 (Monday)
Possible days: "Tuesday", "Wednesday", "Friday" (string values)
Answer: July 23, 2013 (Tuesday)
Scenario 2:
Date: July 23, 2013 (Tuesday)
Possible days: "Tuesday", "Wednesday", "Thursday", "Saturday"
Answer: July 24, 2013 (Wednesday)
Scenario 3:
Date: July 24, 2013 (Wednesday)
Possible days: "Monday", "Tuesday"
Answer: July 29, 2013 (Monday)
Any suggestions?
DateTime date = DateTime.Parse("July 22, 2013");
DayOfWeek dateDay = date.DayOfWeek;
DayOfWeek[] possibleDays = { DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Friday };
int addToBestAnswer = 7;
foreach (var checkDay in possibleDays)
{
if (checkDay-dateDay<addToBestAnswer)
{
addToBestAnswer = checkDay - dateDay;
}
}
DateTime Answer = date.AddDays(addToBestAnswer);
Edit: for only string input:
DateTime date = DateTime.Parse("July 22, 2013");
string[] possibleDays={ "Tuesday","Wednesday","Friday" };
List<int> pDays = new List<int>();
foreach (var inputDay in possibleDays)
{
pDays.Add(int.Parse(inputDay.Replace("Sunday", "0").Replace("Monday", "1").Replace("Tuesday", "2").Replace("Wednesday", "3").Replace("Thursday", "4").Replace("Friday", "5").Replace("Saturday", "6")));
}
int dateDay = (int)date.DayOfWeek;
int addToBestAnswer = 7;
foreach (var checkDay in pDays)
{
int difference = checkDay - dateDay;
if (difference<0)
{
difference = 7 + difference;
}
if (difference<addToBestAnswer&&difference!=0)
{
addToBestAnswer = difference;
}
}
DateTime Answer = date.AddDays(addToBestAnswer);
// Answer.ToShortDateString()+" ("+Answer.DayOfWeek.ToString()+")";
Like this ?
public DateTime GetNextPossibleDay(DateTime DT, DayOfWeek[] PossibleDays)
{
if (PossibleDays.Length == 0)
throw new Exception("No possible day.");
do
{
DT = DT.AddDays(1);
}
while (!PossibleDays.Contains(DT.DayOfWeek));
return DT;
}
You can check every date in the list, unless you get correct one, something like this:
var days = new List<string> {"Tuesday", "Monday"};
var startDate = new DateTime(2013, 7, 24).AddDays(1);
while (!days.Contains(startDate.DayOfWeek.ToString("G")))
{
startDate = startDate.AddDays(1);
}
Console.WriteLine(startDate);
Tested. This code works
List<DayOfWeek> days = new List<DayOfWeek>() { DayOfWeek.Tuesday, DayOfWeek.Wednesday };
DateTime sourceDate = DateTime.Now;
DayOfWeek currentDay = sourceDate.DayOfWeek;
int? smallestValue = null;
foreach (DayOfWeek d in days)
{
int currentValue = (int)d - (int)currentDay;
if (!smallestValue.HasValue)
smallestValue = currentValue;
if(smallestValue > currentValue)
smallestValue = currentValue;
}
DateTime nearestDate = sourceDate.AddDays(smallestValue.Value);
Not a fancy Linq, but this works =)
public static DateTime NearestDate(DateTime baseDateTime, List<string> acceptedDays)
{
DateTime result = new DateTime(baseDateTime.Year, baseDateTime.Month, baseDateTime.Day);
List<DayOfWeek> acceptedDoW = new List<DayOfWeek>();
acceptedDays.ForEach(x => acceptedDoW.Add((DayOfWeek)Enum.Parse(typeof(DayOfWeek), x, true)));
DayOfWeek currentDay = baseDateTime.DayOfWeek;
int closestDay = int.MaxValue;
acceptedDoW.ForEach(x =>
{
int currentSpan = (int)x;
if (x < currentDay)
currentSpan += 7;
currentSpan = currentSpan - (int)currentDay;
if (currentSpan < closestDay)
closestDay = currentSpan;
});
return result.AddDays(closestDay);
}
This should work:
var date = new DateTime(2013, 07, 24);
var posibleDays = new[]{DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Friday};
var nearestDay = posibleDays.Select(dow => new {
DayOfWeek = dow,
Diff = (7 + (dow - date.DayOfWeek)) % 7
})
.Where(x => x.Diff >= 1)
.OrderBy(x => x.Diff)
.FirstOrDefault();
Inspired by a question of myself some time ago. It assumes a day to be in the next week if the DayOfWeek value is earlier than another.
Here a demo with your sample data: http://ideone.com/VzZnzx

Find a range inside a range using LINQ

I've a date list
StartDate EndDate
1-Nov-2011 31-Jan-2012
3-Mar-2012 1-Apr-2012
1-May-2012 31-Dec-2012
1-Jan-2013 1-Dec-2013
Get all the records which falls in this range
1-Jan-2012 31-Dec-2012
The answer would be the first three records from the above list
How could i do it using Linq.
Thanks
This should work:
var rangeStart = new DateTime(2012, 1, 1);
var rangeEnd = new DateTime(2012, 12, 31);
var res = list
.Where(item => (item.StartTime < rangeStart ? rangeStart : item.StartTime) < (item.EndTime < rangeEnd ? item.EndTime : rangeEnd) )
.ToList();
The condition is "the larger of the two left ends needs to be less than the smaller of the two right ends".
I Suggest the following wrapper
public struct DateInterval
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool HasIntersection(DateInterval secondInterval)
{
return HasIntersection(this.StartDate, this.EndDate,secondInterval.StartDate,secondInterval.EndDate);
}
private bool HasIntersection(DateTime dateStart1, DateTime dateEnd1, DateTime dateStart2, DateTime dateEnd2)
{
if (dateEnd1 < dateStart2) return false;
if (dateEnd2 < dateStart1) return false;
return true;
}
}
usage:
var targetInterval = new DateInterval() {StartDate = new DateTime(2012, 1, 1), EndDate = new DateTime(2012, 1, 1)};
var listOfIntervals =GetIntervals();//retrieve data
var filteredList = listOfIntervals.Where(targetInterval.HasIntersection).ToList();

Need the sequence Order of Quarters of FY2011-12 based on a date given as input

ASP.NET using C#
The following are the Quarters for the financial year 2011-12
April 2011 to June2011 - Q1
July2011 to Sep2011 - Q2
Oct2011 to Dec2011 - Q3
Jan2012 to March 2012 - Q4
EDIT:
If i give a date as input then i need the output interms of the Quarter of that month:
Lets consider a date as input is 02-Jan-2012.
then i need the output as Q4
Lets take another date as input: 31May2012.
For this i need the output as Q1
Please help!!
Here is the function
public string GetQuarter(DateTime date)
{
// we just need to check the month irrespective of the other parts(year, day)
// so we will have all the dates with year part common
DateTime dummyDate = new DateTime(1900, date.Month, date.Day);
if (dummyDate < new DateTime(1900, 7, 1) && dummyDate >= new DateTime(1900, 4, 1))
{
return "Q1";
}
else if (dummyDate < new DateTime(1900, 10, 1) && dummyDate >= new DateTime(1900, 7, 1))
{
return "Q2";
}
else if (dummyDate < new DateTime(1900, 1, 1) && dummyDate >= new DateTime(1900, 10, 1))
{
return "Q3";
}
else
{
return "Q4";
}
}
Hope this could help.
static void Main(string[] args)
{
List<DateRange> range = new List<DateRange>();
//temp filling the data
DateTime start = new DateTime(2011, 4, 1);
range.Add(new DateRange() {From=start,To = start.AddMonths(3).AddMilliseconds(-1),Name="Q1"});
start = range.LastOrDefault().To.AddMilliseconds(1);
range.Add(new DateRange() { From = start, To = start.AddMonths(3).AddMilliseconds(-1), Name = "Q2" });
start = range.LastOrDefault().To.AddMilliseconds(1);
range.Add(new DateRange() { From = start, To = start.AddMonths(3).AddMilliseconds(-1), Name = "Q3" });
start = range.LastOrDefault().To.AddMilliseconds(1);
range.Add(new DateRange() { From = start, To = start.AddMonths(3).AddMilliseconds(-1), Name = "Q4" });
var order = range.OrderByDescending(r => r.IsCurrentQuater(DateTime.Now));
foreach (var itm in order)
Console.WriteLine(itm);
}
}
public class DateRange
{
public string Name { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public bool IsCurrentQuater(DateTime date)
{
return date >= From && date <= To;
}
public override string ToString()
{
return string.Format("{0} - {1} to {2}", Name, From, To);
}
}
Regards.

Categories