Determining Date Overlapping in a list of dates - c#

I have a list of items that each have a start and end date time component.
var myDates= new List<Tuple<DateTime, DateTime>>();
Which I fill it out with some date.
Now I wanted to loop through them and see if any two of those have any overlapping date rang. So I did this:
var myOverlapList = (from start in myDates
from endDate in myDates
where !Equals(start, end)
where start.Item1 <= end.Item2 && start.Item2 >= end.Item1
select end);
It works when dates have overlap for example one day back and forth between two dates BUT it does NOT work when two date entries have the EXACT SAME values.
So how I can fix my code or just something else to achieve that.

The
where !Equals(startDate, endDate)
line, which is supposed to filter out the same date tuple actually is filtering out any duplicate, so any matching timespan falls out of the selection. So your query will return all DateTime tuples, which overlap with some other tuple in the collection, but only unique. And you want also to return tuples if they encounter in your collection more then once.Your problem, actually is that you can not differentiate between two different items with the same value. So you need a discriminator for them and because you use a list, the index of an item fits well. You can cast your Tuple<DateTime, DateTime> collection into, e. g. {int id, Tuple<DateTime, DateTime> range} object by
var datesWithId = dates.Select((d, i) => new {id = i, range = d});
and then modify your query like this:
var anyOverlap = (from startDate in datesWithId
from endDate in datesWithId
where startDate.id!=endDate.id
&& startDate.range.Item1 <= endDate.range.Item2
&& startDate.range.Item2 >= endDate.range.Item1
select endDate.range).Distinct();

Related

How to filter list that has date and retrieve only data within 7 days

So I have this model:
Student Model
public int StudentId {get; set;}
public string StudentName {get; set;}
public DateTime EnrollDate {get; set;}
I also have a list of student Model which is something like
List<Student> listOfStudents = new List<Student>();
and inside that list there are 100 students detail and the enroll date.
What I do next is to sort the list into showing from the latest one to the oldest one.
listOfStudents.Sort((x, y) => DateTime.Compare(y.EnrollDate, x.EnrollDate));
and it's working. However, I am currently struggling in showing only the EnrollDate within 7 days from Today.
Conceptually, I think of LINQ a lot like SQL. You have the SELECT portion, which is your projection (i.e. what am I pulling out of this set of data?). If you omit the Select() clause from LINQ, you'll get the whole record vs. only a portion if you wanted to pluck out only pieces of it. You have your WHERE portion which is a limiter, or filter condition that when applied to the set pulls back only the records that satisfy said condition. And lastly, there are operations you can apply that affect the order of the returned set. That's where the OrderBy() and OrderByDescending() come into play. So lets map those concepts to the examples below
No Select(), but we do have a Where() and an OrderBy()
var then = DateTime.Now.AddDays(-7); //One portion of our Where. More below
var sortedStudents = listOfStudents
//Our predicate. 's' = the Student passed to the function. Give me only the students
//where s.EnrollDate is greater or equal to the variable 'then' (defined above)
.Where(s => s.EnrollDate >= then)
//We have no Select statement, so return whole students
//And order them by their enrollment date in ascending order
.OrderBy(s => s.EnrollDate);
When run, sortedStudents will be loaded up only with students (entire Student objects, not a projection) that meet our Where() criteria. The Where() function takes predicate that specifies our criteria. A predicate is simply a function that accepts a record from the set that we're filtering, and returns a bool indicating whether or not it should be included.
Let's change the filter by adjusting the Where()
//Notice we've changed 'then' from 7 days ago to a fixed point in time: 26 June 2018
var then = new DateTime.Parse("26 June 2018");
var sortedStudents = listOfStudents
.Where(s => s.EnrollDate >= then)
//Still no Select(). We'll do that next
.OrderBy(s => s.EnrollDate);
Just like before sortedStudents will have whole Student records, but this time it will only contain those enrolled after or on 26 June 2018, as specified by our predicate.
Let's add a Select()
var then = new DateTime.Parse("26 June 2018");
var dates = listOfStudents
.Where(s => s.EnrollDate >= then)
.Select(s => s.EnrollDate);
Now we've changed it so that instead of pulling back a whole Student we're only plucking out the EnrollDate. Notice I've changed the name of the receiving variable from sortedStudents to dates reflecting the fact that it now only contains a list of DateTime objects.
You could still replace .OrderBy() with .OrderByDescending() to change the order.
How about breaking down the problem into 2 sub-problems?
Sub-problem #1
showing only the EnrollDate within 7 days from Today
We only need Students whose EnrollDate property is within 7 days from today:
var today = DateTime.UtcNow;
sevenDaysOldList = listOfStudents.Where(x => (today - x.EnrollDate).TotalDays < 7);
The subtraction of the two dates results in a TimeSpan with a TotalDays property, which we can use to determine the number of days elapsed between the two dates.
Sub-problem #2
sort the list into showing from the latest one to the oldest one.
We need to sort sevenDaysOldList by EnrollDate in descending order:
sevenDaysOldList.Sort((x, y) => y.EnrollDate.CompareTo(x.EnrollDate));
..which will sort the list in place. OrderByDescending is a good candidate for this (it returns a new ordered list implementing IOrderedEnumerable<T>):
sevenDaysOldList.OrderByDescending(x => x.EnrollDate);
// and of course .OrderBy(x => x.EnrollDate) for ascending order
Combine #1 & #2
You can now combine the solutions of the two sub-problems into one. How you do it is at your own discretion. This is how I would do it:
var sevenDaysOldList = listOfStudents.Where(x => (today - x.EnrollDate).TotalDays < 7)
.OrderByDescending(x => x.EnrollDate);
Update: question in comment
How do I modify/sort the list that remove all the list less than "26 June 2018" ? So the list will only have data date greater than 26 June 2018. Any data with date before 26 June will be removed
You can initialize that date in a DateTime variable, and use it with List<T>.RemoveAll(Predicate<T>), to remove items in sevenDaysOldList which are smaller than that date:
var filterDate = new DateTime(2018, 06, 26);
sevenDaysOldList.RemoveAll(x => x.EnrollDate < filterDate);

