DateTime List find last date that before current - c#

I have a list of dates:
var dates = new List<DateTime>
{
new DateTime(2016, 01, 01),
new DateTime(2016, 02, 01),
new DateTime(2016, 03, 01),
new DateTime(2016, 04, 01),
new DateTime(2016, 05, 01)
};
Now given a certain date, a "StartDate". What is the easiest way to create a list of dates after the startdate, and the last date before?
I.E. - If I supply the date DateTime(2016, 03, 15), I need to return
DateTime(2016, 03, 01),
DateTime(2016, 04, 01),
DateTime(2016, 05, 01)
It could be as simple as finding the last "Active" date and then just using the where from that date. But I'm unsure on how to do this without making it really complicated.

If your list is already sorted, you can use a binary search:
var index = dates.BinarySearch(start);
// If the precise value isn't found, index will be the bitwise complement
// of the first index *later* than the target, so we need to subtract 1.
// But if there were no values earlier, we should start from 0.
if (index < 0)
{
index = Math.Max(~index - 1, 0);
}
return dates.Skip(index).ToList();
This assumes the dates are unique. If there are multiple dates the same as start, there's no guarantee that it will find the first one. If that's a concern, you'd need to search backwards until you found the first match.
You haven't specified whether if there's an exact match, you want to include the date before that or not. If you do, you'll need to adjust this code a bit.

Without making it complicated and if i understand your requirements correctly. You want all the dates after the StartDate and the last entry before the first matching (If Any). Then I find this the easiest most readable way of doing it:
var results = dates.FindAll(x => x >= StartDate);
int index = dates.FindLastIndex(x => x < StartDate);
// there might be no match, if all the list is resulted
if (index >= 0)
results.Insert(0, dates[index]);
If you prefer one query style, you can do the below (I find it not readable):
var results = dates.Where(x => x >= StartDate)
.Concat(dates.Where(x => x < StartDate)
.OrderByDescending(x => x).Take(1));
Last Alternative if you like fancy ways:
int startIndex = dates.FindLastIndex(x=> x < StartDate);
startIndex = Math.Max(0, startIndex);
var results = dates.Skip(startIndex).ToList();

var partialResult = dates.Where(x => x >= date).ToList();
partialResult.Add(dates.Where(x => x < date).Max());
IList<DateTime> result = partialResult.OrderBy(x => x).ToList();

Related

Algorithm to find the closest time

I have a list of DateTime values with dates that contain hours and minutes:
List<DateTime> times = times = new List<DateTime>()
{
new DateTime(2019, 01, 01, 17, 00, 00),
new DateTime(2019, 01, 01, 18, 45, 00),
new DateTime(2019, 01, 01, 19, 00, 00),
new DateTime(2019, 01, 01, 19, 30, 00),
new DateTime(2019, 01, 01, 22, 30, 00)
};
DateTime current = DateTime.Now;
I put them all in a ComboBox, and I want to make some sort of algorithm so when I load my form, it will check for the current time and find the closest value to the current time and select the ComboBox item that contains that hour.
How can I achieve this? I tried to loop through them all and check for the least hour, but that doesn't seem to work. Is there a smarter way to do it?
For example: If the current time is 17:32, it will choose 17:00, because that's the closest. But, if the current time is 18:20, it will choose 18:45 and so on.
Compare to the Ticks property of DateTime (MSDN). It can be seen as a linear representation of the whole date and timestamp and is sortable.
Do something like
comboBox.SelectedItem = times.OrderBy(t => Math.Abs(t.Ticks - current.Ticks)).First()
You could take the difference with DateTime.Now for all your datetimes, order by this difference and take the first result.
times.OrderBy(m => Math.Abs((DateTime.Now - m).TotalMilliseconds)).First();
You would have to select an instance of DateTime which minimizes the temporal distance to the current time. You could use an extension method for IEnumerable<T> to do that as follows.
public static T ArgMin<T, R>(T t1, T t2, Func<T, R> f)
where R : IComparable<R>
{
return f(t1).CompareTo(f(t2)) > 0 ? t2 : t1;
}
public static T ArgMin<T, R>(this IEnumerable<T> Sequence, Func<T, R> f)
where R : IComparable<R>
{
return Sequence.Aggregate((t1, t2) => ArgMin<T, R>(t1, t2, f));
}
var iNow = DateTime.Now;
var iResult = times.ArgMin(iTime => Math.Abs((iTime - iNow).Ticks));
Although very generic, this implementation does not involve any sorting.
You are looking for ArgMax which is not implemented in standard Linq, but can be emulated via Aggreagte
List<DateTime> times = new List<DateTime>() {
new DateTime(2019, 01, 01, 17, 00, 00),
new DateTime(2019, 01, 01, 18, 45, 00),
new DateTime(2019, 01, 01, 19, 00, 00),
new DateTime(2019, 01, 01, 19, 30, 00),
new DateTime(2019, 01, 01, 22, 30, 00),
};
DateTime toFind = new DateTime(2019, 5, 8, 18, 20, 0);
var closestTime = times
.Aggregate((best, item) => Math.Abs((item.TimeOfDay - toFind.TimeOfDay).Ticks) <
Math.Abs((best.TimeOfDay - toFind.TimeOfDay).Ticks)
? item
: best);
Please, note, that if we should find the closest time, we have to get rid of date part - TimeOfDay. If date part should be count, just remove TimeOfDay -
var closestDateAndTime = times
.Aggregate((best, item) => Math.Abs((item - toFind).Ticks) <
Math.Abs((best - toFind).Ticks)
? item
: best);
One option is to use MoreLinq's MinBy:
var actualNow = DateTime.Now;
// set `current` up however you need it
var current = new DateTime(2019, 01, 01, actualNow.Hour, actualNow.Minute, actualNow.Minute, actualNow.Millisecond); // set this up however you need it
var min = times.MinBy(z => Math.Abs((current - z).Ticks)).First();
It avoids the memory pressure of the OrderBy based solutions (since it avoids allocating a list to store the entire set of times).
Note you may want to check whether times is empty before using this (or other) solutions. If you don't wish to do that, consider using:
var min = times.MinBy(z => Math.Abs((current - z).Ticks)).Cast<DateTime?>().FirstOrDefault();
which will return null (rather than default(DateTime), or throw an exception) if times is empty.