Linq: add objects from one list to another with an other type, (MVC, C#)

I have a "FindItemsResults<Appointment> list" which contains appointment objects.
I want to select "Start" property value and of all appointment objects from this List and save these values in an other list of type DateTime ... List<DateTime> avaliableBookingDays
List<DateTime> avaliableBookingDays = new List<DateTime>();
FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);
IEnumerable<DateTime> res = from x in appointments
select new DateTime { (x.Start};
avaliableBookingDays.AddRange(res);
My LINQ exepression fails:
IEnumerable<DateTime> res = from x in appointments
select new DateTime { (x.Start};
I get error, like that:
Cannot inittialize type "DateTime" with a collection inittializer
because it do not implement "System.Collections.IEnumerable"
The above-mentioned LINQ must do this:
foreach (var appObj in appointments)
{
avaliableBookingDays.Add(appObj.Start);
}
Your code is not even going to compile
IEnumerable<DateTime> res = from x in appointments
select new DateTime { (x.Start};
because it has syntax errors ! In your select part, you are projecting to a new DateTime object and you have just an opening ( , which does not make any sense!
I think you were getting the mentioned error when you had the closing bracket as well like
IEnumerable<DateTime> res = from x in appointments
select new DateTime { (x.Start)};
Here your code is trying (assume it was valid) to project the items from appointments collection to another collection of DateTime with some invalid syntax ! There is no need to use the new keyword here as you are not trying to create an object of a view model class/annonymous object since all you need is the Start property value.
Solution
Since Start is the DateTime type property, you want to select only that.
List<DateTime> avaliableBookingDays = (from x in appointments
select x.Start).ToList();
Or
List<DateTime> avaliableBookingDays = appointments.Select(a=>a.Start).ToList();
If you want to project items in sequence to some other type, use Enumerable.Select:
var avaliableBookingDays = appointments.Select(a => a.Start).ToList();
Note that there is no equivalent of ToList() operator in query syntax. So the best you can do is get IEnumerable<DateTime> result and convert it to list later:
IEnumerable<DateTime> res = from x in appointments
select x.Start;
var avaliableBookingDays = res.ToList();
Also keep in mind - DateTime is a value type. You don't need to create new instance of this type to get a copy of appointment start time - copy will be created automatically.

Select if criteria is met

Lets say I have a datatable with three columns: timestamp, curveID and price. I would like to give a time and then select for each day the timestamp, curveID and price but only if all curveIDs are present.
The problem is, not for every time all the data is present, so at 10:00:00 there might be only data for curveID 1 but nothing for ID =2, and so forth.
I thought I could do the following to select the first dataset where all curveIDs are there and time is greater or equal to my criteria:
dataSet.ReadXml(#"C:\temp\Prices.xml", XmlReadMode.InferTypedSchema);
ds = dataSet.Tables[0];
var dt = ds.Clone();
int criteria = 10;
var list = ds.AsEnumerable().Where(x => x.Field<DateTime>("Timestamp").Hour >= criteria)
.GroupBy(x => new{Date = x.Field<DateTime>("Timestamp").Date, Curve = x.Field<object>("CurveID")})
.First().ToList();
However, this returns multiple records on the same day (at different times) for the same curve ID.
I would like to return only a single record for each curveID on each day at a time close to the criteria time where all curveIDs are present.
For clarity, lets say I m looking for curveID 1 & 2, if at 10:00:00 on day 1 only curveID 1 is present but curveID 2 is missing I would need to check whether at 10:01:00 both are there, if yes I take for that day the two record sets from that time. This I would have to check for every day in the database
// criteria is your integer Hour representation
var criteria = 10;
// array of curveIds to look for
var curveIds = new int[] {1, 2};
var result =
// grouping by date first
ds.GroupBy(x => x.Field<DateTime>("Timestamp").Date,
(date, items) => new { date, items = items
// items with the same timestamp go to one group
.GroupBy(i => i.Field<DateTime>("Timestamp"), (datetime, timestampItems) => new { datetime, timestampItems })
// filter by criteria
.Where(dti => dti.datetime.Hour >= criteria)
// filter by curveIds
.Where(dti => curveIds.All(cid => dti.timestampItems.Any(tsi => tsi.Field<int>("curveID") == cid)))
.OrderBy(dti => dti.datetime)
.FirstOrDefault() });
In the end you will receive a "per day" result fitting all your mentioned requirements: occurs after some criteria, have all curveIds, be earliest one.
You may want to group by Date first and then by hour using something like
group thing by new {
firstThing = x.Field<DateTime>("TimeStamp").Date,
secondThing = x.Field<DateTime>("TimeStamp").Date.Hour,
}
My syntax is probably off by a little, but that should get you moving in the right direction

Group-by specific time ranges in LINQ

I have a
List<Advertisement>
where Advertisement contains
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
What I want to do is group the elements in the List<Advertisement> by three specific time ranges using a GroupBy. The three time ranges are as follows:
x => x.StartDate > DateTime.Now.Subtract(TimeSpan.FromDays(1))
x => x.StartDate > DateTime.Now.Subtract(TimeSpan.FromDays(7))
x => x.StartDate > DateTime.Now.Subtract(TimeSpan.FromDays(365))
So elements with start date in the last day, elements with start date in the last week and elements with start date in the last year.
The group of elements from the last year should include those elements with a start date in the last week and day. And the group of elements from the last week should include those elements from the last day.
I just cant seem to think how to do this. Cheers.
As you want each group to also include the previous groups contents (eg lastweek includes last day) I've constructed a union query
var today=items.Where(l=>l.StartDate>DateTime.Now.Subtract(TimeSpan.FromDays(1)));
var lastweek=items.Where(l=>l.StartDate>DateTime.Now.Subtract(TimeSpan.FromDays(7)));
var lastyear=items.Where(l=>l.StartDate>DateTime.Now.Subtract(TimeSpan.FromDays(365)));
var result = today.Select(d => new { Group="Last Day",item=d})
.Union(lastweek.Select(w => new {Group="Last Week",item=w}))
.Union(lastyear.Select(y => new {Group="Last Year",item=y}))
.GroupBy (l => l.Group,l=>l.item);
How this query works is it creates 3 sub queries to select the relevant data.
Each query then uses a select operator to select the match group name and the original item projected into an anonymous object. (Basically creates a new object with the groupname as one property and the original Item as another).
I then use union to combine the multiple results together in one big list. (Union has the added property that it strips duplicates, but there shouldn't be any). Once I have the big list I can then Group by the groupname, the second parameter basically puts the orginal item back in as the group value.
You can create a method (or extension) wich will return some value specific to a group, and then make grouping by it.
One method may be to write an extension method which returns the category that it falls into.
public static int GetCategory(this Advertisement advert)
{
if(x.StartDate > DateTime.Now.Subtract(TimeSpan.FromDays(1)))
{
return 1;
}
etc...
}
then you can group by the GetCategory property. It is probably better to return an Enum instead of an integer.

Get list of months from List<DateTime>

I'm using a List in ASP.NET 3.5/C# to filter an existing list of dates (about 20 in total) on a specific month. So if a user selects a year of 2010 (ddlFromYear.SelectedItem.Text == 2010) then the returned list would consist of only 8 months because we only go up to August.
My question is - how do I output the DateTime as an int, or even preferrably a Month, e.g. "August". That way when I'm binding another DropDown I can list out all the months (January, February...) which as I mentioned would be determined by years (2009, 2010...)
int yearSelected;
bool success = Int32.TryParse(ddlFromYear.SelectedItem.Text, out yearSelected);
if (success)
{
List<DateTime> datesSelected = new List<DateTime>();
datesSelected =
(from n in dates
where n.Year.Equals(yearSelected)
select n).ToList();
dateMonths.Sort();
ddlFromMonth.Items.Clear();
ddlFromMonth.DataSource = datesSelected;
ddlFromMonth.DataBind();
}
If you want the dates expressed as the month name, you would do something like
List<string> months = (from n in dates
where n.Year.Equals(yearSelected)
select n.ToString("MMM")).ToList();
// optionally include call to .Distinct() prior to .ToList() if there
// could be duplicates and you want to exclude them
Which would create { "January", "February", "March" /* etc. */ };
The selected answer is good for the original problem. For anyone who may come to this question and needs to run this over a larger number of dates (ie. not limited to a year), it should be more efficient to do it like this:
DateTimeFormatInfo formatInfo = CultureInfo.CurrentCulture.DateTimeFormat;
List<string> month = dates.Select(n => n.Month)
.Distinct()
.OrderBy(n => n)
.Select(n => formatInfo.GetMonthName(n))
.ToList();

Categories

Resources