Find missing date in List<DateTime> and insert that date on the right index

Say I have a List with the following dates.
List<DateTime> dateList = new List<DateTime>();
dateList.Add(new DateTime(2002, 01, 01));
dateList.Add(new DateTime(2002, 01, 02));
dateList.Add(new DateTime(2002, 01, 03));
dateList.Add(new DateTime(2002, 01, 04));
dateList.Add(new DateTime(2002, 01, 06));
dateList.Add(new DateTime(2002, 01, 08));
How can I iterate through the dateList, find the DateTimes missing (2002-01-05 and 2002-01-07), then create those DateTimes and add them to the dateList on the correct index?
You can use following approach that determines the min- and max-dates and the TimeSpan difference and then generates the list:
DateTime min = dateList.Min();
DateTime max = dateList.Max();
TimeSpan diff = max - min;
dateList = Enumerable.Range(0, diff.Days + 1).Select(d => min.AddDays(d)).ToList();
You need to use diff.Days + 1 to include the end-date.
If you can't use LINQ for whatever reason you could use a for-loop and List.Insert, but you have to use List.Sort beforehand if you aren't sure whether the list is sorted or not:
dateList.Sort();
if (dateList.Count > 1)
{
for (int i = 0; i < dateList.Count - 1; i++)
{
DateTime currrent = dateList[i].Date;
DateTime next = dateList[i + 1].Date;
DateTime expected = currrent.AddDays(1);
if (next != expected)
{
dateList.Insert(++i, expected);
}
}
}
You see how much more readable the LINQ version is.
The idea here is to loop and find missing dates, and add it into a new list. At the end, join the two list and reorder by date. Not the best performance, but the simple to understand/maintain.
var dates = new List<DateTime>();
dates.Add(new DateTime(2002, 01, 01));
dates.Add(new DateTime(2002, 01, 02));
dates.Add(new DateTime(2002, 01, 03));
dates.Add(new DateTime(2002, 01, 04));
dates.Add(new DateTime(2002, 01, 06));
dates.Add(new DateTime(2002, 01, 08));
// algo works only if dates are in order
dates = dates.OrderBy(date => date).ToList();
var missingDates = new List<DateTime>();
for (int i = 0; i + 1 < dates.Count; i++)
{
var diff = (dates[i + 1] - dates[i]).TotalDays;
for(int iToComplete = 1; iToComplete < diff; iToComplete++)
{
missingDates.Add(dates[i].AddDays(iToComplete));
}
}
dates.AddRange(missingDates);
dates = dates.OrderBy(date => date).ToList();
You can just generate list of all dates when you have start and end date. And then use Except to find dates missing in original list:
var startDate = dateList.Min();
var endDate = dateList.Max();
var allDates = Enumerable.Range(0, (endDate - startDate).Days)
.Select(i => startDate.AddDays(i));
var missingDates = allDates.Except(dateList);
Missing dates:
1/5/2002
1/7/2002

Excluding dates from period using Nodatime

I'm trying to workout the amount of time between two LocalDateTime values and exclude specific dates (in this example, it's bank holidays).
var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);
var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
The differenceBetween value shows the number of days/hours/minutes/seconds between the two dates, as you would expect.
I could check every single day from the start date and see if the bankHolidays collection contains that date e.g.
var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);
var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
var london = DateTimeZoneProviders.Tzdb["Europe/London"];
for (var i = 1; i < differenceBetween.Days; ++i)
{
var x = localDateTime1.InZoneStrictly(london) + Duration.FromStandardDays(i);
if (bankHolidays.Any(date => date == x.Date))
{
//subtract one day for the period.
}
}
I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?
I also need to include weekends in this exclusion too, the obvious way seems to be to check the day of the week for weekends whilst checking bank holidays, this just doesn't seem like the best/correct way of handling it though.
I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?
Well, it's relatively easy to count the number of bank holidays included in a date-to-date range:
Sort all the bank holidays in chronological order
Use a binary search to find out where the start date would come in the collection
Use a binary search to find out where the end date would come in the collection
Subtract one index from another to find how many entries are within that range
Work out the whole period using Period.Between as you're already doing
Subtract the number of entries in the range from the total number of days in the range
The fiddly bit is taking into account that the start and/or end dates may be bank holidays. There's a lot of potential for off-by-one errors, but with a good set of unit tests it should be okay.
Alternatively, if you've got relatively few bank holidays, you can just use:
var period = Period.Between(start, end,
PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
var holidayCount = holidays.Count(x => x >= start && x <= end);
period = period - Period.FromDays(holidayCount);
Just use TimeSpan to get the difference, all times are in your current local time zone:
var bankHolidays = new[] { new DateTime(2013, 12, 25), new DateTime(2013, 12, 26) };
var localDateTime1 = new DateTime(2013, 11, 18, 10, 30, 0);
var localDateTime2 = new DateTime(2013, 12, 29, 10, 15, 0);
var span = localDateTime2 - localDateTime1;
var holidays = bankHolidays[1] - bankHolidays[0];
var duration = span-holidays;
Now duration is your time elapsed between localDateTime1 and localDateTime2.
If you want to exlude two dates via the bankHolidays you can easiely modify the operations above.
You might use an extra method for this operation:
public static TimeSpan GetPeriod(DateTime start, DateTime end, params DateTime[] exclude)
{
var span = end - start;
if (exclude == null) return span;
span = exclude.Where(d => d >= start && d <= end)
.Aggregate(span, (current, date) => current.Subtract(new TimeSpan(1, 0, 0, 0)));
return span;
}
Now you can just use this:
var duration = GetPeriod(localDateTime1, localDateTime2, bankHolidays);

Check if date range is sequential in c#?

Assume I have a user interface where the user can select days. Is there a way to check if the days selected are sequential, such as:
4/4, 4/5, 4/6, 4/7, 4/8, 4/9, 4/10 or
4/29, 4/30, 5/1, 5/2, 5/3
I know I probably can loop through the date range and check, but I was more curious if there was a built in method already to check for this.
Regarding the above scenarios, they are in order and they can roll over into the next month.
I am using the .NET Framework 2.0 and can't use LINQ.
Regarding Tom's answer:
DateTime dtStart = new DateTime(2011,5,4);
DateTime dtEnd = new DateTime(2011,5,11);
int numberOfDaysSelected = 7; //Assume 7 days were selected.
TimeSpan ts = dtEnd - dtStart;
if(ts.Days == numberOfDaysSelected - 1)
{
Console.WriteLine("Sequential");
}
else
{
Console.WriteLine("Non-Sequential");
}
I do not believe there is a built in method to achieve your desired results but if you can easily tell the earliest and latest dates, you could create a new TimeSpan by subtracting the the earliest date from the latest date and then verifying that the number of days of the timespan matches the number of dates selected - 1.
You didn't tell us if the days are ordered.
You didn't tell us if they might fall over a month boundary as in
30, 31, 1.
I'll assume ordered, and I'll assume they won't fall over a month boundary (because your example is ordered, and it doesn't fall over a month boundary).
Then you can say
public bool IsSequential(this IEnumerable<DateTime> sequence) {
Contract.Requires(sequence != null);
var e = sequence.GetEnumerator();
if(!e.MoveNext()) {
// empty sequence is sequential
return true;
}
int previous = e.Current.Date;
while(e.MoveNext()) {
if(e.Current.Date != previous.AddDays(1)) {
return false;
}
previous = e.Current.Date;
}
return true;
}
Note that this solution requires only walking the sequence once. If you don't have an ordered sequence, or if you permit falling over a month boundary the solution is more complicated.
Nothing built in but you can build one easily using Linq:
List<DateTime> timeList = new List<DateTime>();
//populate list..
bool isSequential = timeList.Zip(timeList.Skip(1),
(a, b) => b.Date == a.Date.AddDays(1))
.All(x => x);
Edited - misunderstood question first to mean ascending in time as opposed to sequential - fixed that.
Extension method using Linq:
public static bool IsContiguous(this IEnumerable<DateTime> dates)
{
var startDate = dates.FirstOrDefault();
if (startDate == null)
return true;
//.All() doesn't provide an indexed overload :(
return dates
.Select((d, i) => new { Date = d, Index = i })
.All(d => (d.Date - startDate).Days == d.Index);
}
Testing it:
List<DateTime> contiguousDates = new List<DateTime>
{
new DateTime(2011, 05, 05),
new DateTime(2011, 05, 06),
new DateTime(2011, 05, 07),
};
List<DateTime> randomDates = new List<DateTime>
{
new DateTime(2011, 05, 05),
new DateTime(2011, 05, 07),
new DateTime(2011, 05, 08),
};
Console.WriteLine(contiguousDates.IsContiguous());
Console.WriteLine(randomDates.IsContiguous());
Returns
True
False
EDIT:
.NET 2-like answer:
public static bool CheckContiguousDates(DateTime[] dates)
{
//assuming not null and count > 0
var startDate = dates[0];
for (int i = 0; i < dates.Length; i++)
{
if ((dates[i] - startDate).Days != i)
return false;
}
return true;
}
You can use the TimeGapCalculator of the Time Period Library for .NET to find gaps between multiple time periods (independent of order, count and overlapping):
// ----------------------------------------------------------------------
public void SequentialPeriodsDemo()
{
// sequential
ITimePeriodCollection periods = new TimePeriodCollection();
periods.Add( new Days( new DateTime( 2011, 5, 4 ), 2 ) );
periods.Add( new Days( new DateTime( 2011, 5, 6 ), 3 ) );
Console.WriteLine( "Sequential: " + IsSequential( periods ) );
periods.Add( new Days( new DateTime( 2011, 5, 10 ), 1 ) );
Console.WriteLine( "Sequential: " + IsSequential( periods ) );
} // SequentialPeriodsDemo
// --------------------------------------------------------------------
public bool IsSequential( ITimePeriodCollection periods, ITimePeriod limits = null )
{
return new TimeGapCalculator<TimeRange>(
new TimeCalendar() ).GetGaps( periods, limits ).Count == 0;
} // IsSequential

is there a formula to calculate recurrence

I'm using ASP.Net MVC2. I would like to know if there is there a formula to calculate recurrence date? So from my client side I'm selecting dates and using ajax.post to send to the controller. My expecting result would be like so for example:
maxdate is September 30th
currentdate is today
duration is 3 days for every week
so output would be
aug12-aug14
aug19-aug21
aug26-28 until the end of september
Enumerable.Range(0, int.MaxValue)
.Select(i => new
{
start = DateTime.Today.AddDays(7*i),
end = DateTime.Today.AddDays(7*i + 2)
})
.TakeWhile(d => d.end <= new DateTime(2010, 9, 30))
Unless you're looking for the dates in between start and end inclusive:
Enumerable.Range(0, int.MaxValue)
.SelectMany(i => new[]
{
DateTime.Today.AddDays(7*i),
DateTime.Today.AddDays(7*i + 1),
DateTime.Today.AddDays(7*i + 2)
})
.TakeWhile(d => d <= new DateTime(2010, 9, 30))

Categories

Resources